问题 如何用go创建xml的CDATA节点?


我有以下结构:

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 以最低的成本?


7986
2018-01-07 07:24


起源

为什么呢 需要 是CDATA? CDATA部分是便利设施,它可以与XML编码值互换,文档也是相同的。 - Tomalak
@Tomalak这是公司规范...... - Spirit Zhang
该 来源 encoding/xml/marshal.go 并不建议支持输出CDATA。 (同样,CDATA在技术上是不必要的。也许规格可以修改完毕?) - Tomalak
CDATA不是没有必要的,它有明确的目的。 XML是一种人类可读的格式,可以手工创建。拥有CDATA部分非常方便,因为您不能指望用户对他们正在编写的内容进行HTML编码。 Go应该绝对支持。 - this.lau_


答案:


正如@Tomalak所说,不支持输出CDATA。

你可以写 ![CDATA[ 作为xml标记,稍后将从生成的xml中替换结束标记。这对你有用吗?它可能不是成本最低的,但最简单的。你当然可以在下面的例子中用Marshal调用替换MarshalIndent调用。

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))
}

2
2018-01-07 15:28



那太可怕了,太可悲了。有没有人提交增强请求以获得更高效的标准api实现? - Rick-777
@ Rick-777:如果对该功能有合理需求,也许吧。但正如其他评论所说,XML解析器需要同样处理CDATA块和等效编码字符数据,因此没有太多理由关心编码时使用哪个版本。 - James Henstridge
这不完全正确。解析器需要找到CDATA的末尾,否则不会解析块中的所有字符数据。这意味着,例如,很容易将包含<和>符号的逐字javascript代码放入XHTML而无需使用&lt;或&gt;形成。 - Rick-777
@rputikar - 可能值得对问题进行更新以反映这一点:: 注意 对于其他阅读此内容的人来说,xml包中现在有一个charData函数来处理这个问题 golang.org/pkg/encoding/xml/#CharData  - 防止cdml标记中的xml编码和包装 - SwiftD
@me无法使用CDATA包,使用innerXML并添加CDATA标签 - SwiftD


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

码:

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>

6
2017-10-27 18:22





@ 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)。


编辑:添加 xml:"summary" 到了 RootElement 结构,所以你也可以 Unmarshal xml反向返回结构(需要在两个地方设置)。


5
2018-02-05 20:24



这个答案对我有用。 - Melvin
编辑:添加了将xml解组成一个结构的能力(缺少一个 xml 标签) - eduncan911
如果要保持结构字段“简单”,可以使用自定义MarshalXML函数引入字符串类型: play.golang.org/p/IoOLiVhESsU - Sergey


通过@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>

2
2018-02-02 22:20





如果您使用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"`
}

1
2018-06-29 05:35



[] xml: invalid tag in field ProductName of type main.XMLProduct: "product_name,cdata" - Bryce