| // Copyright 2011 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package xml |
| |
| import ( |
| "bufio" |
| "io" |
| "os" |
| "reflect" |
| "strconv" |
| "strings" |
| ) |
| |
| const ( |
| // A generic XML header suitable for use with the output of Marshal and MarshalIndent. |
| // This is not automatically added to any output of this package, it is provided as a |
| // convenience. |
| Header = `<?xml version="1.0" encoding="UTF-8">\n` |
| ) |
| |
| // A Marshaler can produce well-formatted XML representing its internal state. |
| // It is used by both Marshal and MarshalIndent. |
| type Marshaler interface { |
| MarshalXML() ([]byte, os.Error) |
| } |
| |
| type printer struct { |
| *bufio.Writer |
| } |
| |
| // Marshal writes an XML-formatted representation of v to w. |
| // |
| // If v implements Marshaler, then Marshal calls its MarshalXML method. |
| // Otherwise, Marshal uses the following procedure to create the XML. |
| // |
| // Marshal handles an array or slice by marshalling each of the elements. |
| // Marshal handles a pointer by marshalling the value it points at or, if the |
| // pointer is nil, by writing nothing. Marshal handles an interface value by |
| // marshalling the value it contains or, if the interface value is nil, by |
| // writing nothing. Marshal handles all other data by writing a single XML |
| // element containing the data. |
| // |
| // The name of that XML element is taken from, in order of preference: |
| // - the tag on an XMLName field, if the data is a struct |
| // - the value of an XMLName field of type xml.Name |
| // - the tag of the struct field used to obtain the data |
| // - the name of the struct field used to obtain the data |
| // - the name '???'. |
| // |
| // The XML element for a struct contains marshalled elements for each of the |
| // exported fields of the struct, with these exceptions: |
| // - the XMLName field, described above, is omitted. |
| // - a field with tag "attr" becomes an attribute in the XML element. |
| // - a field with tag "chardata" is written as character data, |
| // not as an XML element. |
| // - a field with tag "innerxml" is written verbatim, |
| // not subject to the usual marshalling procedure. |
| // |
| // Marshal will return an error if asked to marshal a channel, function, or map. |
| func Marshal(w io.Writer, v interface{}) (err os.Error) { |
| p := &printer{bufio.NewWriter(w)} |
| err = p.marshalValue(reflect.ValueOf(v), "???") |
| p.Flush() |
| return err |
| } |
| |
| func (p *printer) marshalValue(val reflect.Value, name string) os.Error { |
| if !val.IsValid() { |
| return nil |
| } |
| |
| kind := val.Kind() |
| typ := val.Type() |
| |
| // Try Marshaler |
| if typ.NumMethod() > 0 { |
| if marshaler, ok := val.Interface().(Marshaler); ok { |
| bytes, err := marshaler.MarshalXML() |
| if err != nil { |
| return err |
| } |
| p.Write(bytes) |
| return nil |
| } |
| } |
| |
| // Drill into pointers/interfaces |
| if kind == reflect.Ptr || kind == reflect.Interface { |
| if val.IsNil() { |
| return nil |
| } |
| return p.marshalValue(val.Elem(), name) |
| } |
| |
| // Slices and arrays iterate over the elements. They do not have an enclosing tag. |
| 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), name); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| // Find XML name |
| xmlns := "" |
| if kind == reflect.Struct { |
| if f, ok := typ.FieldByName("XMLName"); ok { |
| if tag := f.Tag.Get("xml"); tag != "" { |
| if i := strings.Index(tag, " "); i >= 0 { |
| xmlns, name = tag[:i], tag[i+1:] |
| } else { |
| name = tag |
| } |
| } else if v, ok := val.FieldByIndex(f.Index).Interface().(Name); ok && v.Local != "" { |
| xmlns, name = v.Space, v.Local |
| } |
| } |
| } |
| |
| p.WriteByte('<') |
| p.WriteString(name) |
| |
| // Attributes |
| if kind == reflect.Struct { |
| if len(xmlns) > 0 { |
| p.WriteString(` xmlns="`) |
| Escape(p, []byte(xmlns)) |
| p.WriteByte('"') |
| } |
| |
| for i, n := 0, typ.NumField(); i < n; i++ { |
| if f := typ.Field(i); f.PkgPath == "" && f.Tag.Get("xml") == "attr" { |
| if f.Type.Kind() == reflect.String { |
| if str := val.Field(i).String(); str != "" { |
| p.WriteByte(' ') |
| p.WriteString(strings.ToLower(f.Name)) |
| p.WriteString(`="`) |
| Escape(p, []byte(str)) |
| p.WriteByte('"') |
| } |
| } |
| } |
| } |
| } |
| p.WriteByte('>') |
| |
| switch k := val.Kind(); k { |
| case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
| p.WriteString(strconv.Itoa64(val.Int())) |
| case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
| p.WriteString(strconv.Uitoa64(val.Uint())) |
| case reflect.Float32, reflect.Float64: |
| p.WriteString(strconv.Ftoa64(val.Float(), 'g', -1)) |
| case reflect.String: |
| Escape(p, []byte(val.String())) |
| case reflect.Bool: |
| p.WriteString(strconv.Btoa(val.Bool())) |
| case reflect.Array: |
| // will be [...]byte |
| bytes := make([]byte, val.Len()) |
| for i := range bytes { |
| bytes[i] = val.Index(i).Interface().(byte) |
| } |
| Escape(p, bytes) |
| case reflect.Slice: |
| // will be []byte |
| bytes := val.Interface().([]byte) |
| Escape(p, bytes) |
| case reflect.Struct: |
| for i, n := 0, val.NumField(); i < n; i++ { |
| if f := typ.Field(i); f.Name != "XMLName" && f.PkgPath == "" { |
| name := f.Name |
| switch tag := f.Tag.Get("xml"); tag { |
| case "": |
| case "chardata": |
| if tk := f.Type.Kind(); tk == reflect.String { |
| p.Write([]byte(val.Field(i).String())) |
| } else if tk == reflect.Slice { |
| if elem, ok := val.Field(i).Interface().([]byte); ok { |
| Escape(p, elem) |
| } |
| } |
| continue |
| case "innerxml": |
| iface := val.Field(i).Interface() |
| switch raw := iface.(type) { |
| case []byte: |
| p.Write(raw) |
| continue |
| case string: |
| p.WriteString(raw) |
| continue |
| } |
| case "attr": |
| continue |
| default: |
| name = tag |
| } |
| |
| if err := p.marshalValue(val.Field(i), name); err != nil { |
| return err |
| } |
| } |
| } |
| default: |
| return &UnsupportedTypeError{typ} |
| } |
| |
| p.WriteByte('<') |
| p.WriteByte('/') |
| p.WriteString(name) |
| p.WriteByte('>') |
| |
| return nil |
| } |
| |
| // A MarshalXMLError is returned when Marshal or MarshalIndent encounter a type |
| // that cannot be converted into XML. |
| type UnsupportedTypeError struct { |
| Type reflect.Type |
| } |
| |
| func (e *UnsupportedTypeError) String() string { |
| return "xml: unsupported type: " + e.Type.String() |
| } |