Commit 3f6b91b1 authored by Charles Weill's avatar Charles Weill Committed by Russ Cox

encoding/xml: Add CDATA-wrapper output support to xml.Marshal.

Fixes #12963

Change-Id: Icc50dfb6130fe1e189d45f923c2f7408d3cf9401
Reviewed-on: https://go-review.googlesource.com/16047Reviewed-by: 's avatarRuss Cox <rsc@golang.org>
parent a48de745
...@@ -768,7 +768,11 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error { ...@@ -768,7 +768,11 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error {
} }
switch finfo.flags & fMode { 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 { if err := s.trim(finfo.parents); err != nil {
return err return err
} }
...@@ -777,7 +781,9 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error { ...@@ -777,7 +781,9 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error {
if err != nil { if err != nil {
return err return err
} }
Escape(p, data) if err := emit(p, data); err != nil {
return err
}
continue continue
} }
if vf.CanAddr() { if vf.CanAddr() {
...@@ -787,27 +793,37 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error { ...@@ -787,27 +793,37 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error {
if err != nil { if err != nil {
return err return err
} }
Escape(p, data) if err := emit(p, data); err != nil {
return err
}
continue continue
} }
} }
var scratch [64]byte var scratch [64]byte
switch vf.Kind() { switch vf.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 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: 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: 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: 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: case reflect.String:
if err := EscapeText(p, []byte(vf.String())); err != nil { if err := emit(p, []byte(vf.String())); err != nil {
return err return err
} }
case reflect.Slice: case reflect.Slice:
if elem, ok := vf.Interface().([]byte); ok { if elem, ok := vf.Interface().([]byte); ok {
if err := EscapeText(p, elem); err != nil { if err := emit(p, elem); err != nil {
return err return err
} }
} }
......
...@@ -356,6 +356,15 @@ type NestedAndComment struct { ...@@ -356,6 +356,15 @@ type NestedAndComment struct {
Comment string `xml:",comment"` 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{} { func ifaceptr(x interface{}) interface{} {
return &x return &x
} }
...@@ -978,6 +987,42 @@ var marshalTests = []struct { ...@@ -978,6 +987,42 @@ var marshalTests = []struct {
MyInt: 42, 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. // Test omitempty with parent chain; see golang.org/issue/4168.
{ {
ExpectXML: `<Strings><A></A></Strings>`, ExpectXML: `<Strings><A></A></Strings>`,
...@@ -1016,6 +1061,10 @@ var marshalTests = []struct { ...@@ -1016,6 +1061,10 @@ var marshalTests = []struct {
ExpectXML: `<NestedAndComment><A><B></B><B></B></A><!--test--></NestedAndComment>`, ExpectXML: `<NestedAndComment><A><B></B><B></B></A><!--test--></NestedAndComment>`,
Value: &NestedAndComment{AB: make([]string, 2), Comment: "test"}, 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) { func TestMarshal(t *testing.T) {
......
...@@ -431,7 +431,7 @@ func (p *Decoder) unmarshal(val reflect.Value, start *StartElement) error { ...@@ -431,7 +431,7 @@ func (p *Decoder) unmarshal(val reflect.Value, start *StartElement) error {
} }
} }
case fCharData: case fCDATA, fCharData:
if !saveData.IsValid() { if !saveData.IsValid() {
saveData = finfo.value(sv) saveData = finfo.value(sv)
} }
......
...@@ -31,6 +31,7 @@ type fieldFlags int ...@@ -31,6 +31,7 @@ type fieldFlags int
const ( const (
fElement fieldFlags = 1 << iota fElement fieldFlags = 1 << iota
fAttr fAttr
fCDATA
fCharData fCharData
fInnerXml fInnerXml
fComment fComment
...@@ -38,7 +39,7 @@ const ( ...@@ -38,7 +39,7 @@ const (
fOmitEmpty fOmitEmpty
fMode = fElement | fAttr | fCharData | fInnerXml | fComment | fAny fMode = fElement | fAttr | fCDATA | fCharData | fInnerXml | fComment | fAny
) )
var tinfoMap = make(map[reflect.Type]*typeInfo) var tinfoMap = make(map[reflect.Type]*typeInfo)
...@@ -130,6 +131,8 @@ func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, erro ...@@ -130,6 +131,8 @@ func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, erro
switch flag { switch flag {
case "attr": case "attr":
finfo.flags |= fAttr finfo.flags |= fAttr
case "cdata":
finfo.flags |= fCDATA
case "chardata": case "chardata":
finfo.flags |= fCharData finfo.flags |= fCharData
case "innerxml": case "innerxml":
...@@ -148,7 +151,7 @@ func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, erro ...@@ -148,7 +151,7 @@ func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, erro
switch mode := finfo.flags & fMode; mode { switch mode := finfo.flags & fMode; mode {
case 0: case 0:
finfo.flags |= fElement finfo.flags |= fElement
case fAttr, fCharData, fInnerXml, fComment, fAny: case fAttr, fCDATA, fCharData, fInnerXml, fComment, fAny:
if f.Name == "XMLName" || tag != "" && mode != fAttr { if f.Name == "XMLName" || tag != "" && mode != fAttr {
valid = false valid = false
} }
......
...@@ -1943,6 +1943,46 @@ func Escape(w io.Writer, s []byte) { ...@@ -1943,6 +1943,46 @@ func Escape(w io.Writer, s []byte) {
EscapeText(w, s) 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='...'` // procInst parses the `param="..."` or `param='...'`
// value out of the provided string, returning "" if not found. // value out of the provided string, returning "" if not found.
func procInst(param, s string) string { func procInst(param, s string) string {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment