tinydom

go语言的微型xml dom解析器

This project is maintained by tinyhubs

Overview

Build Status GoDoc Language License codecov goreport

tinydom是一个非验证的,轻量级的,经过充分测试的go语言(golang)xml流的dom构造器。

tinydom简介

tidydom使用golang的encoding/xml标准库作为底层XML文本流的解析器。使用tinydom提供的接口可以实现简单的XML文件的读取和生成。 tinydom借鉴了tinyxml2的接口设计技巧,提供了丰富的XML元素的查找手段。

如何使用

接口定义

一个XML文档由XMLDocumentXMLElementXMLTextXMLCommentXMLProcInstXMLDirective这几种类型的节点组成。

加载文档

tinydom.LoadDocument用于从一个文件流或者字符流读取XML数据,并构建出tinydom.XMLDocument对象,一般用于读取XML文件的场景。

import "tinydom"
doc, err := tinydom.LoadDocument(strings.NewReader(s))

FirstChildElementLastChildElementPrevElementNextElement这几个函数,主要是为了方便查找XMLElement元素, 大部分情况下我们建立XML文档的DOM模型就是为了对XMLElement进行访问。

xmlstr := `
<books>
    <book><name>The Moon</name><author>Tom</author></book>
    <book><name>Go west</name><author>Suny</author></book>
<books>
`
doc, _ := tinydom.LoadDocument(strings.NewReader(xmlstr))
elem1 := doc.FirstChildElement("books").FirstChildElement("book").FirstChildElement("name")
fmt.Println(elem1.Text()) //	The Moon
elem2 := doc.FirstChildElement("books").FirstChildElement("book").LastChildElement("author")
fmt.Println(elem2.Text()) //	Suny

查找节点

FirstChildLastChild这两个函数用于获取某个节点的子节点,不管节点是什么类型都可以用这两个函数获取到.

FirstChildElementLastChildElement这两个函数专用于查找指定的Element节点,如果指定了name参数,那么只查找指定名字的子Element节点.

PrevNext这两个函数用于查找当前节点的前一个或者后一个兄弟节点.

PrevElementNextElement这两个函数用于查找当前节点的上一个或者下一个Element节点.我们同样可以通过指定这两个函数的name参数来查找执行名字的Element节点.

Parent函数直接获取当前节点的父节点.

我们提供了一堆的转换函数:

ToElement() XMLElement

ToText() XMLText

ToComment() XMLComment

ToDocument() XMLDocument

ToProcInst() XMLProcInst

ToDirective() XMLDirective

XMLElement接口提供了多个获取属性的函数:

查找: FindAttribute(name string) XMLAttribute

遍历: ForeachAttribute(callback func(attribute XMLAttribute) int) int

属性个数统计: AttributeCount() int

直接获取属性字符串: Attribute(name string, def string) string

文档的遍历

ParentFirstChildLastChildPrevNext用于使我们可以方便地在XML的DOM树中游走。 下面这个函数可以用于对一个doc进行遍历:

func walk(m int , rootNode tinydom.XMLNode) {
    if nil == rootNode {
        return
    }
    for child := rootNode.FirstChild(); nil != child; child = child.Next() {
        fmt.Println(strings.Repeat(" ", m), child.Value())
        walk(m + 1, child)
    }
}

您可以这样调用:

walk(0, doc)

还有一个更好的替代方式是使用tinydom.XMLVisitor接口对文档中的元素进行遍历,可参见代码中tinydom.XMLVisitor的接口定义。

新建文档

tinydom.NewDocument用于在内存中生成DOM,一般用于生成XML文件。

tinydom提供了一系列的NewXXX方法用于创建各种不同类型的节点:

tinydom.NewText(document XMLDocument, text string) XMLText

tinydom.NewComment(document XMLDocument, comment string) XMLComment

tinydom.NewElement(document XMLDocument, name string) XMLElement

tinydom.NewProcInst(document XMLDocument, target string, inst string) XMLProcInst

tinydom.NewDirective(document XMLDocument, directive string) XMLDirective

而下面这些函数用于将任意类型的节点加入当前节点,或者对节点进行删除操作:

我们也可以对节点的属性进行操作:

下面的代码创建了一个XML文档:

doc := tinydom.NewDocument()
books := doc.InsertEndChild(tinydom.NewElement(doc, "books"))
book := books.InsertEndChild(tinydom.NewElement(doc, "book"))
name := book.InsertEndChild(tinydom.NewElement(doc, "name"))
name.InsertEndChild(tinydom.NewText(doc, "The Moon"))
doc.InsertEndChild(tinydom.NewProcInst(doc, "xml", `version="1.0" encoding="UTF-8"`))

我们可以使用tinydom.XMLDocumentAccept方法来将这个XML文档输出:

doc.Accept(tinydom.NewSimplePrinter(os.Stdout, tinydom.PrettyPrint))

输出

tinydom采用了访问者模式(参见tinydom.XMLVisitor接口)来对文档的所有节点进行遍历,tinydom.XMLVisitortinydom.XMLDocumentAccept方法结合基本可以输出满足我们大多数场景的XML文档输出任务.我们完全可以使用该机制自己定制文档输出格式.

不过,为了方便大多数使用场景,tinydom仍然提供了一个专用于打印的visitor.下面这行代码用于直接向屏幕打印XML文档:

doc.Accept(tinydom.NewSimplePrinter(os.Stdout, tinydom.PrettyPrint))

tinydom.NewSimplePrinter的接口如下:

func NewSimplePrinter(writer io.Writer, options PrintOptions) XMLVisitor

tinydom.NewSimplePrinter的第二个参数用于控制输出格式:

type PrintOptions struct {
    Indent        []byte //  缩进前缀,只允许填写tab或者空白,如果Indent长度为0表示折行但是不缩进,如果Indent为null表示不折行
    TextWrapWidth int    //  超过多长才强制换行
}

为简化编码tinydom也提供了两种缺省的PrintOptions:

对于自定义XML文档输出模式而言,处理XML字符转义是个麻烦,因为你必须处理一些细节.但tinydom也可在这方面帮助你.tinydom提供了 tinydom.EscapeAttributetinydom.EscapeText来方便处理属性和XMLText中的转义字符.您也可以使用golang自带 的xml.EscapeText,只是这个函数做了更多的转义,会导致文档更难阅读和编辑.

XML字符转义

受益于go的xml库,tinydom也支持XML字符转义,使用tinydom在读写xml的数据的时候不需要关注XML转义字符,tinydom自动会处理好,可参考下面的例子:

xmlstr :=
    `<talks>
        <talk from="bill" to="tom">[&amp;&apos;&quot;&gt;&lt;] are the xml escape chars? </talk>
        <talk from="tom" to="bill">yes, that is right</talk>
     </talks>
    `
doc, _ := tinydom.LoadDocument(strings.NewReader(xmlstr))
talk := doc.FirstChildElement("talks").FirstChildElement("talk").Text()
fmt.Print(talk) //  [&'"><] are the xml escape chars?

xml文档输出时,可使用tinydom.EscapeAttributetinydom.EscapeText来对字符进行转义.

CDATA

只有XMLText对象才涉及到CDATA,可以通过XMLText获取到CDATA对象的数据,tinydom能够自动识别CDATA,但是将DOM对象序列化成字符串时,除非节点指定了CDATA属性,否则会直接转义。

xmlstr := `<content><![CDATA[<example>This is ok in cdata text</example>]]></content>`
doc, _ := tinydom.LoadDocument(strings.NewReader(xmlstr))
content := doc.FirstChildElement("content")
fmt.Println("\nRead CDATA:", content.Text())
fmt.Println("\nNormal Print:")
doc.Accept(tinydom.NewSimplePrinter(os.Stdout))
text := content.FirstChild().ToText()
text.SetCDATA(true)
fmt.Println("\nSpecial as CDATA:")
doc.Accept(tinydom.NewSimplePrinter(os.Stdout))

名字空间

不支持: 虽然golang标准库是能够正常处理名字空间的,但当前tinydom还无法正确处理xml的名字空间,所有带有名字空间前缀的节点或者属性都会被丢弃。后续计划将这块功能补齐。

BOM

golang的xml解析器自身还不支持BOM,所以本解析器还无法解析带BOM头的xml文件。

Changelog

1.0.0 初始版本

1.1.0 小版本改进,能力增强,bug解决

1.2.0 小版本改进,能力增强,bug解决