【问题标题】:Custom string translation while decoding XML in Golang在 Golang 中解码 XML 时自定义字符串翻译
【发布时间】:2016-05-31 18:05:57
【问题描述】:

我正在解码一些仅包含字符串值和属性的 XML。它还包含"&" 的一些实例,这很不幸,我想将其解码为"&" 而不是"&"。我还将对这些字符串值做更多的工作,其中我需要字符 "|" 永远不会出现,因此我想用 "%7C" 替换任何 "|" 实例。

我可以在解码后使用strings.Replace 进行这些更改,但由于解码已经在做类似的工作(毕竟它确实将"&" 转换为"&")我想同时进行.

我要解析的文件很大,所以我会做类似于http://blog.davidsingleton.org/parsing-huge-xml-files-with-go/的事情

这是一个简短的 xml 文件示例:

<?xml version="1.0" encoding="utf-8"?>
<tests>
    <test_content>X&amp;amp;Y is a dumb way to write XnY | also here's a pipe.</test_content>
    <test_attr>
      <test name="Normal" value="still normal" />
      <test name="X&amp;amp;Y" value="should be the same as X&amp;Y | XnY would have been easier." />
    </test_attr>
</tests>

还有一些执行标准解码并打印结果的 Go 代码:

package main

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

type XMLTests struct {
    Content string     `xml:"test_content"`
    Tests   []*XMLTest `xml:"test_attr>test"`
}

type XMLTest struct {
    Name  string `xml:"name,attr"`
    Value string `xml:"value,attr"`
}

func main() {
    xmlFile, err := os.Open("test.xml")
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }
    defer xmlFile.Close()

    var q XMLTests

    decoder := xml.NewDecoder(xmlFile)

    // I tried this to no avail:
    // decoder.Entity = make(map[string]string)
    // decoder.Entity["|"] = "%7C"
    // decoder.Entity["&amp;amp;"] = "&"

    var inElement string
    for {
        t, _ := decoder.Token()
        if t == nil {
            break
        }
        switch se := t.(type) {
        case xml.StartElement:
            inElement = se.Name.Local
            if inElement == "tests" {
                decoder.DecodeElement(&q, &se)
            }
        default:
        }
    }

    fmt.Println(q.Content)
    for _, t := range q.Tests {
        fmt.Printf("\t%s\t\t%s\n", t.Name, t.Value)
    }
}

如何修改此代码以获得我想要的?即:如何自定义解码器?

我查看了文档,特别是 https://golang.org/pkg/encoding/xml/#Decoder 并尝试使用实体地图,但我无法取得任何进展。

编辑:

基于 cmets,我按照 Multiple-types decoder in golang 中的示例对上述代码添加/更改了以下内容:

type string2 string

type XMLTests struct {
    Content string2    `xml:"test_content"`
    Tests   []*XMLTest `xml:"test_attr>test"`
}

type XMLTest struct {
    Name  string2 `xml:"name,attr"`
    Value string2 `xml:"value,attr"`
}

func (s *string2) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
    var content string
    if err := d.DecodeElement(&content, &start); err != nil {
        return err
    }
    content = strings.Replace(content, "|", "%7C", -1)
    content = strings.Replace(content, "&amp;", "&", -1)
    *s = string2(content)
    return nil
}

这适用于test_content,但不适用于属性?

X&Y is a dumb way to write XnY %7C also here's a pipe.
    Normal      still normal
    X&amp;Y     should be the same as X&Y | XnY would have been easier.

【问题讨论】:

  • 您实际上想要做类似stackoverflow.com/questions/21164455/… 的事情,您可以在其中提供UnmarshalXML 的实现,尽管我个人认为这并不比事后调用像type.Sanatize() 这样的函数更好.我个人会采用后者,因为它不太容易混淆。我看到自定义 Unmarshal 实现很像运算符重载,更多的混乱和工作超出了他们的价值。
  • @evanmcdonnal 这两个选项都很不满意。我的意思是现有的解码器已经在改变“&”到“&”以及其他标准 xml 转义,是否真的如此硬编码以至于我不能在那里标记?我并不想像其他问题那样真正打破 XML 规则。
  • 我的意思是这就是实现UnmarshalXML 所做的......你可以解码所有内容,运行字符串替换,然后调用常规Unmarshal,这不像你必须做任何艰苦的工作.我不太了解 xml 的规范,但 afaik | 没有特殊名称,那么您为什么希望能够将其视为转义字符?是的,我希望特殊字符列表是硬编码和未导出的,为什么不呢?
  • @evanmcdonnal 你说得很对。不幸的是,无论哪种方式,我都不会免费获得任何东西。谢谢。
  • @evanmcdonnal 请看我的编辑。使用另一个问题的示例,我似乎只有 50% 的路。

标签: xml go xml-parsing


【解决方案1】:

要处理属性,可以使用UnmarshalerAttr 接口和UnmarshalXMLAttr 方法。你的例子就变成了:

package main

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

type string2 string

type XMLTests struct {
    Content string2    `xml:"test_content"`
    Tests   []*XMLTest `xml:"test_attr>test"`
}

type XMLTest struct {
    Name  string2 `xml:"name,attr"`
    Value string2 `xml:"value,attr"`
}

func decode(s string) string2 {
    s = strings.Replace(s, "|", "%7C", -1)
    s = strings.Replace(s, "&amp;", "&", -1)
    return string2(s)
}

func (s *string2) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
    var content string
    if err := d.DecodeElement(&content, &start); err != nil {
        return err
    }
    *s = decode(content)
    return nil
}

func (s *string2) UnmarshalXMLAttr(attr xml.Attr) error {
    *s = decode(attr.Value)
    return nil
}

func main() {
    xmlData := `<?xml version="1.0" encoding="utf-8"?>
<tests>
    <test_content>X&amp;amp;Y is a dumb way to write XnY | also here's a pipe.</test_content>
    <test_attr>
      <test name="Normal" value="still normal" />
      <test name="X&amp;amp;Y" value="should be the same as X&amp;Y | XnY would have been easier." />
    </test_attr>
</tests>`
    xmlFile := strings.NewReader(xmlData)

    var q XMLTests

    decoder := xml.NewDecoder(xmlFile)
    decoder.Decode(&q)

    fmt.Println(q.Content)
    for _, t := range q.Tests {
        fmt.Printf("\t%s\t\t%s\n", t.Name, t.Value)
    }
}

输出:

X&Y is a dumb way to write XnY %7C also here's a pipe.
    Normal      still normal
    X&Y     should be the same as X&Y %7C XnY would have been easier.

(您可以在Go playground 中进行测试。)

因此,如果在任何地方使用 string2 都适合您,这应该可以解决问题。

edit:更简单的代码,不使用DecodeElement 和类型开关...)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-12
    • 1970-01-01
    • 1970-01-01
    • 2015-07-24
    • 1970-01-01
    • 2022-10-13
    相关资源
    最近更新 更多