garbagespeak.com/html_parser/parser.go

124 lines
2.5 KiB
Go
Raw Normal View History

package html_parser
import (
"bytes"
"io"
"log"
"strings"
"golang.org/x/net/html"
)
func htmlGetAttr(n *html.Node, key string) (string, bool) {
for _, attr := range n.Attr {
if attr.Key == key {
return attr.Val, true
}
}
return "", false
}
// htmlRenderNode renders a node, excluding its children
func htmlRenderNode(n *html.Node) string {
var buf bytes.Buffer
w := io.Writer(&buf)
// walk backwards through the document tree to find the root
for {
if n.Parent != nil {
n = n.Parent
continue
}
break
}
err := html.Render(w, n)
if err != nil {
return ""
}
return buf.String()
}
func htmlHasID(n *html.Node, id string) bool {
if n.Type == html.ElementNode {
s, ok := htmlGetAttr(n, "id")
if ok && s == id {
return true
}
}
return false
}
func htmlWalkTree(n *html.Node, id string) *html.Node {
if htmlHasID(n, id) {
return n
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
res := htmlWalkTree(c, id)
if res != nil {
return res
}
}
return nil
}
func htmlGetByID(n *html.Node, id string) *html.Node {
return htmlWalkTree(n, id)
}
// ParseAndSplice parses HTML within 'file' and splices `htmlContent` into its node tree at `id`
//
// Returns rendered HTML content as a string
func ParseAndSplice(file io.Reader, id, htmlContent string) (content string) {
doc, err := html.Parse(file)
if err != nil {
log.Fatal(err)
}
doc = htmlGetByID(doc, id)
if doc == nil {
return
}
// Get rid of the element's children, as we want to swap our own content in as this element's _only_ content
doc.FirstChild = nil
doc.LastChild = nil
// Parse the content we're swapping in
// This creates a "well-formed" tree, meaning that it adds <html><head></head><body><our node/></body></html>
// but we don't want a well-formed tree, we want our new content as a single Node. The tree parents will be
// removed
newHtml, err := html.Parse(strings.NewReader(htmlContent))
if err != nil {
log.Fatal(err)
}
// Remove the tree parents, as we do not want a "well-formed" tree
for {
if newHtml.LastChild == nil {
break
}
newHtml = newHtml.LastChild
}
// We've walked all the way down the new tree to the lowermost node of our new HTML tree.
// Get its parent to get the outermost element of the new content (spliced in nodes should
// not consist of nested node)
newHtml = newHtml.Parent
// "detach" the new node so it can be spliced into the tree
newHtml.Parent = nil
newHtml.PrevSibling = nil
newHtml.NextSibling = nil
doc.AppendChild(newHtml)
content = htmlRenderNode(doc)
return
}