]> Cypherpunks repositories - gostls13.git/commitdiff
encoding/xml: Add CDATA-wrapper output support to xml.Marshal.
authorCharles Weill <weill@google.com>
Fri, 23 Oct 2015 20:08:20 +0000 (16:08 -0400)
committerRuss Cox <rsc@golang.org>
Wed, 25 Nov 2015 17:04:43 +0000 (17:04 +0000)
Fixes #12963

Change-Id: Icc50dfb6130fe1e189d45f923c2f7408d3cf9401
Reviewed-on: https://go-review.googlesource.com/16047
Reviewed-by: Russ Cox <rsc@golang.org>
src/encoding/xml/marshal.go
src/encoding/xml/marshal_test.go
src/encoding/xml/read.go
src/encoding/xml/typeinfo.go
src/encoding/xml/xml.go

index 86d1422a5bd53fbc458c358079e5e0940b9161c5..f908ccb1cb1dd5f30ce480173a98a6cdea67856f 100644 (file)
@@ -768,7 +768,11 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error {
                }
 
                switch finfo.flags & fMode {
-               case fCharData:
+               case fCDATA, fCharData:
+                       emit := EscapeText
+                       if finfo.flags&fMode == fCDATA {
+                               emit = emitCDATA
+                       }
                        if err := s.trim(finfo.parents); err != nil {
                                return err
                        }
@@ -777,7 +781,9 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error {
                                if err != nil {
                                        return err
                                }
-                               Escape(p, data)
+                               if err := emit(p, data); err != nil {
+                                       return err
+                               }
                                continue
                        }
                        if vf.CanAddr() {
@@ -787,27 +793,37 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error {
                                        if err != nil {
                                                return err
                                        }
-                                       Escape(p, data)
+                                       if err := emit(p, data); err != nil {
+                                               return err
+                                       }
                                        continue
                                }
                        }
                        var scratch [64]byte
                        switch vf.Kind() {
                        case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
-                               Escape(p, strconv.AppendInt(scratch[:0], vf.Int(), 10))
+                               if err := emit(p, strconv.AppendInt(scratch[:0], vf.Int(), 10)); err != nil {
+                                       return err
+                               }
                        case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
-                               Escape(p, strconv.AppendUint(scratch[:0], vf.Uint(), 10))
+                               if err := emit(p, strconv.AppendUint(scratch[:0], vf.Uint(), 10)); err != nil {
+                                       return err
+                               }
                        case reflect.Float32, reflect.Float64:
-                               Escape(p, strconv.AppendFloat(scratch[:0], vf.Float(), 'g', -1, vf.Type().Bits()))
+                               if err := emit(p, strconv.AppendFloat(scratch[:0], vf.Float(), 'g', -1, vf.Type().Bits())); err != nil {
+                                       return err
+                               }
                        case reflect.Bool:
-                               Escape(p, strconv.AppendBool(scratch[:0], vf.Bool()))
+                               if err := emit(p, strconv.AppendBool(scratch[:0], vf.Bool())); err != nil {
+                                       return err
+                               }
                        case reflect.String:
-                               if err := EscapeText(p, []byte(vf.String())); err != nil {
+                               if err := emit(p, []byte(vf.String())); err != nil {
                                        return err
                                }
                        case reflect.Slice:
                                if elem, ok := vf.Interface().([]byte); ok {
-                                       if err := EscapeText(p, elem); err != nil {
+                                       if err := emit(p, elem); err != nil {
                                                return err
                                        }
                                }
index aab94b16f3597760244ccc512b40718e7aa17f71..fe8b16fe43145b63bfba444c554c66028df96381 100644 (file)
@@ -356,6 +356,15 @@ type NestedAndComment struct {
        Comment string   `xml:",comment"`
 }
 
+type CDataTest struct {
+       Chardata string `xml:",cdata"`
+}
+
+type NestedAndCData struct {
+       AB    []string `xml:"A>B"`
+       CDATA string   `xml:",cdata"`
+}
+
 func ifaceptr(x interface{}) interface{} {
        return &x
 }
@@ -978,6 +987,42 @@ var marshalTests = []struct {
                        MyInt: 42,
                },
        },
+       // Test outputting CDATA-wrapped text.
+       {
+               ExpectXML: `<CDataTest></CDataTest>`,
+               Value:     &CDataTest{},
+       },
+       {
+               ExpectXML: `<CDataTest><![CDATA[http://example.com/tests/1?foo=1&bar=baz]]></CDataTest>`,
+               Value: &CDataTest{
+                       Chardata: "http://example.com/tests/1?foo=1&bar=baz",
+               },
+       },
+       {
+               ExpectXML: `<CDataTest><![CDATA[Literal <![CDATA[Nested]]]]><![CDATA[>!]]></CDataTest>`,
+               Value: &CDataTest{
+                       Chardata: "Literal <![CDATA[Nested]]>!",
+               },
+       },
+       {
+               ExpectXML: `<CDataTest><![CDATA[<![CDATA[Nested]]]]><![CDATA[> Literal!]]></CDataTest>`,
+               Value: &CDataTest{
+                       Chardata: "<![CDATA[Nested]]> Literal!",
+               },
+       },
+       {
+               ExpectXML: `<CDataTest><![CDATA[<![CDATA[Nested]]]]><![CDATA[> Literal! <![CDATA[Nested]]]]><![CDATA[> Literal!]]></CDataTest>`,
+               Value: &CDataTest{
+                       Chardata: "<![CDATA[Nested]]> Literal! <![CDATA[Nested]]> Literal!",
+               },
+       },
+       {
+               ExpectXML: `<CDataTest><![CDATA[<![CDATA[<![CDATA[Nested]]]]><![CDATA[>]]]]><![CDATA[>]]></CDataTest>`,
+               Value: &CDataTest{
+                       Chardata: "<![CDATA[<![CDATA[Nested]]>]]>",
+               },
+       },
+
        // Test omitempty with parent chain; see golang.org/issue/4168.
        {
                ExpectXML: `<Strings><A></A></Strings>`,
@@ -1016,6 +1061,10 @@ var marshalTests = []struct {
                ExpectXML: `<NestedAndComment><A><B></B><B></B></A><!--test--></NestedAndComment>`,
                Value:     &NestedAndComment{AB: make([]string, 2), Comment: "test"},
        },
+       {
+               ExpectXML: `<NestedAndCData><A><B></B><B></B></A><![CDATA[test]]></NestedAndCData>`,
+               Value:     &NestedAndCData{AB: make([]string, 2), CDATA: "test"},
+       },
 }
 
 func TestMarshal(t *testing.T) {
index 75b9f2ba1b2ee44fad5e1a72e53058c988364db2..77b4c7b4955b720cd8538aeea29ca71ac1a904e4 100644 (file)
@@ -431,7 +431,7 @@ func (p *Decoder) unmarshal(val reflect.Value, start *StartElement) error {
                                        }
                                }
 
-                       case fCharData:
+                       case fCDATA, fCharData:
                                if !saveData.IsValid() {
                                        saveData = finfo.value(sv)
                                }
index 6766b88f09a194546589ca2f320dcc063d62ea0f..6483c8dbe67e0bd22f64ee92b55e194b1f48aa94 100644 (file)
@@ -31,6 +31,7 @@ type fieldFlags int
 const (
        fElement fieldFlags = 1 << iota
        fAttr
+       fCDATA
        fCharData
        fInnerXml
        fComment
@@ -38,7 +39,7 @@ const (
 
        fOmitEmpty
 
-       fMode = fElement | fAttr | fCharData | fInnerXml | fComment | fAny
+       fMode = fElement | fAttr | fCDATA | fCharData | fInnerXml | fComment | fAny
 )
 
 var tinfoMap = make(map[reflect.Type]*typeInfo)
@@ -130,6 +131,8 @@ func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, erro
                        switch flag {
                        case "attr":
                                finfo.flags |= fAttr
+                       case "cdata":
+                               finfo.flags |= fCDATA
                        case "chardata":
                                finfo.flags |= fCharData
                        case "innerxml":
@@ -148,7 +151,7 @@ func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, erro
                switch mode := finfo.flags & fMode; mode {
                case 0:
                        finfo.flags |= fElement
-               case fAttr, fCharData, fInnerXml, fComment, fAny:
+               case fAttr, fCDATA, fCharData, fInnerXml, fComment, fAny:
                        if f.Name == "XMLName" || tag != "" && mode != fAttr {
                                valid = false
                        }
index 6c7debe521051cf1b80d531e543030662cb2f95c..bd766a693457e19720620b7c6222c6ceef54a565 100644 (file)
@@ -1943,6 +1943,46 @@ func Escape(w io.Writer, s []byte) {
        EscapeText(w, s)
 }
 
+var (
+       cdataStart  = []byte("<![CDATA[")
+       cdataEnd    = []byte("]]>")
+       cdataEscape = []byte("]]]]><![CDATA[>")
+)
+
+// emitCDATA writes to w the CDATA-wrapped plain text data s.
+// It escapes CDATA directives nested in s.
+func emitCDATA(w io.Writer, s []byte) error {
+       if len(s) == 0 {
+               return nil
+       }
+       if _, err := w.Write(cdataStart); err != nil {
+               return err
+       }
+       for {
+               i := bytes.Index(s, cdataEnd)
+               if i >= 0 && i+len(cdataEnd) <= len(s) {
+                       // Found a nested CDATA directive end.
+                       if _, err := w.Write(s[:i]); err != nil {
+                               return err
+                       }
+                       if _, err := w.Write(cdataEscape); err != nil {
+                               return err
+                       }
+                       i += len(cdataEnd)
+               } else {
+                       if _, err := w.Write(s); err != nil {
+                               return err
+                       }
+                       break
+               }
+               s = s[i:]
+       }
+       if _, err := w.Write(cdataEnd); err != nil {
+               return err
+       }
+       return nil
+}
+
 // procInst parses the `param="..."` or `param='...'`
 // value out of the provided string, returning "" if not found.
 func procInst(param, s string) string {