Идентификатор Golang UnmarshalXML и MarshalXML

#xml #go

#xml #Вперед

Вопрос:

Я разархивирую xml, но я не могу получить тот же результат при использовании MarshalXML (см. Пример ниже). Как вы можете видеть, xs:simpleType преобразуется в simpleType и другие отличия. Как я могу создать полную идентификацию? Мне нужно преобразовать произвольный xml в соответствии с определенными правилами, поэтому я не могу использовать определенные теги.

Вы можете просмотреть рабочий код здесь:https://play.golang.org/p/QK2AfPsR9ZN

 package main

import (
    "bytes"
    "encoding/xml"
    "fmt"
)

var data = []byte(`
<xs:simpleType name="x"></xs:simpleType>
`)

type Node struct {
    XMLName xml.Name
    Attrs   []xml.Attr `xml:",any,attr"`
    Content []byte     `xml:",innerxml"`
    Nodes   []Node     `xml:",any"`
}

func (n *Node) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
    n.Attrs = start.Attr
    type node Node
    return d.DecodeElement((*node)(n), amp;start)
}

func (n *Node) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
    n.Attrs = start.Attr
    type node Node
    return e.EncodeElement((*node)(n), start)
}

func main() {
    buf := bytes.NewBuffer(data)
    dec := xml.NewDecoder(buf)
    var n Node
    dec.Decode(amp;n)
    output, _ := xml.Marshal(n)
    fmt.Println(string(output))
}
  

Вывод:

 <simpleType xmlns="xs" name="x" name="x"></simpleType>
  

Комментарии:

1. Вам не нужна пользовательская логика маршалинга или отмены маршалинга, и вы можете принудительно использовать это поведение, если перед маршалингом вы объедините пространство имен и имя тега: Go Playground . Отвечает ли это на ваш вопрос?

2. @icza спасибо за совет. Но если я попытаюсь использовать этот XML play.golang.org/p/ECIeA35BOpW — входящий и распечатанный XML отличаются. Почему?

3. Потому что в моем примере не изменились имена дочерних узлов. Вы должны сделать это рекурсивно. Смотрите мой ответ ниже.

Ответ №1:

Вам не нужна пользовательская логика маршалинга или отмены маршалинга, и вы можете принудительно использовать это поведение, если перед маршалингом вы объедините пространство имен и имя тега:

 n.XMLName.Local = n.XMLName.Space   ":"   n.XMLName.Local
n.XMLName.Space = ""
  

Пример ввода соответствует выводу (попробуйте это на игровой площадке Go):

 <xs:simpleType name="x" other="y">Some content</xs:simpleType>
  

Конечно, если есть дочерние теги, вам придется рекурсивно обрабатывать имена всех узлов. Давайте напишем помощник для этого:

 func setName(n *Node) {
    n.XMLName.Local = n.XMLName.Space   ":"   n.XMLName.Local
    n.XMLName.Space = ""

    for i := range n.Nodes {
        setName(amp;n.Nodes[i])
    }
}
  

Пример ввода XML:

 <xs:simpleType name="comma-separated-integer">
  <xs:annotation>
    <xs:documentation>The comma-separated-integer element.</xs:documentation>
  </xs:annotation>
  <xs:restriction base="xs:token">
    <xs:pattern value="[1-9][0-9]*(, ?[1-9][0-9]*)*"/>
  </xs:restriction>
</xs:simpleType>
  

Использование помощника:

 buf := bytes.NewBuffer(data)
dec := xml.NewDecoder(buf)
var n Node
if err := dec.Decode(amp;n); err != nil {
    panic(err)
}

setName(amp;n)

output, err := xml.Marshal(n)
fmt.Println(string(output))
if err != nil {
    panic(err)
}
  

Тогда выходные данные почти совпадают с входными. Попробуйте это на игровой площадке Go.

Проблема в том, что внутреннее содержимое дублируется. Это потому, что Node.Content это внутреннее содержимое XML, и дочерние теги также добавляются в Node.Nodes качестве дочерних узлов. Таким образом, чтобы получить тот же результат, дочерние узлы не нужно маршалировать, Content они уже содержат дочерние узлы.

Удалите дублирование, очистив Nodes :

 n.XMLName.Local = n.XMLName.Space   ":"   n.XMLName.Local
n.XMLName.Space = ""
n.Nodes = nil
  

Попробуйте это на игровой площадке Go.

Примечание: если вы собираетесь манипулировать дочерними узлами XML, это неприемлемый вариант. Затем вы можете сохранить setName() вспомогательную функцию и, чтобы устранить дублирование, вы можете обнулить Content узлы, у которых есть дочерние узлы:

 func setName(n *Node) {
    n.XMLName.Local = n.XMLName.Space   ":"   n.XMLName.Local
    n.XMLName.Space = ""
    if len(n.Nodes) != 0 {
        n.Content = nil
    }

    for i := range n.Nodes {
        setName(amp;n.Nodes[i])
    }
}
  

Попробуйте это на игровой площадке Go.

Комментарии:

1. Я думаю, вам нужно добавить: if len(n.Nodes) != 0 {n.Content = nil} в функцию setName, потому что содержимое <xs:documentation> отсутствует. play.golang.org/p/_K7IvGakL9B

2. @nrw2000 Да, вы правы, работал над этим. Смотрите отредактированный ответ.

Ответ №2:

В нашей команде мы уже несколько месяцев используем фантастический модуль, который выражает XML, вдохновленный Python etrees.

https://github.com/beevik/etree

Это простой, но очень мощный инструмент для интерпретации XML-файлов и управления ими.