使用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
相关产品推荐
相关产品推荐

