mirror of
https://github.com/acaloiaro/garbagespeak.com
synced 2026-07-21 18:29:08 +00:00
124 lines
2.5 KiB
Go
124 lines
2.5 KiB
Go
|
|
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
|
||
|
|
}
|