Go操作XML
来源:互联网 发布:书生商友软件 编辑:程序博客网 时间:2024/06/07 20:57
简介
Go的标准库encoding/xml提供了对XML的操作。xml包提供了两种方式来操作XML,一种是高阶的方式,一种是低阶的方式。高阶的方式提供了Marshal和Unmarshal两个函数分别来编码(将Go数据结构转换成XML)和解码(将XML转换成Go数据结构)。低阶的方法则基于token来进行编码和解码。由于低阶的方法更常使用,因此先介绍低阶的方法。
低阶方法(Token)
Token和XML数据结构
低阶方法是以Token为单位操纵XML,Token有四种类型:StartElement
,用来表示XML开始节点;EndElement
,用来表示XML结束节点;CharData
,即为XML的原始文本(raw text);Comment
,表示注释。比如:
<!-- comment --><action application="answer">raw text</action>
上例中,<action application="answer">
为 StartElement
, </action>
为 EndElement
,raw text
为 CharData
,<!-- -->
为 Comment
。进一步的,开始节点和结束节点均有名字,开始节点还可以拥有一个或多个属性。而注释和原始文本仅仅是字符串。在xml包中,对以上数据结构进行了封装,如下所示(这里仅列出重要的部分):
// 名字type Name struct { Space string // 名称空间,例如 <space:action></space:action> Local string // 名称,例如<action></action>}// 属性type Attr struct { Name Name Value string}// 差别联合体类型,包含StartElement,EndElement,CharData,Comment等类型type Token interface{}// 开始节点type StartElement struct { Name Name Attr []Attr}func (e StartElement) End() EndElement // 用来产生对应的结束节点// 结束节点type EndElement struct { Name Name}// raw texttype CharData []byte// 注释type Comment []byte
解码
解码器
xml包提供了一个解码器*xml.Decoder
,用来以Token方式解码:
type Decoder struct { // ...}func NewDecoder(r io.Reader) *Decoder // 用来创建 Decoder,参数为io.Readerfunc (d *Decoder) Token() (Token, error) // 返回下一个Token,解析结束返回io.EOF
例子
有了数据结构和解码器的定义,就可以编写实际例子了,这里以一个打印XML结构的例子来说明:
package mainimport ( "bytes" "encoding/xml" "fmt" "io" "os")func main() { // 要解析的XML如下,为了提高可读性,用+号连接若干字符串,用以进行排版 data := `<extension name="rtp_multicast_page">` + `<condition field="destination_number" expression="^pagegroup$|^7243$">` + `<!-- comment -->` + `<action application="answer">raw text</action>` + `<action application="esf_page_group"/>` + `</condition>` + `</extension>` // 创建一个reader,以满足io.Reader接口 reader := bytes.NewReader([]byte(data)) // 以io.Reader为参数,创建解码器 dec := xml.NewDecoder(reader) // 开始遍历解码 indent := "" // 控制缩进 sep := " " // 每层的缩进量为四个空格 for { tok, err := dec.Token() // 返回下一个Token // 错误处理 if err == io.EOF { // 如果读到结尾,则退出循环 break } else if err != nil { // 其他错误则退出程序 os.Exit(1) } switch tok := tok.(type) { // Type switch case xml.StartElement: // 开始节点,打印名字和属性 fmt.Print(indent) fmt.Printf("<%s ", tok.Name.Local) s := "" for _, v := range tok.Attr { fmt.Printf(`%s%s="%s"`, s, v.Name.Local, v.Value) s = " " } fmt.Println(">") indent += sep // 遇到开始节点,则增加缩进量 case xml.EndElement: // 结束节点,打印名字 indent = indent[:len(indent)-len(sep)] // 遇到结束节点,则减少缩进量 fmt.Printf("%s</%s>\n", indent, tok.Name.Local) case xml.CharData: // 原始字符串,直接打印 fmt.Printf("%s%s\n", indent, tok) case xml.Comment: // 注释,直接打印 fmt.Printf("%s<!-- %s -->\n", indent, tok) } }}
该例用一个无限for循环,不断的获取Token,然后用Type Switch判断类型,根据不同的类型进行处理。最后的输出如下:
<extension name="rtp_multicast_page"> <condition field="destination_number" expression="^pagegroup$|^7243$"> <!-- comment --> <action application="answer"> raw text </action> <action application="esf_page_group"> </action> </condition></extension>
编码
编码器
xml包提供了编码器,用以编码:
// 编码器type Encoder struct { // 没有导出任何字段}func NewEncoder(w io.Writer) *Encoder // 创建编码器,参数为io.Writerfunc (enc *Encoder) EncodeToken(t Token) error // 编码Tokenfunc (enc *Encoder) Flush() error // 刷新缓冲区,将已经编码内容写入io.Writerfunc (enc *Encoder) Indent(prefix, indent string) // 用作缩进
例子
有了编码器的定义,就可以编写实际代码了,假设我们要生成以下XML:
<extension name="rtp_multicast_page"> <condition field="destination_number" expression="^pagegroup$|^7243$"> <action application="answer">raw text</action> <action application="esf_page_group"></action> </condition></extension>
代码如下:
package mainimport ( "bytes" "encoding/xml" "fmt")// 为了少敲几个字符,声明了attrmap类型和start函数type attrmap map[string]string // 属性的键值对容器// start()用来构建开始节点func start(tag string, attrs attrmap) xml.StartElement { var a []xml.Attr for k, v := range attrs { a = append(a, xml.Attr{xml.Name{"", k}, v}) } return xml.StartElement{xml.Name{"", tag}, a}}func main() { // 创建编码器 buffer := new(bytes.Buffer) enc := xml.NewEncoder(buffer) // 设置缩进,这里为4个空格 enc.Indent("", " ") // 开始生成XML startExtension := start("extension", attrmap{"name": "rtp_multicast_page"}) enc.EncodeToken(startExtension) startCondition := start("condition", attrmap{"field": "destination_number", "expression": "^pagegroup$|^7243$"}) enc.EncodeToken(startCondition) startAction := start("action", attrmap{"application": "answer"}) enc.EncodeToken(startAction) enc.EncodeToken(xml.CharData("raw text")) enc.EncodeToken(startAction.End()) startAction = start("action", attrmap{"application": "esf_page_group"}) enc.EncodeToken(startAction) enc.EncodeToken(startAction.End()) enc.EncodeToken(startCondition.End()) enc.EncodeToken(startExtension.End()) // 写入XML enc.Flush() // 打印结果 fmt.Println(buffer)}
注意上例中我们调用了func (e StartElement) End() EndElement
用来以开始节点创建相应的结束节点。最后打印结果如下:
<extension name="rtp_multicast_page"> <condition field="destination_number" expression="^pagegroup$|^7243$"> <action application="answer">raw text</action> <action application="esf_page_group"></action> </condition></extension>
高阶方法(Marshal和Unmarshal)
转换规则
- 因为xml包是以反射机制实现的转换,因此自定义的结构体必须导出所要转换的字段。
- 通常情况下都是结构体类型和XML数据之间互相转换。xml包定义了结构体和XML数据的转换规则。xml包根据字段的命名,字段的标签来映射XML元素,规则大致如下(仅列出重要部分,详细信息请参见go文档):
- 形如
xml:"value,value,..."
的结构体标签为xml包所解析,第一个value对应XML中的名字(节点名、属性名)。 - 字段与XML节点名对应关系:
- 如果存在名为XMLName的字段,并且标签中存在名字值,则该名字值为节点名称,否则
- 如果存在名为XMLName的字段,并且类型为xml.Name,则该字段的值为节点名称,否则
- 结构体名称。
- 字段标签的解析:
"-"
忽略该字段"name,attr"
字段映射为XML属性,name为属性名",attr"
字段映射为XML属性,字段名为属性名",chardata"
字段映射为原始字符串"omitempty"
若包含此标签则在字段值为0值时忽略此字段
- 视匿名字段的字段为结构体的字段
- 形如
编码
Marshal
xml包提供了Marshal方法用于编码XML:
// 接收一个interface{},遍历其结构,编码为XMLfunc Marshal(v interface{}) ([]byte, error)// 和Marshal类似,只不过在编码时加了缩进,用于方便阅读func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
例子
假设要生成的XML如下:
<extension name="rtp_multicast_page"> <condition field="destination_number" expression="^pagegroup$|^7243$"> <action application="answer">raw text</action> <action application="esf_page_group"></action> </condition></extension>
Go代码:
package mainimport ( "encoding/xml" "fmt")type Action struct { XMLName string `xml:"action"` Application string `xml:"application,attr"` Data string `xml:",chardata"`}type Condition struct { XMLName string `xml:"condition"` Field string `xml:"field,attr"` Expression string `xml:"expression,attr"` Actions []Action}type Extension struct { XMLName string `xml:"extension"` Name string `xml:"name,attr"` Cond Condition}func main() { var actions []Action actions = append(actions, Action{"", "answer", "raw text"}) actions = append(actions, Action{"", "esf_page_group", ""}) condition := Condition{"", "destination_number", "^pagegroup$|^7243$", actions} extension := Extension{"", "rtp_multicast_page", condition} data, _ := xml.MarshalIndent(extension, "", " ") fmt.Printf("%s\n", data)}
输出为:
<extension name="rtp_multicast_page"> <condition field="destination_number" expression="^pagegroup$|^7243$"> <action application="answer">raw text</action> <action application="esf_page_group"></action> </condition></extension>
解码
Unmarshal
xml包提供了Unmarshal方法用于解码XML:
// 将data解码为v,v通常是结构体func Unmarshal(data []byte, v interface{}) error
例子
Unmarshal和Marshal互为相反操作,结构体不需要修改,只需要将上例的输出改为输入就可以了。
package mainimport ( "encoding/xml" "fmt")type Action struct { XMLName string Application string `xml:"application,attr"` Data string `xml:",chardata"`}type Condition struct { XMLName string `xml:"condition"` Field string `xml:"field,attr"` Expression string `xml:"expression,attr"` Actions []Action}type Extension struct { XMLName string `xml:"extension"` Name string `xml:"name,attr"` Cond Condition `xml:"condition"`}func main() { data := `<extension name="rtp_multicast_page">` + `<condition field="destination_number" expression="^pagegroup$|^7243$">` + `<Actions application="answer">raw text</Actions>` + `<Actions application="esf_page_group"></Actions>` + `</condition>` + `</extension>` var ext Extension xml.Unmarshal([]byte(data), &ext) fmt.Println(ext)}
结果为:
{ rtp_multicast_page { destination_number ^pagegroup$|^7243$ [{ answer raw text} { esf_page_group }]}}
这正是上一例中的输入。
结语
高阶方法和低阶方法各有各的适用场合。高阶方法适用于需要编码和解码整个XML并且需要以结构化的数据操纵XML的时候。另外高阶方法必须导出结构体,会破坏封装,这很可能是我们不想要的。低阶方法通常用在解析XML中的若干节点时使用。
本人初学Go语言,文中可能出现谬误,欢迎大家指正。
- Go操作XML
- go解析XML
- Go 解析XML
- go语言解析xml
- go xml 解析
- 【go】++/--不是操作符
- Go实践-文件系统操作
- go impot _ 操作
- go 语言文件操作
- Go语言 操作二进制文件
- Go 操作CSV格式文件
- Go语言操作json
- Go语言操作Redis
- go语言文件操作
- Go语言Slice操作.
- go的读写操作
- Go文件操作
- go文件操作
- Truck History
- Myeclipse错误:Errors occurred during the build. Errors running builder 'DeploymentBuilder' on project
- HDOJ-魔咒词典
- 三层架构
- 294. Flip Game II
- Go操作XML
- 图像处理之_导数微分
- 自定义View:侧滑菜单动画实现
- 图像处理之_霍夫(Hough)变换
- [leetCode刷题笔记]300. Longest Increasing Subsequence
- VNC安装与配置
- Leetcode 424. Longest Repeating Character Replacement
- C++primer学习笔记-----4.11类型转换
- mybatis整合ss的时候,无法autowire使用mapper的自动注入,找不到bean:NoSuchBeanDefinitionException