如何使用Go创建XML的CDATA节点?

16

我有以下的结构体:

type XMLProduct struct {
    XMLName          xml.Name `xml:"row"`
    ProductId        string   `xml:"product_id"`
    ProductName      string   `xml:"product_name"`
    OriginalPrice    string   `xml:"original_price"`
    BargainPrice     string   `xml:"bargain_price"`
    TotalReviewCount int      `xml:"total_review_count"`
    AverageScore     float64  `xml:"average_score"`
}

我使用encoding/xml对此进行编码,然后在网页上显示。

ProductName 字段需要用 <![CDATA[]]包装起来。但如果我写成 <![CDATA[ + p.ProductName + ]]>,则会将 <> 翻译为 &lt;&gt;

如何以最小的代价创建 CDATA


为什么需要使用CDATA?CDATA部分是一种方便的工具,可以与XML编码值互换,而文档内容并不会有所改变。 - Tomalak
3
@Tomalak 这是公司的规格说明... - Derrick Zhang
encoding/xml/marshal.go 的源代码并未表明支持输出 CDATA。(再次说明,CDATA 在技术上是不必要的。也许规范毕竟可以被修改?) - Tomalak
4
CDATA 不是不必要的,它有明确的用途。XML 应该是一种可供人类阅读的格式,潜在地可以手动创建。拥有 CDATA 部分非常方便,因为你不能期望用户对他们所写的内容进行 HTML 编码。Go 语言肯定应该支持它。 - laurent
6个回答

19

@spirit-zhang:自Go 1.6起,您现在可以使用、cdata标记:

package main

import (
    "fmt"
    "encoding/xml"
)

type RootElement struct {
    XMLName xml.Name `xml:"root"`
    Summary *Summary `xml:"summary"`
}

type Summary struct {
    XMLName xml.Name `xml:"summary"`
    Text    string   `xml:",cdata"`
}

func main() {

    cdata := `<a href="http://example.org">My Example Website</a>`
    v := RootElement{
        Summary: &Summary{
            Text: cdata,
        },
    }

    b, err := xml.MarshalIndent(v, "", "  ")
    if err != nil {
        fmt.Println("oopsie:", err)
        return
    }
    fmt.Println(string(b))
}

输出:

<root>
  <summary><![CDATA[<a href="http://example.org">My Example Website</a>]]></summary>
</root>

游乐场:https://play.golang.org/p/xRn6fe0ilj

基本规则是:1)它必须是,cdata,你不能指定节点名称,2)使用xml.Name命名节点。

这是如今大多数针对 Go 1.6+ 和 XML 的自定义内容的工作方式(嵌入带有xml.Name的结构体)。


编辑:在RootElement结构体中添加了xml:"summary",所以您也可以将XML调回结构体进行反向Unmarshal(需要在两个地方设置)。


已添加将XML反序列化回结构体的功能(缺少一个“xml”标记)。 - eduncan911
如果您想保持结构字段“简单”,可以通过自定义MarshalXML函数引入字符串类型:https://play.golang.org/p/IoOLiVhESsU - Sergey Kolodyazhnyy
@Sergey 确实如此。然而,在 Go 1.6 及更高版本中,大多数自定义 Xml 扩展都使用这种嵌入 xml.Name 的单个元素拼写模式。坚持这种模式有助于可读性和可维护性。另外,你的示例需要更多的按键次数。 :) - eduncan911

8

我不确定innerxml标签在哪个版本的go中可用,但它允许您包含不会被转义的数据:

代码:

package main

import (
    "encoding/xml"
    "os"
)

type SomeXML struct {
    Unescaped CharData
    Escaped   string
}

type CharData struct {
    Text []byte `xml:",innerxml"`
}

func NewCharData(s string) CharData {
    return CharData{[]byte("<![CDATA[" + s + "]]>")}
}

func main() {
    var s SomeXML
    s.Unescaped = NewCharData("http://www.example.com/?param1=foo&param2=bar")
    s.Escaped = "http://www.example.com/?param1=foo&param2=bar"
    data, _ := xml.MarshalIndent(s, "", "\t")
    os.Stdout.Write(data)
}

输出:

<SomeXML>
    <Unescaped><![CDATA[http://www.example.com/?param1=foo&param2=bar]]></Unescaped>
    <Escaped>http://www.example.com/?param1=foo&amp;param2=bar</Escaped>
</SomeXML>

4

使用",cdata"标注的CDATA。使用"Cdata"创建结构体并与您的xml对象一起使用非常方便。

package main

import (
    "encoding/xml"
    "fmt"
)

type Person struct {
    Name    string `xml:"Name"`
    Age     int    `xml:"AGE"`
    Address Cdata  `xml:"ADDRESS"`
}

type Cdata struct {
    Value string `xml:",cdata"`
}

func main() {

    var address Cdata
    address.Value = "John's House, <House #>: 10,Universe  PIN: 00000  "

    var person Person
    person.Name = "John"
    person.Age = 12
    person.Address = address

    xml, err := xml.MarshalIndent(person, "", "  ")
    if err != nil {
        fmt.Println("oopsie:", err.Error())
        return
    }

    fmt.Println(string(xml))
}

输出:

<Person>
  <Name>John</Name>
  <AGE>12</AGE>
  <ADDRESS><![CDATA[John's House, <House #>: 10,Universe  PIN: 00000  ]]></ADDRESS>
</Person>

Playground: https://play.golang.org/p/sux2_JB-hkt


3

在@BeMasher的回答上进行扩展,您可以使用xml.Marshaller接口来为您完成工作。

package main

import (
    "encoding/xml"
    "os"
)

type SomeXML struct {
    Unescaped CharData
    Escaped   string
}

type CharData string

func (n CharData) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
    return e.EncodeElement(struct{
        S string `xml:",innerxml"`
    }{
        S: "<![CDATA[" + string(n) + "]]>",
    }, start)
}

func main() {
    var s SomeXML
    s.Unescaped = "http://www.example.com/?param1=foo&param2=bar"
    s.Escaped = "http://www.example.com/?param1=foo&param2=bar"
    data, _ := xml.MarshalIndent(s, "", "\t")
    os.Stdout.Write(data)
}

输出:

<SomeXML>
    <Unescaped><![CDATA[http://www.example.com/?param1=foo&param2=bar]]></Unescaped>
    <Escaped>http://www.example.com/?param1=foo&amp;param2=bar</Escaped>
</SomeXML>

3
如@Tomalak所提到的,输出CDATA是不被支持的。
你可以将![CDATA[写成xml标签,然后在生成的xml中替换闭合标签。这对你有用吗?这可能不是最低成本的方法,但是最简单的方法。当然,你可以在下面的示例中使用MarshalIndent调用来替换Marshal调用。 http://play.golang.org/p/2-u7H85-wn
package main

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

type XMLProduct struct {
    XMLName          xml.Name `xml:"row"`
    ProductId        string   `xml:"product_id"`
    ProductName      string   `xml:"![CDATA["`
    OriginalPrice    string   `xml:"original_price"`
    BargainPrice     string   `xml:"bargain_price"`
    TotalReviewCount int      `xml:"total_review_count"`
    AverageScore     float64  `xml:"average_score"`
}

func main() {
    prod := XMLProduct{
        ProductId:        "ProductId",
        ProductName:      "ProductName",
        OriginalPrice:    "OriginalPrice",
        BargainPrice:     "BargainPrice",
        TotalReviewCount: 20,
        AverageScore:     2.1}

    out, err := xml.MarshalIndent(prod, " ", "  ")
    if err != nil {
        fmt.Printf("error: %v", err)
        return
    }

    out = bytes.Replace(out, []byte("<![CDATA[>"), []byte("<![CDATA["), -1)
    out = bytes.Replace(out, []byte("</![CDATA[>"), []byte("]]>"), -1)
    fmt.Println(string(out))
}

7
那太可怕了,令人十分悲伤。有人提出增强请求,以便在标准 API 中获得更有效的实现吗? - Rick-777
@Rick-777:如果确实需要这个功能,也许可以考虑。但正如其他评论所说,XML解析器需要将CDATA块和等效的编码字符数据视为同一种类型,因此在编码时使用哪个版本并没有太大的关系。 - James Henstridge
2
这并不完全正确。解析器需要找到CDATA的结尾,但不需要解析块中的所有字符数据。这意味着,例如,可以轻松地将包含<和>符号的原始JavaScript代码放入XHTML中,而无需使用<或>形式。 - Rick-777
@rputikar - 可能值得在问题中更新一下以反映这一点:: 注意 对于其他阅读此内容的人,现在xml包中有一个charData函数来处理此问题 https://golang.org/pkg/encoding/xml/#CharData - 防止xml编码并将其包装在cdata标记中 - SwiftD
@我 无法让CDATA包正常工作,只能使用innerXML并添加CDATA标记。 - SwiftD

0
如果您使用的是Go 1.6或更高版本,只需添加“cdata”标记即可正常工作。
type XMLProduct struct {
    XMLName          xml.Name `xml:"row"`
    ProductId        string   `xml:"product_id"`
    ProductName      string   `xml:"product_name,cdata"`
    OriginalPrice    string   `xml:"original_price"`
    BargainPrice     string   `xml:"bargain_price"`
    TotalReviewCount int      `xml:"total_review_count"`
    AverageScore     float64  `xml:"average_score"`
}

[] xml: invalid tag in field ProductName of type main.XMLProduct: "product_name,cdata" - Bryce
正如 @Bryce 所提到的,这似乎不起作用,但是必须像其他答案所提到的那样使用嵌套结构体。 - msiemens

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接