How to create a CDATA node of xml with go?
Asked Answered
U

6

16

I have the following struct:

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"`
}

And I use the encoding/xml to encode this and then display it on web page.

The ProductName field needs to be enclosed with <![CDATA[]]. But if I write it as <![CDATA[ + p.ProductName + ]]>, the < and > will be translated to &lt; and &gt;.

How can I create the CDATA at minimal cost?

Uppermost answered 7/1, 2013 at 7:24 Comment(4)
Why does it need to be CDATA? A CDATA section is a convenience facility, it can be interchanged with an XML encoded value and the document would be the same.Serology
@Serology It's the company specification...Uppermost
The source of encoding/xml/marshal.go does not suggest that outputting CDATA is supported. (Again, CDATA is technically unnecessary. Maybe the specification can be modified after all?)Serology
CDATA is not unnecessary, it has a clear purpose. XML is meant to be a human-readable format and can potentially be created by hand. Having CDATA sections is very convenient as you cannot expect users to HTML-encode what they are writing. Go should definitely support that.Marinamarinade
S
3

As @Tomalak mentioned, outputting CDATA is not supported.

You can probably write ![CDATA[ as xml tag and later on replace the closing tag from the resulting xml. Will this work for you? Its probably not the one with minimal costs, but easiest. You can of course replace the MarshalIndent call with just the Marshal call in the example below.

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))
}
Shing answered 7/1, 2013 at 15:28 Comment(5)
That's horrible and quite sad. Has anyone filed an enhancement request to get a more efficient implementation into the standard api?Parochialism
@Rick-777: if there was a legitimate need for the feature, perhaps. But as other comments have said, XML parsers are required to treat CDATA blocks and equivalent encoded character data the same, so there isn't much reason to care which version is used when encoding.Disadvantaged
That's not entirely correct. The parser is required to find the end of the CDATA but not otherwise parse all the character data in the block. This means it is, for example, easy to put verbatim javascript code containing < and > symbols into XHTML without needing to use the &lt; or &gt; form.Parochialism
@Shing - might be worth putting an update in the question to reflect this :: NOTE For anyone else reading this, there is now a charData function in the xml package to handle this golang.org/pkg/encoding/xml/#CharData - prevents xml encoding and wraps in cdata tagsCaye
@me Cannot get the CDATA package to work, resorted to innerXML and add CDATA tagsCaye
D
20

@spirit-zhang: since Go 1.6, you can now use ,cdata tags:

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

Outputs:

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

Playground: https://play.golang.org/p/xRn6fe0ilj

The rules are basically: 1) it has to be ,cdata, you can't specify the node name and 2) use the xml.Name to name the node as you want.

This is how most of the custom stuff for Go 1.6+ and XML works these days (embedded structs with xml.Name).


EDIT: Added xml:"summary" to the RootElement struct, so you can you can also Unmarshal the xml back to the struct in reverse (required to be set in both places).

Demise answered 5/2, 2017 at 20:24 Comment(3)
EDIT: added the ability to Unmarshal xml back into a struct (was missing an xml tag)Demise
If you want to keep your structure fields "simple", you can introduce string type with custom MarshalXML function: play.golang.org/p/IoOLiVhESsUCurl
@Sergey true. however, most custom extensibility of Xml in Go 1.6 and forward use this pattern of individual elements spelled out with xml.Name embedded. Sticking with the pattern helps readability and maintainability. Also, your example is a lot more key strokes. :)Demise
L
8

I'm not sure which version of go the innerxml tag became available in, but it allows you to include data which won't be escaped:

Code:

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

Output:

<SomeXML>
    <Unescaped><![CDATA[http://www.example.com/?param1=foo&param2=bar]]></Unescaped>
    <Escaped>http://www.example.com/?param1=foo&amp;param2=bar</Escaped>
</SomeXML>
Ladonnalady answered 27/10, 2014 at 18:22 Comment(0)
V
4

Expanding on the answer by @BeMasher, you can use the xml.Marshaller interface to do the work for you.

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

Output:

<SomeXML>
    <Unescaped><![CDATA[http://www.example.com/?param1=foo&param2=bar]]></Unescaped>
    <Escaped>http://www.example.com/?param1=foo&amp;param2=bar</Escaped>
</SomeXML>
Vasilek answered 2/2, 2015 at 22:20 Comment(0)
K
4

CDATA with ",cdata" notation. It is handy to create struct with "Cdata" and use along with your xml object

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

Output:

<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

Knoxville answered 27/10, 2018 at 19:22 Comment(0)
S
3

As @Tomalak mentioned, outputting CDATA is not supported.

You can probably write ![CDATA[ as xml tag and later on replace the closing tag from the resulting xml. Will this work for you? Its probably not the one with minimal costs, but easiest. You can of course replace the MarshalIndent call with just the Marshal call in the example below.

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))
}
Shing answered 7/1, 2013 at 15:28 Comment(5)
That's horrible and quite sad. Has anyone filed an enhancement request to get a more efficient implementation into the standard api?Parochialism
@Rick-777: if there was a legitimate need for the feature, perhaps. But as other comments have said, XML parsers are required to treat CDATA blocks and equivalent encoded character data the same, so there isn't much reason to care which version is used when encoding.Disadvantaged
That's not entirely correct. The parser is required to find the end of the CDATA but not otherwise parse all the character data in the block. This means it is, for example, easy to put verbatim javascript code containing < and > symbols into XHTML without needing to use the &lt; or &gt; form.Parochialism
@Shing - might be worth putting an update in the question to reflect this :: NOTE For anyone else reading this, there is now a charData function in the xml package to handle this golang.org/pkg/encoding/xml/#CharData - prevents xml encoding and wraps in cdata tagsCaye
@me Cannot get the CDATA package to work, resorted to innerXML and add CDATA tagsCaye
R
0

If you use Go version 1.6 or later, just adding 'cdata' tag will work fine.

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"`
}
Roadwork answered 29/6, 2016 at 5:35 Comment(2)
[] xml: invalid tag in field ProductName of type main.XMLProduct: "product_name,cdata"Calico
As mentioned by @Bryce, this doesn't seem to work but one has to use a nested struct instead as mentioned by the other answers.Modred

© 2022 - 2024 — McMap. All rights reserved.