|
|
|
|
|
|
|
|
|
package xml |
|
|
|
import ( |
|
"bufio" |
|
"bytes" |
|
"encoding" |
|
"fmt" |
|
"io" |
|
"reflect" |
|
"strconv" |
|
"strings" |
|
) |
|
|
|
const ( |
|
|
|
|
|
|
|
Header = `<?xml version="1.0" encoding="UTF-8"?>` + "\n" |
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func Marshal(v interface{}) ([]byte, error) { |
|
var b bytes.Buffer |
|
if err := NewEncoder(&b).Encode(v); err != nil { |
|
return nil, err |
|
} |
|
return b.Bytes(), nil |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type Marshaler interface { |
|
MarshalXML(e *Encoder, start StartElement) error |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type MarshalerAttr interface { |
|
MarshalXMLAttr(name Name) (Attr, error) |
|
} |
|
|
|
|
|
|
|
|
|
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) { |
|
var b bytes.Buffer |
|
enc := NewEncoder(&b) |
|
enc.Indent(prefix, indent) |
|
if err := enc.Encode(v); err != nil { |
|
return nil, err |
|
} |
|
return b.Bytes(), nil |
|
} |
|
|
|
|
|
type Encoder struct { |
|
p printer |
|
} |
|
|
|
|
|
func NewEncoder(w io.Writer) *Encoder { |
|
e := &Encoder{printer{Writer: bufio.NewWriter(w)}} |
|
e.p.encoder = e |
|
return e |
|
} |
|
|
|
|
|
|
|
|
|
func (enc *Encoder) Indent(prefix, indent string) { |
|
enc.p.prefix = prefix |
|
enc.p.indent = indent |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (enc *Encoder) Encode(v interface{}) error { |
|
err := enc.p.marshalValue(reflect.ValueOf(v), nil, nil) |
|
if err != nil { |
|
return err |
|
} |
|
return enc.p.Flush() |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (enc *Encoder) EncodeElement(v interface{}, start StartElement) error { |
|
err := enc.p.marshalValue(reflect.ValueOf(v), nil, &start) |
|
if err != nil { |
|
return err |
|
} |
|
return enc.p.Flush() |
|
} |
|
|
|
var ( |
|
begComment = []byte("<!--") |
|
endComment = []byte("-->") |
|
endProcInst = []byte("?>") |
|
endDirective = []byte(">") |
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (enc *Encoder) EncodeToken(t Token) error { |
|
|
|
p := &enc.p |
|
switch t := t.(type) { |
|
case StartElement: |
|
if err := p.writeStart(&t); err != nil { |
|
return err |
|
} |
|
case EndElement: |
|
if err := p.writeEnd(t.Name); err != nil { |
|
return err |
|
} |
|
case CharData: |
|
escapeText(p, t, false) |
|
case Comment: |
|
if bytes.Contains(t, endComment) { |
|
return fmt.Errorf("xml: EncodeToken of Comment containing --> marker") |
|
} |
|
p.WriteString("<!--") |
|
p.Write(t) |
|
p.WriteString("-->") |
|
return p.cachedWriteError() |
|
case ProcInst: |
|
|
|
|
|
if t.Target == "xml" && p.Buffered() != 0 { |
|
return fmt.Errorf("xml: EncodeToken of ProcInst xml target only valid for xml declaration, first token encoded") |
|
} |
|
if !isNameString(t.Target) { |
|
return fmt.Errorf("xml: EncodeToken of ProcInst with invalid Target") |
|
} |
|
if bytes.Contains(t.Inst, endProcInst) { |
|
return fmt.Errorf("xml: EncodeToken of ProcInst containing ?> marker") |
|
} |
|
p.WriteString("<?") |
|
p.WriteString(t.Target) |
|
if len(t.Inst) > 0 { |
|
p.WriteByte(' ') |
|
p.Write(t.Inst) |
|
} |
|
p.WriteString("?>") |
|
case Directive: |
|
if !isValidDirective(t) { |
|
return fmt.Errorf("xml: EncodeToken of Directive containing wrong < or > markers") |
|
} |
|
p.WriteString("<!") |
|
p.Write(t) |
|
p.WriteString(">") |
|
default: |
|
return fmt.Errorf("xml: EncodeToken of invalid token type") |
|
|
|
} |
|
return p.cachedWriteError() |
|
} |
|
|
|
|
|
|
|
func isValidDirective(dir Directive) bool { |
|
var ( |
|
depth int |
|
inquote uint8 |
|
incomment bool |
|
) |
|
for i, c := range dir { |
|
switch { |
|
case incomment: |
|
if c == '>' { |
|
if n := 1 + i - len(endComment); n >= 0 && bytes.Equal(dir[n:i+1], endComment) { |
|
incomment = false |
|
} |
|
} |
|
|
|
case inquote != 0: |
|
if c == inquote { |
|
inquote = 0 |
|
} |
|
|
|
case c == '\'' || c == '"': |
|
inquote = c |
|
case c == '<': |
|
if i+len(begComment) < len(dir) && bytes.Equal(dir[i:i+len(begComment)], begComment) { |
|
incomment = true |
|
} else { |
|
depth++ |
|
} |
|
case c == '>': |
|
if depth == 0 { |
|
return false |
|
} |
|
depth-- |
|
} |
|
} |
|
return depth == 0 && inquote == 0 && !incomment |
|
} |
|
|
|
|
|
|
|
func (enc *Encoder) Flush() error { |
|
return enc.p.Flush() |
|
} |
|
|
|
type printer struct { |
|
*bufio.Writer |
|
encoder *Encoder |
|
seq int |
|
indent string |
|
prefix string |
|
depth int |
|
indentedIn bool |
|
putNewline bool |
|
defaultNS string |
|
attrNS map[string]string |
|
attrPrefix map[string]string |
|
prefixes []printerPrefix |
|
tags []Name |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type printerPrefix struct { |
|
prefix string |
|
url string |
|
mark bool |
|
} |
|
|
|
func (p *printer) prefixForNS(url string, isAttr bool) string { |
|
|
|
|
|
|
|
|
|
if url == xmlURL { |
|
return "xml" |
|
} |
|
if !isAttr && url == p.defaultNS { |
|
|
|
return "" |
|
} |
|
return p.attrPrefix[url] |
|
} |
|
|
|
|
|
|
|
|
|
func (p *printer) defineNS(attr Attr, ignoreNonEmptyDefault bool) error { |
|
var prefix string |
|
if attr.Name.Local == "xmlns" { |
|
if attr.Name.Space != "" && attr.Name.Space != "xml" && attr.Name.Space != xmlURL { |
|
return fmt.Errorf("xml: cannot redefine xmlns attribute prefix") |
|
} |
|
} else if attr.Name.Space == "xmlns" && attr.Name.Local != "" { |
|
prefix = attr.Name.Local |
|
if attr.Value == "" { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return nil |
|
} |
|
} else { |
|
|
|
return nil |
|
} |
|
if prefix == "" { |
|
if attr.Value == p.defaultNS { |
|
|
|
return nil |
|
} |
|
if attr.Value != "" && ignoreNonEmptyDefault { |
|
|
|
|
|
|
|
|
|
|
|
return nil |
|
} |
|
} else if _, ok := p.attrPrefix[attr.Value]; ok { |
|
|
|
|
|
|
|
|
|
return nil |
|
} |
|
p.pushPrefix(prefix, attr.Value) |
|
return nil |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (p *printer) createNSPrefix(url string, isAttr bool) { |
|
if _, ok := p.attrPrefix[url]; ok { |
|
|
|
return |
|
} |
|
switch { |
|
case !isAttr && url == p.defaultNS: |
|
|
|
return |
|
case url == "": |
|
|
|
|
|
|
|
if p.defaultNS != "" { |
|
|
|
|
|
p.pushPrefix("", "") |
|
} |
|
return |
|
case url == xmlURL: |
|
return |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
prefix := strings.TrimRight(url, "/") |
|
if i := strings.LastIndex(prefix, "/"); i >= 0 { |
|
prefix = prefix[i+1:] |
|
} |
|
if prefix == "" || !isName([]byte(prefix)) || strings.Contains(prefix, ":") { |
|
prefix = "_" |
|
} |
|
if strings.HasPrefix(prefix, "xml") { |
|
|
|
prefix = "_" + prefix |
|
} |
|
if p.attrNS[prefix] != "" { |
|
|
|
for p.seq++; ; p.seq++ { |
|
if id := prefix + "_" + strconv.Itoa(p.seq); p.attrNS[id] == "" { |
|
prefix = id |
|
break |
|
} |
|
} |
|
} |
|
|
|
p.pushPrefix(prefix, url) |
|
} |
|
|
|
|
|
|
|
|
|
func (p *printer) writeNamespaces() { |
|
for i := len(p.prefixes) - 1; i >= 0; i-- { |
|
prefix := p.prefixes[i] |
|
if prefix.mark { |
|
return |
|
} |
|
p.WriteString(" ") |
|
if prefix.prefix == "" { |
|
|
|
p.WriteString(`xmlns="`) |
|
} else { |
|
p.WriteString("xmlns:") |
|
p.WriteString(prefix.prefix) |
|
p.WriteString(`="`) |
|
} |
|
EscapeText(p, []byte(p.nsForPrefix(prefix.prefix))) |
|
p.WriteString(`"`) |
|
} |
|
} |
|
|
|
|
|
|
|
func (p *printer) pushPrefix(prefix, url string) { |
|
p.prefixes = append(p.prefixes, printerPrefix{ |
|
prefix: prefix, |
|
url: p.nsForPrefix(prefix), |
|
}) |
|
p.setAttrPrefix(prefix, url) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (p *printer) nsForPrefix(prefix string) string { |
|
if prefix == "" { |
|
return p.defaultNS |
|
} |
|
return p.attrNS[prefix] |
|
} |
|
|
|
|
|
|
|
func (p *printer) markPrefix() { |
|
p.prefixes = append(p.prefixes, printerPrefix{ |
|
mark: true, |
|
}) |
|
} |
|
|
|
|
|
|
|
func (p *printer) popPrefix() { |
|
for len(p.prefixes) > 0 { |
|
prefix := p.prefixes[len(p.prefixes)-1] |
|
p.prefixes = p.prefixes[:len(p.prefixes)-1] |
|
if prefix.mark { |
|
break |
|
} |
|
p.setAttrPrefix(prefix.prefix, prefix.url) |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
func (p *printer) setAttrPrefix(prefix, url string) { |
|
if prefix == "" { |
|
p.defaultNS = url |
|
return |
|
} |
|
if url == "" { |
|
delete(p.attrPrefix, p.attrNS[prefix]) |
|
delete(p.attrNS, prefix) |
|
return |
|
} |
|
if p.attrPrefix == nil { |
|
|
|
p.attrPrefix = make(map[string]string) |
|
p.attrNS = make(map[string]string) |
|
} |
|
|
|
|
|
|
|
delete(p.attrPrefix, p.attrNS[prefix]) |
|
p.attrPrefix[url] = prefix |
|
p.attrNS[prefix] = url |
|
} |
|
|
|
var ( |
|
marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem() |
|
marshalerAttrType = reflect.TypeOf((*MarshalerAttr)(nil)).Elem() |
|
textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() |
|
) |
|
|
|
|
|
|
|
func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplate *StartElement) error { |
|
if startTemplate != nil && startTemplate.Name.Local == "" { |
|
return fmt.Errorf("xml: EncodeElement of StartElement with missing name") |
|
} |
|
|
|
if !val.IsValid() { |
|
return nil |
|
} |
|
if finfo != nil && finfo.flags&fOmitEmpty != 0 && isEmptyValue(val) { |
|
return nil |
|
} |
|
|
|
|
|
|
|
|
|
for val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr { |
|
if val.IsNil() { |
|
return nil |
|
} |
|
val = val.Elem() |
|
} |
|
|
|
kind := val.Kind() |
|
typ := val.Type() |
|
|
|
|
|
if val.CanInterface() && typ.Implements(marshalerType) { |
|
return p.marshalInterface(val.Interface().(Marshaler), p.defaultStart(typ, finfo, startTemplate)) |
|
} |
|
if val.CanAddr() { |
|
pv := val.Addr() |
|
if pv.CanInterface() && pv.Type().Implements(marshalerType) { |
|
return p.marshalInterface(pv.Interface().(Marshaler), p.defaultStart(pv.Type(), finfo, startTemplate)) |
|
} |
|
} |
|
|
|
|
|
if val.CanInterface() && typ.Implements(textMarshalerType) { |
|
return p.marshalTextInterface(val.Interface().(encoding.TextMarshaler), p.defaultStart(typ, finfo, startTemplate)) |
|
} |
|
if val.CanAddr() { |
|
pv := val.Addr() |
|
if pv.CanInterface() && pv.Type().Implements(textMarshalerType) { |
|
return p.marshalTextInterface(pv.Interface().(encoding.TextMarshaler), p.defaultStart(pv.Type(), finfo, startTemplate)) |
|
} |
|
} |
|
|
|
|
|
if (kind == reflect.Slice || kind == reflect.Array) && typ.Elem().Kind() != reflect.Uint8 { |
|
for i, n := 0, val.Len(); i < n; i++ { |
|
if err := p.marshalValue(val.Index(i), finfo, startTemplate); err != nil { |
|
return err |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
tinfo, err := getTypeInfo(typ) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var start StartElement |
|
|
|
|
|
|
|
explicitNS := false |
|
|
|
if startTemplate != nil { |
|
start.Name = startTemplate.Name |
|
explicitNS = true |
|
start.Attr = append(start.Attr, startTemplate.Attr...) |
|
} else if tinfo.xmlname != nil { |
|
xmlname := tinfo.xmlname |
|
if xmlname.name != "" { |
|
start.Name.Space, start.Name.Local = xmlname.xmlns, xmlname.name |
|
} else if v, ok := xmlname.value(val).Interface().(Name); ok && v.Local != "" { |
|
start.Name = v |
|
} |
|
explicitNS = true |
|
} |
|
if start.Name.Local == "" && finfo != nil { |
|
start.Name.Local = finfo.name |
|
if finfo.xmlns != "" { |
|
start.Name.Space = finfo.xmlns |
|
explicitNS = true |
|
} |
|
} |
|
if start.Name.Local == "" { |
|
name := typ.Name() |
|
if name == "" { |
|
return &UnsupportedTypeError{typ} |
|
} |
|
start.Name.Local = name |
|
} |
|
|
|
|
|
|
|
|
|
|
|
defaultNS := p.defaultNS |
|
|
|
|
|
for i := range tinfo.fields { |
|
finfo := &tinfo.fields[i] |
|
if finfo.flags&fAttr == 0 { |
|
continue |
|
} |
|
attr, err := p.fieldAttr(finfo, val) |
|
if err != nil { |
|
return err |
|
} |
|
if attr.Name.Local == "" { |
|
continue |
|
} |
|
start.Attr = append(start.Attr, attr) |
|
if attr.Name.Space == "" && attr.Name.Local == "xmlns" { |
|
defaultNS = attr.Value |
|
} |
|
} |
|
if !explicitNS { |
|
|
|
|
|
start.Name.Space = defaultNS |
|
} |
|
|
|
|
|
start.setDefaultNamespace() |
|
|
|
if err := p.writeStart(&start); err != nil { |
|
return err |
|
} |
|
|
|
if val.Kind() == reflect.Struct { |
|
err = p.marshalStruct(tinfo, val) |
|
} else { |
|
s, b, err1 := p.marshalSimple(typ, val) |
|
if err1 != nil { |
|
err = err1 |
|
} else if b != nil { |
|
EscapeText(p, b) |
|
} else { |
|
p.EscapeString(s) |
|
} |
|
} |
|
if err != nil { |
|
return err |
|
} |
|
|
|
if err := p.writeEnd(start.Name); err != nil { |
|
return err |
|
} |
|
|
|
return p.cachedWriteError() |
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (p *printer) fieldAttr(finfo *fieldInfo, val reflect.Value) (Attr, error) { |
|
fv := finfo.value(val) |
|
name := Name{Space: finfo.xmlns, Local: finfo.name} |
|
if finfo.flags&fOmitEmpty != 0 && isEmptyValue(fv) { |
|
return Attr{}, nil |
|
} |
|
if fv.Kind() == reflect.Interface && fv.IsNil() { |
|
return Attr{}, nil |
|
} |
|
if fv.CanInterface() && fv.Type().Implements(marshalerAttrType) { |
|
attr, err := fv.Interface().(MarshalerAttr).MarshalXMLAttr(name) |
|
return attr, err |
|
} |
|
if fv.CanAddr() { |
|
pv := fv.Addr() |
|
if pv.CanInterface() && pv.Type().Implements(marshalerAttrType) { |
|
attr, err := pv.Interface().(MarshalerAttr).MarshalXMLAttr(name) |
|
return attr, err |
|
} |
|
} |
|
if fv.CanInterface() && fv.Type().Implements(textMarshalerType) { |
|
text, err := fv.Interface().(encoding.TextMarshaler).MarshalText() |
|
if err != nil { |
|
return Attr{}, err |
|
} |
|
return Attr{name, string(text)}, nil |
|
} |
|
if fv.CanAddr() { |
|
pv := fv.Addr() |
|
if pv.CanInterface() && pv.Type().Implements(textMarshalerType) { |
|
text, err := pv.Interface().(encoding.TextMarshaler).MarshalText() |
|
if err != nil { |
|
return Attr{}, err |
|
} |
|
return Attr{name, string(text)}, nil |
|
} |
|
} |
|
|
|
switch fv.Kind() { |
|
case reflect.Ptr, reflect.Interface: |
|
if fv.IsNil() { |
|
return Attr{}, nil |
|
} |
|
fv = fv.Elem() |
|
} |
|
s, b, err := p.marshalSimple(fv.Type(), fv) |
|
if err != nil { |
|
return Attr{}, err |
|
} |
|
if b != nil { |
|
s = string(b) |
|
} |
|
return Attr{name, s}, nil |
|
} |
|
|
|
|
|
|
|
func (p *printer) defaultStart(typ reflect.Type, finfo *fieldInfo, startTemplate *StartElement) StartElement { |
|
var start StartElement |
|
|
|
|
|
if startTemplate != nil { |
|
start.Name = startTemplate.Name |
|
start.Attr = append(start.Attr, startTemplate.Attr...) |
|
} else if finfo != nil && finfo.name != "" { |
|
start.Name.Local = finfo.name |
|
start.Name.Space = finfo.xmlns |
|
} else if typ.Name() != "" { |
|
start.Name.Local = typ.Name() |
|
} else { |
|
|
|
|
|
start.Name.Local = typ.Elem().Name() |
|
} |
|
|
|
|
|
if start.Name.Space == "" { |
|
start.Name.Space = p.defaultNS |
|
} |
|
start.setDefaultNamespace() |
|
return start |
|
} |
|
|
|
|
|
func (p *printer) marshalInterface(val Marshaler, start StartElement) error { |
|
|
|
|
|
p.tags = append(p.tags, Name{}) |
|
n := len(p.tags) |
|
|
|
err := val.MarshalXML(p.encoder, start) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
|
|
if len(p.tags) > n { |
|
return fmt.Errorf("xml: %s.MarshalXML wrote invalid XML: <%s> not closed", receiverType(val), p.tags[len(p.tags)-1].Local) |
|
} |
|
p.tags = p.tags[:n-1] |
|
return nil |
|
} |
|
|
|
|
|
func (p *printer) marshalTextInterface(val encoding.TextMarshaler, start StartElement) error { |
|
if err := p.writeStart(&start); err != nil { |
|
return err |
|
} |
|
text, err := val.MarshalText() |
|
if err != nil { |
|
return err |
|
} |
|
EscapeText(p, text) |
|
return p.writeEnd(start.Name) |
|
} |
|
|
|
|
|
func (p *printer) writeStart(start *StartElement) error { |
|
if start.Name.Local == "" { |
|
return fmt.Errorf("xml: start tag with no name") |
|
} |
|
|
|
p.tags = append(p.tags, start.Name) |
|
p.markPrefix() |
|
|
|
|
|
|
|
|
|
ignoreNonEmptyDefault := start.Name.Space == "" |
|
for _, attr := range start.Attr { |
|
if err := p.defineNS(attr, ignoreNonEmptyDefault); err != nil { |
|
return err |
|
} |
|
} |
|
|
|
for _, attr := range start.Attr { |
|
name := attr.Name |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if name.Space != "" && !name.isNamespace() { |
|
p.createNSPrefix(name.Space, true) |
|
} |
|
} |
|
p.createNSPrefix(start.Name.Space, false) |
|
|
|
p.writeIndent(1) |
|
p.WriteByte('<') |
|
p.writeName(start.Name, false) |
|
p.writeNamespaces() |
|
for _, attr := range start.Attr { |
|
name := attr.Name |
|
if name.Local == "" || name.isNamespace() { |
|
|
|
continue |
|
} |
|
p.WriteByte(' ') |
|
p.writeName(name, true) |
|
p.WriteString(`="`) |
|
p.EscapeString(attr.Value) |
|
p.WriteByte('"') |
|
} |
|
p.WriteByte('>') |
|
return nil |
|
} |
|
|
|
|
|
|
|
func (p *printer) writeName(name Name, isAttr bool) { |
|
if prefix := p.prefixForNS(name.Space, isAttr); prefix != "" { |
|
p.WriteString(prefix) |
|
p.WriteByte(':') |
|
} |
|
p.WriteString(name.Local) |
|
} |
|
|
|
func (p *printer) writeEnd(name Name) error { |
|
if name.Local == "" { |
|
return fmt.Errorf("xml: end tag with no name") |
|
} |
|
if len(p.tags) == 0 || p.tags[len(p.tags)-1].Local == "" { |
|
return fmt.Errorf("xml: end tag </%s> without start tag", name.Local) |
|
} |
|
if top := p.tags[len(p.tags)-1]; top != name { |
|
if top.Local != name.Local { |
|
return fmt.Errorf("xml: end tag </%s> does not match start tag <%s>", name.Local, top.Local) |
|
} |
|
return fmt.Errorf("xml: end tag </%s> in namespace %s does not match start tag <%s> in namespace %s", name.Local, name.Space, top.Local, top.Space) |
|
} |
|
p.tags = p.tags[:len(p.tags)-1] |
|
|
|
p.writeIndent(-1) |
|
p.WriteByte('<') |
|
p.WriteByte('/') |
|
p.writeName(name, false) |
|
p.WriteByte('>') |
|
p.popPrefix() |
|
return nil |
|
} |
|
|
|
func (p *printer) marshalSimple(typ reflect.Type, val reflect.Value) (string, []byte, error) { |
|
switch val.Kind() { |
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
|
return strconv.FormatInt(val.Int(), 10), nil, nil |
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
|
return strconv.FormatUint(val.Uint(), 10), nil, nil |
|
case reflect.Float32, reflect.Float64: |
|
return strconv.FormatFloat(val.Float(), 'g', -1, val.Type().Bits()), nil, nil |
|
case reflect.String: |
|
return val.String(), nil, nil |
|
case reflect.Bool: |
|
return strconv.FormatBool(val.Bool()), nil, nil |
|
case reflect.Array: |
|
if typ.Elem().Kind() != reflect.Uint8 { |
|
break |
|
} |
|
|
|
var bytes []byte |
|
if val.CanAddr() { |
|
bytes = val.Slice(0, val.Len()).Bytes() |
|
} else { |
|
bytes = make([]byte, val.Len()) |
|
reflect.Copy(reflect.ValueOf(bytes), val) |
|
} |
|
return "", bytes, nil |
|
case reflect.Slice: |
|
if typ.Elem().Kind() != reflect.Uint8 { |
|
break |
|
} |
|
|
|
return "", val.Bytes(), nil |
|
} |
|
return "", nil, &UnsupportedTypeError{typ} |
|
} |
|
|
|
var ddBytes = []byte("--") |
|
|
|
func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error { |
|
s := parentStack{p: p} |
|
for i := range tinfo.fields { |
|
finfo := &tinfo.fields[i] |
|
if finfo.flags&fAttr != 0 { |
|
continue |
|
} |
|
vf := finfo.value(val) |
|
|
|
|
|
switch vf.Kind() { |
|
case reflect.Ptr, reflect.Interface: |
|
if !vf.IsNil() { |
|
vf = vf.Elem() |
|
} |
|
} |
|
|
|
switch finfo.flags & fMode { |
|
case fCharData: |
|
if err := s.setParents(&noField, reflect.Value{}); err != nil { |
|
return err |
|
} |
|
if vf.CanInterface() && vf.Type().Implements(textMarshalerType) { |
|
data, err := vf.Interface().(encoding.TextMarshaler).MarshalText() |
|
if err != nil { |
|
return err |
|
} |
|
Escape(p, data) |
|
continue |
|
} |
|
if vf.CanAddr() { |
|
pv := vf.Addr() |
|
if pv.CanInterface() && pv.Type().Implements(textMarshalerType) { |
|
data, err := pv.Interface().(encoding.TextMarshaler).MarshalText() |
|
if err != nil { |
|
return err |
|
} |
|
Escape(p, data) |
|
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)) |
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
|
Escape(p, strconv.AppendUint(scratch[:0], vf.Uint(), 10)) |
|
case reflect.Float32, reflect.Float64: |
|
Escape(p, strconv.AppendFloat(scratch[:0], vf.Float(), 'g', -1, vf.Type().Bits())) |
|
case reflect.Bool: |
|
Escape(p, strconv.AppendBool(scratch[:0], vf.Bool())) |
|
case reflect.String: |
|
if err := EscapeText(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 { |
|
return err |
|
} |
|
} |
|
} |
|
continue |
|
|
|
case fComment: |
|
if err := s.setParents(&noField, reflect.Value{}); err != nil { |
|
return err |
|
} |
|
k := vf.Kind() |
|
if !(k == reflect.String || k == reflect.Slice && vf.Type().Elem().Kind() == reflect.Uint8) { |
|
return fmt.Errorf("xml: bad type for comment field of %s", val.Type()) |
|
} |
|
if vf.Len() == 0 { |
|
continue |
|
} |
|
p.writeIndent(0) |
|
p.WriteString("<!--") |
|
dashDash := false |
|
dashLast := false |
|
switch k { |
|
case reflect.String: |
|
s := vf.String() |
|
dashDash = strings.Index(s, "--") >= 0 |
|
dashLast = s[len(s)-1] == '-' |
|
if !dashDash { |
|
p.WriteString(s) |
|
} |
|
case reflect.Slice: |
|
b := vf.Bytes() |
|
dashDash = bytes.Index(b, ddBytes) >= 0 |
|
dashLast = b[len(b)-1] == '-' |
|
if !dashDash { |
|
p.Write(b) |
|
} |
|
default: |
|
panic("can't happen") |
|
} |
|
if dashDash { |
|
return fmt.Errorf(`xml: comments must not contain "--"`) |
|
} |
|
if dashLast { |
|
|
|
p.WriteByte(' ') |
|
} |
|
p.WriteString("-->") |
|
continue |
|
|
|
case fInnerXml: |
|
iface := vf.Interface() |
|
switch raw := iface.(type) { |
|
case []byte: |
|
p.Write(raw) |
|
continue |
|
case string: |
|
p.WriteString(raw) |
|
continue |
|
} |
|
|
|
case fElement, fElement | fAny: |
|
if err := s.setParents(finfo, vf); err != nil { |
|
return err |
|
} |
|
} |
|
if err := p.marshalValue(vf, finfo, nil); err != nil { |
|
return err |
|
} |
|
} |
|
if err := s.setParents(&noField, reflect.Value{}); err != nil { |
|
return err |
|
} |
|
return p.cachedWriteError() |
|
} |
|
|
|
var noField fieldInfo |
|
|
|
|
|
func (p *printer) cachedWriteError() error { |
|
_, err := p.Write(nil) |
|
return err |
|
} |
|
|
|
func (p *printer) writeIndent(depthDelta int) { |
|
if len(p.prefix) == 0 && len(p.indent) == 0 { |
|
return |
|
} |
|
if depthDelta < 0 { |
|
p.depth-- |
|
if p.indentedIn { |
|
p.indentedIn = false |
|
return |
|
} |
|
p.indentedIn = false |
|
} |
|
if p.putNewline { |
|
p.WriteByte('\n') |
|
} else { |
|
p.putNewline = true |
|
} |
|
if len(p.prefix) > 0 { |
|
p.WriteString(p.prefix) |
|
} |
|
if len(p.indent) > 0 { |
|
for i := 0; i < p.depth; i++ { |
|
p.WriteString(p.indent) |
|
} |
|
} |
|
if depthDelta > 0 { |
|
p.depth++ |
|
p.indentedIn = true |
|
} |
|
} |
|
|
|
type parentStack struct { |
|
p *printer |
|
xmlns string |
|
parents []string |
|
} |
|
|
|
|
|
|
|
|
|
func (s *parentStack) setParents(finfo *fieldInfo, vf reflect.Value) error { |
|
xmlns := s.p.defaultNS |
|
if finfo.xmlns != "" { |
|
xmlns = finfo.xmlns |
|
} |
|
commonParents := 0 |
|
if xmlns == s.xmlns { |
|
for ; commonParents < len(finfo.parents) && commonParents < len(s.parents); commonParents++ { |
|
if finfo.parents[commonParents] != s.parents[commonParents] { |
|
break |
|
} |
|
} |
|
} |
|
|
|
for i := len(s.parents) - 1; i >= commonParents; i-- { |
|
if err := s.p.writeEnd(Name{ |
|
Space: s.xmlns, |
|
Local: s.parents[i], |
|
}); err != nil { |
|
return err |
|
} |
|
} |
|
s.parents = finfo.parents |
|
s.xmlns = xmlns |
|
if commonParents >= len(s.parents) { |
|
|
|
return nil |
|
} |
|
if (vf.Kind() == reflect.Ptr || vf.Kind() == reflect.Interface) && vf.IsNil() { |
|
|
|
s.parents = s.parents[:commonParents] |
|
return nil |
|
} |
|
|
|
for _, name := range s.parents[commonParents:] { |
|
start := &StartElement{ |
|
Name: Name{ |
|
Space: s.xmlns, |
|
Local: name, |
|
}, |
|
} |
|
|
|
|
|
if s.xmlns != s.p.defaultNS { |
|
start.setDefaultNamespace() |
|
} |
|
if err := s.p.writeStart(start); err != nil { |
|
return err |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
|
|
|
|
type UnsupportedTypeError struct { |
|
Type reflect.Type |
|
} |
|
|
|
func (e *UnsupportedTypeError) Error() string { |
|
return "xml: unsupported type: " + e.Type.String() |
|
} |
|
|
|
func isEmptyValue(v reflect.Value) bool { |
|
switch v.Kind() { |
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.String: |
|
return v.Len() == 0 |
|
case reflect.Bool: |
|
return !v.Bool() |
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
|
return v.Int() == 0 |
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
|
return v.Uint() == 0 |
|
case reflect.Float32, reflect.Float64: |
|
return v.Float() == 0 |
|
case reflect.Interface, reflect.Ptr: |
|
return v.IsNil() |
|
} |
|
return false |
|
} |
|
|