2016-05-31 130 views
2

我正在解码一些只包含字符串值和属性的XML。它还包含一些"&"的实例,这是不幸的,我想将其解码为"&"而不是"&"。我还将对这些字符串值做一些更多的工作,其中我需要字符"|"永不出现,所以我想用"%7C"替换任何"|"实例。在Golang中解码XML时的自定义字符串翻译

我能做的解码后用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,并尝试使用Entity贴图,但我无法取得任何进展。

编辑:

基础上的评论,我已经按照从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. 
+2

你真的想这样做http://stackoverflow.com/questions/21164455/multiple-types-在golang中提供了一个'UnmarshalXML'的实现,尽管我个人认为它比在事实之后调用类似'type.Sanatize()'的函数更好。我个人会采取后者,因为它没有混淆。我看到自定义的'Unmarshal'实现很像操作符重载,更多的混淆和工作,而不是他们的价值。 – evanmcdonnal

+0

@evanmcdonnal两个选项都相当不满意。我的意思是现有的解码器已经将“&”与其他标准xml转义一起更改为“&”,是否真的如此硬编码以至于我不能在此处进行标记?我没有试图像其他问题那样真正地破坏XML规则。 –

+0

我的意思就是实现'UnmarshalXML'的功能......你可以解码所有东西,运行字符串替换,然后调用常规的'Unmarshal',这不像你必须做任何艰苦的工作。我对xml的规范不是很熟悉,但afaik'|'没有特殊的名称,那么为什么你希望能够像转义字符那样对待它呢?是的,我希望特殊字符的列表能够被硬编码并且不被导出,为什么它不是? – evanmcdonnal

回答

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到处是适合你的,这应该做的伎俩。

编辑:简单的代码,而无需使用DecodeElement和类型开关...)