Go操作XML

前端之家收集整理的这篇文章主要介绍了Go操作XML前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

简介

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,表示注释。比如:

  1. <!-- comment -->
  2. <action application="answer">raw text</action>

上例中,<action application="answer">StartElement</action>EndElementraw textCharData<!-- -->Comment。进一步的,开始节点和结束节点均有名字,开始节点还可以拥有一个或多个属性。而注释和原始文本仅仅是字符串。在xml包中,对以上数据结构进行了封装,如下所示(这里仅列出重要的部分):

  1. // 名字
  2. type Name struct {
  3. Space string // 名称空间,例如 <space:action></space:action>
  4. Local string // 名称,例如<action></action>
  5. }
  6.  
  7. // 属性
  8. type Attr struct {
  9. Name Name
  10. Value string
  11. }
  12.  
  13. // 差别联合体类型,包含StartElement,EndElement,CharData,Comment等类型
  14. type Token interface{}
  15.  
  16. // 开始节点
  17. type StartElement struct {
  18. Name Name
  19. Attr []Attr
  20. }
  21. func (e StartElement) End() EndElement // 用来产生对应的结束节点
  22.  
  23. // 结束节点
  24. type EndElement struct {
  25. Name Name
  26. }
  27.  
  28. // raw text
  29. type CharData []byte
  30.  
  31. // 注释
  32. type Comment []byte

解码

解码器

xml包提供了一个解码器*xml.Decoder,用来以Token方式解码:

  1. type Decoder struct {
  2. // ...
  3. }
  4. func NewDecoder(r io.Reader) *Decoder // 用来创建 Decoder,参数为io.Reader
  5. func (d *Decoder) Token() (Token,error) // 返回下一个Token,解析结束返回io.EOF

例子

有了数据结构和解码器的定义,就可以编写实际例子了,这里以一个打印XML结构的例子来说明:

  1. package main
  2.  
  3. import (
  4. "bytes"
  5. "encoding/xml"
  6. "fmt"
  7. "io"
  8. "os"
  9. )
  10.  
  11. func main() {
  12. // 要解析的XML如下,为了提高可读性,用+号连接若干字符串,用以进行排版
  13. data :=
  14. `<extension name="rtp_multicast_page">` +
  15. `<condition field="destination_number" expression="^pagegroup$|^7243$">` +
  16. `<!-- comment -->` +
  17. `<action application="answer">raw text</action>` +
  18. `<action application="esf_page_group"/>` +
  19. `</condition>` +
  20. `</extension>`
  21.  
  22. // 创建一个reader,以满足io.Reader接口
  23. reader := bytes.NewReader([]byte(data))
  24. // 以io.Reader为参数,创建解码器
  25. dec := xml.NewDecoder(reader)
  26.  
  27. // 开始遍历解码
  28. indent := "" // 控制缩进
  29. sep := " " // 每层的缩进量为四个空格
  30. for {
  31. tok,err := dec.Token() // 返回下一个Token
  32. // 错误处理
  33. if err == io.EOF { // 如果读到结尾,则退出循环
  34. break
  35. } else if err != nil { // 其他错误退出程序
  36. os.Exit(1)
  37. }
  38. switch tok := tok.(type) { // Type switch
  39. case xml.StartElement: // 开始节点,打印名字和属性
  40. fmt.Print(indent)
  41. fmt.Printf("<%s ",tok.Name.Local)
  42. s := ""
  43. for _,v := range tok.Attr {
  44. fmt.Printf(`%s%s="%s"`,s,v.Name.Local,v.Value)
  45. s = " "
  46. }
  47. fmt.Println(">")
  48. indent += sep // 遇到开始节点,则增加缩进量
  49. case xml.EndElement: // 结束节点,打印名字
  50. indent = indent[:len(indent)-len(sep)] // 遇到结束节点,则减少缩进量
  51. fmt.Printf("%s</%s>\n",indent,tok.Name.Local)
  52. case xml.CharData: // 原始字符串,直接打印
  53. fmt.Printf("%s%s\n",tok)
  54. case xml.Comment: // 注释,直接打印
  55. fmt.Printf("%s<!-- %s -->\n",tok)
  56. }
  57. }
  58. }

该例用一个无限for循环,不断的获取Token,然后用Type Switch判断类型,根据不同的类型进行处理。最后的输出如下:

  1. <extension name="rtp_multicast_page">
  2. <condition field="destination_number" expression="^pagegroup$|^7243$">
  3. <!-- comment -->
  4. <action application="answer">
  5. raw text
  6. </action>
  7. <action application="esf_page_group">
  8. </action>
  9. </condition>
  10. </extension>

编码

编码器

xml包提供了编码器,用以编码:

  1. // 编码器
  2. type Encoder struct {
  3. // 没有导出任何字段
  4. }
  5. func NewEncoder(w io.Writer) *Encoder // 创建编码器,参数为io.Writer
  6. func (enc *Encoder) EncodeToken(t Token) error // 编码Token
  7. func (enc *Encoder) Flush() error // 刷新缓冲区,将已经编码内容写入io.Writer
  8. func (enc *Encoder) Indent(prefix,indent string) // 用作缩进

例子

有了编码器的定义,就可以编写实际代码了,假设我们要生成以下XML:

  1. <extension name="rtp_multicast_page">
  2. <condition field="destination_number" expression="^pagegroup$|^7243$">
  3. <action application="answer">raw text</action>
  4. <action application="esf_page_group"></action>
  5. </condition>
  6. </extension>

代码如下:

  1. package main
  2.  
  3. import (
  4. "bytes"
  5. "encoding/xml"
  6. "fmt"
  7. )
  8.  
  9. // 为了少敲几个字符,声明了attrmap类型和start函数
  10. type attrmap map[string]string // 属性的键值对容器
  11.  
  12. // start()用来构建开始节点
  13. func start(tag string,attrs attrmap) xml.StartElement {
  14. var a []xml.Attr
  15. for k,v := range attrs {
  16. a = append(a,xml.Attr{xml.Name{"",k},v})
  17. }
  18. return xml.StartElement{xml.Name{"",tag},a}
  19. }
  20.  
  21. func main() {
  22. // 创建编码器
  23. buffer := new(bytes.Buffer)
  24. enc := xml.NewEncoder(buffer)
  25.  
  26. // 设置缩进,这里为4个空格
  27. enc.Indent(""," ")
  28.  
  29. // 开始生成XML
  30. startExtension := start("extension",attrmap{"name": "rtp_multicast_page"})
  31. enc.EncodeToken(startExtension)
  32. startCondition := start("condition",attrmap{"field": "destination_number","expression": "^pagegroup$|^7243$"})
  33. enc.EncodeToken(startCondition)
  34. startAction := start("action",attrmap{"application": "answer"})
  35. enc.EncodeToken(startAction)
  36. enc.EncodeToken(xml.CharData("raw text"))
  37. enc.EncodeToken(startAction.End())
  38. startAction = start("action",attrmap{"application": "esf_page_group"})
  39. enc.EncodeToken(startAction)
  40. enc.EncodeToken(startAction.End())
  41. enc.EncodeToken(startCondition.End())
  42. enc.EncodeToken(startExtension.End())
  43.  
  44. // 写入XML
  45. enc.Flush()
  46.  
  47. // 打印结果
  48. fmt.Println(buffer)
  49. }

注意上例中我们调用func (e StartElement) End() EndElement用来以开始节点创建相应的结束节点。最后打印结果如下:

  1. <extension name="rtp_multicast_page">
  2. <condition field="destination_number" expression="^pagegroup$|^7243$">
  3. <action application="answer">raw text</action>
  4. <action application="esf_page_group"></action>
  5. </condition>
  6. </extension>

高阶方法(Marshal和Unmarshal)

转换规则

  • 因为xml包是以@L_404_1@机制实现的转换,因此自定义的结构体必须导出所要转换的字段。
  • 通常情况下都是结构体类型和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:

  1. // 接收一个interface{},遍历其结构,编码为XML
  2. func Marshal(v interface{}) ([]byte,error)
  3.  
  4. // 和Marshal类似,只不过在编码时加了缩进,用于方便阅读
  5. func MarshalIndent(v interface{},prefix,indent string) ([]byte,error)

例子

假设要生成的XML如下:

  1. <extension name="rtp_multicast_page">
  2. <condition field="destination_number" expression="^pagegroup$|^7243$">
  3. <action application="answer">raw text</action>
  4. <action application="esf_page_group"></action>
  5. </condition>
  6. </extension>

Go代码

  1. package main
  2.  
  3. import (
  4. "encoding/xml"
  5. "fmt"
  6. )
  7.  
  8. type Action struct {
  9. XMLName string `xml:"action"`
  10. Application string `xml:"application,attr"`
  11. Data string `xml:",chardata"`
  12. }
  13.  
  14. type Condition struct {
  15. XMLName string `xml:"condition"`
  16. Field string `xml:"field,attr"`
  17. Expression string `xml:"expression,attr"`
  18. Actions []Action
  19. }
  20.  
  21. type Extension struct {
  22. XMLName string `xml:"extension"`
  23. Name string `xml:"name,attr"`
  24. Cond Condition
  25. }
  26.  
  27. func main() {
  28. var actions []Action
  29. actions = append(actions,Action{"","answer","raw text"})
  30. actions = append(actions,"esf_page_group",""})
  31. condition := Condition{"","destination_number","^pagegroup$|^7243$",actions}
  32. extension := Extension{"","rtp_multicast_page",condition}
  33.  
  34. data,_ := xml.MarshalIndent(extension,""," ")
  35. fmt.Printf("%s\n",data)
  36. }

输出为:

  1. <extension name="rtp_multicast_page">
  2. <condition field="destination_number" expression="^pagegroup$|^7243$">
  3. <action application="answer">raw text</action>
  4. <action application="esf_page_group"></action>
  5. </condition>
  6. </extension>

解码

Unmarshal

xml包提供了Unmarshal方法用于解码XML:

  1. // 将data解码为v,v通常是结构体
  2. func Unmarshal(data []byte,v interface{}) error

例子

Unmarshal和Marshal互为相反操作,结构体不需要修改,只需要将上例的输出改为输入就可以了。

  1. package main
  2.  
  3. import (
  4. "encoding/xml"
  5. "fmt"
  6. )
  7.  
  8. type Action struct {
  9. XMLName string
  10. Application string `xml:"application,attr"`
  11. Data string `xml:",chardata"`
  12. }
  13.  
  14. type Condition struct {
  15. XMLName string `xml:"condition"`
  16. Field string `xml:"field,attr"`
  17. Actions []Action
  18. }
  19.  
  20. type Extension struct {
  21. XMLName string `xml:"extension"`
  22. Name string `xml:"name,attr"`
  23. Cond Condition `xml:"condition"`
  24. }
  25.  
  26. func main() {
  27. data :=
  28. `<extension name="rtp_multicast_page">` +
  29. `<condition field="destination_number" expression="^pagegroup$|^7243$">` +
  30. `<Actions application="answer">raw text</Actions>` +
  31. `<Actions application="esf_page_group"></Actions>` +
  32. `</condition>` +
  33. `</extension>`
  34.  
  35. var ext Extension
  36. xml.Unmarshal([]byte(data),&ext)
  37. fmt.Println(ext)
  38. }

结果为:

  1. { rtp_multicast_page { destination_number ^pagegroup$|^7243$ [{ answer raw text} { esf_page_group }]}}

这正是上一例中的输入。

结语

高阶方法和低阶方法各有各的适用场合。高阶方法适用于需要编码和解码整个XML并且需要以结构化的数据操纵XML的时候。另外高阶方法必须导出结构体,会破坏封装,这很可能是我们不想要的。低阶方法通常用在解析XML中的若干节点时使用。 本人初学Go语言,文中可能出现谬误,欢迎大家指正。

猜你在找的XML相关文章