如何在Go中反序列化含任意嵌套navPoint的epub toc.ncx文件?
NCX文件的navMap包含可无限嵌套的navPoint元素,直接用值类型自引用结构体结合标准库xml包反序列化会触发栈溢出——因为xml包处理递归结构时会无限递归分配值类型实例,耗尽栈内存。以下是两种可行的解决方法:
方法1:使用指针类型的自引用字段
将结构体中存储子navPoint的字段定义为指针切片,让xml包处理引用而非重复分配值类型实例,避免栈溢出。
示例代码:
package main import ( "encoding/xml" "fmt" "strings" ) type NCX struct { XMLName xml.Name `xml:"ncx"` NavMap NavMap `xml:"navMap"` } type NavMap struct { XMLName xml.Name `xml:"navMap"` NavPoint []*NavPoint `xml:"navPoint"` // 子节点用指针切片 } type NavPoint struct { XMLName xml.Name `xml:"navPoint"` ID string `xml:"id,attr"` NavLabel NavLabel `xml:"navLabel"` Content Content `xml:"content"` NavPoint []*NavPoint `xml:"navPoint"` // 自引用字段用指针切片 } type NavLabel struct { XMLName xml.Name `xml:"navLabel"` Text string `xml:"text"` } type Content struct { XMLName xml.Name `xml:"content"` Src string `xml:"src,attr"` } func main() { ncxContent := ` <ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1"> <navMap> <navPoint id="navPoint-1"> <navLabel><text>第一章</text></navLabel> <content src="chapter1.xhtml"/> <navPoint id="navPoint-1-1"> <navLabel><text>1.1 小节</text></navLabel> <content src="chapter1-1.xhtml"/> </navPoint> </navPoint> <navPoint id="navPoint-2"> <navLabel><text>第二章</text></navLabel> <content src="chapter2.xhtml"/> </navPoint> </navMap> </ncx> ` var ncx NCX err := xml.Unmarshal([]byte(ncxContent), &ncx) if err != nil { fmt.Printf("解析错误: %v\n", err) return } printNavPoints(ncx.NavMap.NavPoint, 0) } func printNavPoints(points []*NavPoint, indent int) { indentStr := strings.Repeat(" ", indent) for _, p := range points { fmt.Printf("%sID: %s, 标题: %s, 路径: %s\n", indentStr, p.ID, p.NavLabel.Text, p.Content.Src) if len(p.NavPoint) > 0 { printNavPoints(p.NavPoint, indent+1) } } }
方法2:自定义UnmarshalXML方法
如果指针方式不满足需求,可以手动实现NavPoint的UnmarshalXML方法,控制递归解析逻辑,完全掌控XML元素的处理流程。
示例代码:
package main import ( "encoding/xml" "fmt" "strings" ) type NCX struct { XMLName xml.Name `xml:"ncx"` NavMap NavMap `xml:"navMap"` } type NavMap struct { XMLName xml.Name `xml:"navMap"` NavPoint []NavPoint `xml:"navPoint"` } type NavPoint struct { ID string `xml:"id,attr"` NavLabel NavLabel `xml:"navLabel"` Content Content `xml:"content"` NavPoint []NavPoint `xml:"navPoint"` } type NavLabel struct { Text string `xml:"text"` } type Content struct { Src string `xml:"src,attr"` } // 自定义解析方法,手动处理嵌套结构 func (np *NavPoint) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { // 解析当前节点的id属性 for _, attr := range start.Attr { if attr.Name.Local == "id" { np.ID = attr.Value } } // 遍历子元素 for { token, err := d.Token() if err != nil { return err } switch elem := token.(type) { case xml.StartElement: switch elem.Name.Local { case "navLabel": var nl NavLabel if err := d.DecodeElement(&nl, &elem); err != nil { return err } np.NavLabel = nl case "content": var c Content if err := d.DecodeElement(&c, &elem); err != nil { return err } np.Content = c case "navPoint": var childNavPoint NavPoint if err := d.DecodeElement(&childNavPoint, &elem); err != nil { return err } np.NavPoint = append(np.NavPoint, childNavPoint) } case xml.EndElement: if elem == start.End() { return nil } } } } func main() { ncxContent := ` <ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1"> <navMap> <navPoint id="navPoint-1"> <navLabel><text>第一章</text></navLabel> <content src="chapter1.xhtml"/> <navPoint id="navPoint-1-1"> <navLabel><text>1.1 小节</text></navLabel> <content src="chapter1-1.xhtml"/> </navPoint> </navPoint> <navPoint id="navPoint-2"> <navLabel><text>第二章</text></navLabel> <content src="chapter2.xhtml"/> </navPoint> </navMap> </ncx> ` var ncx NCX err := xml.Unmarshal([]byte(ncxContent), &ncx) if err != nil { fmt.Printf("解析错误: %v\n", err) return } printNavPoints(ncx.NavMap.NavPoint, 0) } func printNavPoints(points []NavPoint, indent int) { indentStr := strings.Repeat(" ", indent) for _, p := range points { fmt.Printf("%sID: %s, 标题: %s, 路径: %s\n", indentStr, p.ID, p.NavLabel.Text, p.Content.Src) if len(p.NavPoint) > 0 { printNavPoints(p.NavPoint, indent+1) } } }
原代码栈溢出原因
原代码中NavPoint的子字段是值类型切片[]NavPoint,xml包反序列化时会不断创建新的NavPoint值实例,递归调用Unmarshal导致栈内存耗尽。指针类型或自定义解析方法则从根源上避免了这种无限递归分配的问题。
内容的提问来源于stack exchange,提问作者jbrown
相关产品推荐
相关产品推荐

