You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用goquery处理HTML时自闭合标签被错误嵌套的问题求助

Goquery处理自定义自闭合标签时结构异常的解决办法

问题场景

处理包含自定义自闭合标签(如<en-media>)的HTML内容时,使用goquery的NewDocumentFromReader加载内容后未做任何修改,但调用doc.Html()输出时,<en-media>标签会错误地将后续所有元素嵌套在自身内部,导致HTML结构异常。

原始HTML

<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div><br/></div><en-media hash="667676uy" type="application/pdf" /><ul style="--en-todo:true;"><li style="--en-checked:false;"><div>test todoist</div></li></ul><div><br/></div><div><br/></div></en-note>

加载代码

doc, err := goquery.NewDocumentFromReader(bytes.NewReader([]byte(content)))

异常输出HTML

<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div><br/></div><en-media hash="667676uy" type="application/pdf"><ul style="--en-todo:true;"><li style="--en-checked:true;"><div>Otra prueba de Todoist</div></li></ul><div><br/></div><div><br/></div></en-media></en-note>

原因分析

goquery底层依赖golang.org/x/net/html包的HTML5解析器,该解析器默认将自定义标签视为非自闭合的块级元素。即使原始代码中写了自闭合形式的<en-media ... />,解析器仍会认为该标签需要显式的闭合标签,因此将后续所有内容当作其子元素,直到文档结束才自动添加</en-media>闭合标签。

解决方案

方案1:配置解析器识别自定义自闭合标签

修改html.DefaultTreeSanitizer的AllowSelfClosing集合,将自定义标签加入其中,告知解析器该标签为自闭合类型:

import (
    "bytes"
    "github.com/PuerkitoBio/goquery"
    "golang.org/x/net/html"
    "golang.org/x/net/html/atom"
)

func main() {
    content := `<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div><br/></div><en-media hash="667676uy" type="application/pdf" /><ul style="--en-todo:true;"><li style="--en-checked:false;"><div>test todoist</div></li></ul><div><br/></div><div><br/></div></en-note>`

    // 将自定义标签en-media加入允许自闭合的集合
    html.DefaultTreeSanitizer.AllowSelfClosing[atom.Get("en-media")] = true

    doc, err := goquery.NewDocumentFromReader(bytes.NewReader([]byte(content)))
    if err != nil {
        panic(err)
    }

    htmlStr, err := doc.Html()
    if err != nil {
        panic(err)
    }
    println(htmlStr)
}

方案2:字符串替换修正异常输出

如果不想修改解析器配置,可在输出HTML后,通过字符串替换修正错误的标签结构(适合简单场景):

import (
    "bytes"
    "strings"
    "github.com/PuerkitoBio/goquery"
)

func main() {
    content := `<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div><br/></div><en-media hash="667676uy" type="application/pdf" /><ul style="--en-todo:true;"><li style="--en-checked:false;"><div>test todoist</div></li></ul><div><br/></div><div><br/></div></en-note>`

    doc, err := goquery.NewDocumentFromReader(bytes.NewReader([]byte(content)))
    if err != nil {
        panic(err)
    }

    htmlStr, err := doc.Html()
    if err != nil {
        panic(err)
    }

    // 替换错误的标签结构
    fixedHtml := strings.Replace(htmlStr, `<en-media hash="667676uy" type="application/pdf">`, `<en-media hash="667676uy" type="application/pdf" />`, 1)
    fixedHtml = strings.Replace(fixedHtml, `</en-media>`, "", 1)
    println(fixedHtml)
}

方案3:用XML解析模式处理ENML

由于ENML本质是XML格式,可先用XML解析器解析内容,再转换为goquery文档(需确保输入为严格XML格式):

import (
    "bytes"
    "encoding/xml"
    "strings"
    "github.com/PuerkitoBio/goquery"
    "golang.org/x/net/html"
)

// 将XML节点转换为HTML节点
func xmlToHtmlNode(xmlNode xml.Node) (*html.Node, error) {
    htmlNode := &html.Node{Type: html.ElementNode, Data: xmlNode.Data}
    // 复制属性
    for _, attr := range xmlNode.Attr {
        htmlNode.Attr = append(htmlNode.Attr, html.Attribute{Key: attr.Name.Local, Val: attr.Value})
    }
    // 递归处理子节点
    for _, child := range xmlNode.Child {
        switch child.Type {
        case xml.ElementNode:
            childHtmlNode, err := xmlToHtmlNode(*child)
            if err != nil {
                return nil, err
            }
            htmlNode.AppendChild(childHtmlNode)
        case xml.TextNode:
            htmlNode.AppendChild(&html.Node{Type: html.TextNode, Data: child.Data})
        }
    }
    return htmlNode, nil
}

func main() {
    content := `<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div><br/></div><en-media hash="667676uy" type="application/pdf" /><ul style="--en-todo:true;"><li style="--en-checked:false;"><div>test todoist</div></li></ul><div><br/></div><div><br/></div></en-note>`

    // 移除DOCTYPE,避免XML解析报错
    xmlContent := strings.Replace(content, `<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">`, "", 1)
    var root xml.Node
    err := xml.Unmarshal([]byte(xmlContent), &root)
    if err != nil {
        panic(err)
    }

    htmlRoot, err := xmlToHtmlNode(root)
    if err != nil {
        panic(err)
    }

    doc := goquery.NewDocumentFromNode(htmlRoot)
    htmlStr, err := doc.Html()
    if err != nil {
        panic(err)
    }

    // 加回DOCTYPE
    fixedHtml := `<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">` + htmlStr
    println(fixedHtml)
}

内容的提问来源于stack exchange,提问作者Manzurul Hoque Rumi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 07:32:21