Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a CDATA node of xml with go?

Tags:

xml

cdata

go

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?

like image 973
Derrick Zhang Avatar asked Jan 07 '13 07:01

Derrick Zhang


People also ask

How do I add CDATA to XML?

CDATA sections can appear inside element content and allow < and & character literals to appear. A CDATA section begins with the character sequence <! [CDATA[ and ends with the character sequence ]]>. Between the two character sequences, an XML processor ignores all markup characters such as <, >, and &.

How do I get CDATA from XML?

In current Java DOM implementation you can access CDATA simply as text data using e. getTextContent() . See example without type check, cast, e. getData() .

What is a CDATA section in XML?

A CDATA section is used to mark a section of an XML document, so that the XML parser interprets it only as character data, and not as markup. It comes handy when one XML data need to be embedded within another XML document.

Can we use CDATA in XML attribute?

No, The markup denoting a CDATA Section is not permitted as the value of an attribute.


Video Answer


5 Answers

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

like image 94
eduncan911 Avatar answered Oct 04 '22 11:10

eduncan911


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>
like image 41
BeMasher Avatar answered Oct 04 '22 12:10

BeMasher


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

like image 36
Vijay Avatar answered Oct 04 '22 11:10

Vijay


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))
}
like image 32
rputikar Avatar answered Oct 04 '22 12:10

rputikar


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>
like image 34
chowey Avatar answered Oct 04 '22 12:10

chowey