diff options
author | Ondřej Surý <ondrej@sury.org> | 2011-08-03 16:54:30 +0200 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2011-08-03 16:54:30 +0200 |
commit | 28592ee1ea1f5cdffcf85472f9de0285d928cf12 (patch) | |
tree | 32944e18b23f7fe4a0818a694aa2a6dfb1835463 /src/pkg/xml | |
parent | e836bee4716dc0d4d913537ad3ad1925a7ac32d0 (diff) | |
download | golang-upstream/59.tar.gz |
Imported Upstream version 59upstream/59
Diffstat (limited to 'src/pkg/xml')
-rw-r--r-- | src/pkg/xml/Makefile | 1 | ||||
-rw-r--r-- | src/pkg/xml/atom_test.go | 50 | ||||
-rw-r--r-- | src/pkg/xml/embed_test.go | 10 | ||||
-rw-r--r-- | src/pkg/xml/marshal.go | 228 | ||||
-rw-r--r-- | src/pkg/xml/marshal_test.go | 299 | ||||
-rw-r--r-- | src/pkg/xml/read.go | 128 | ||||
-rw-r--r-- | src/pkg/xml/read_test.go | 78 | ||||
-rw-r--r-- | src/pkg/xml/xml.go | 23 | ||||
-rw-r--r-- | src/pkg/xml/xml_test.go | 27 |
9 files changed, 756 insertions, 88 deletions
diff --git a/src/pkg/xml/Makefile b/src/pkg/xml/Makefile index b780face6..d66c4988a 100644 --- a/src/pkg/xml/Makefile +++ b/src/pkg/xml/Makefile @@ -7,6 +7,7 @@ include ../../Make.inc TARG=xml GOFILES=\ + marshal.go\ read.go\ xml.go\ diff --git a/src/pkg/xml/atom_test.go b/src/pkg/xml/atom_test.go new file mode 100644 index 000000000..d365510bf --- /dev/null +++ b/src/pkg/xml/atom_test.go @@ -0,0 +1,50 @@ +// 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 + +var atomValue = &Feed{ + Title: "Example Feed", + Link: []Link{{Href: "http://example.org/"}}, + Updated: ParseTime("2003-12-13T18:30:02Z"), + Author: Person{Name: "John Doe"}, + Id: "urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6", + + Entry: []Entry{ + { + Title: "Atom-Powered Robots Run Amok", + Link: []Link{{Href: "http://example.org/2003/12/13/atom03"}}, + Id: "urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a", + Updated: ParseTime("2003-12-13T18:30:02Z"), + Summary: NewText("Some text."), + }, + }, +} + +var atomXml = `` + + `<feed xmlns="http://www.w3.org/2005/Atom">` + + `<Title>Example Feed</Title>` + + `<Id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</Id>` + + `<Link href="http://example.org/"></Link>` + + `<Updated>2003-12-13T18:30:02Z</Updated>` + + `<Author><Name>John Doe</Name><URI></URI><Email></Email></Author>` + + `<Entry>` + + `<Title>Atom-Powered Robots Run Amok</Title>` + + `<Id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</Id>` + + `<Link href="http://example.org/2003/12/13/atom03"></Link>` + + `<Updated>2003-12-13T18:30:02Z</Updated>` + + `<Author><Name></Name><URI></URI><Email></Email></Author>` + + `<Summary>Some text.</Summary>` + + `</Entry>` + + `</feed>` + +func ParseTime(str string) Time { + return Time(str) +} + +func NewText(text string) Text { + return Text{ + Body: text, + } +} diff --git a/src/pkg/xml/embed_test.go b/src/pkg/xml/embed_test.go index abfe781ac..ec7f478be 100644 --- a/src/pkg/xml/embed_test.go +++ b/src/pkg/xml/embed_test.go @@ -12,14 +12,14 @@ type C struct { } type A struct { - XMLName Name "http://domain a" + XMLName Name `xml:"http://domain a"` C B B FieldA string } type B struct { - XMLName Name "b" + XMLName Name `xml:"b"` C FieldB string } @@ -65,7 +65,7 @@ func TestEmbedded1(t *testing.T) { } type A2 struct { - XMLName Name "http://domain a" + XMLName Name `xml:"http://domain a"` XY string Xy string } @@ -92,7 +92,7 @@ func TestEmbedded2(t *testing.T) { } type A3 struct { - XMLName Name "http://domain a" + XMLName Name `xml:"http://domain a"` xy string } @@ -108,7 +108,7 @@ func TestEmbedded3(t *testing.T) { } type A4 struct { - XMLName Name "http://domain a" + XMLName Name `xml:"http://domain a"` Any string } diff --git a/src/pkg/xml/marshal.go b/src/pkg/xml/marshal.go new file mode 100644 index 000000000..2ac03a91e --- /dev/null +++ b/src/pkg/xml/marshal.go @@ -0,0 +1,228 @@ +// 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() +} diff --git a/src/pkg/xml/marshal_test.go b/src/pkg/xml/marshal_test.go new file mode 100644 index 000000000..77b2e726d --- /dev/null +++ b/src/pkg/xml/marshal_test.go @@ -0,0 +1,299 @@ +// 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 ( + "reflect" + "testing" + + "os" + "bytes" + "strings" + "strconv" +) + +type DriveType int + +const ( + HyperDrive DriveType = iota + ImprobabilityDrive +) + +type Passenger struct { + Name []string `xml:"name"` + Weight float32 `xml:"weight"` +} + +type Ship struct { + XMLName Name `xml:"spaceship"` + + Name string `xml:"attr"` + Pilot string `xml:"attr"` + Drive DriveType `xml:"drive"` + Age uint `xml:"age"` + Passenger []*Passenger `xml:"passenger"` + secret string +} + +type RawXML string + +func (rx RawXML) MarshalXML() ([]byte, os.Error) { + return []byte(rx), nil +} + +type NamedType string + +type Port struct { + XMLName Name `xml:"port"` + Type string `xml:"attr"` + Number string `xml:"chardata"` +} + +type Domain struct { + XMLName Name `xml:"domain"` + Country string `xml:"attr"` + Name []byte `xml:"chardata"` +} + +type SecretAgent struct { + XMLName Name `xml:"agent"` + Handle string `xml:"attr"` + Identity string + Obfuscate string `xml:"innerxml"` +} + +var nilStruct *Ship + +var marshalTests = []struct { + Value interface{} + ExpectXML string +}{ + // Test nil marshals to nothing + {Value: nil, ExpectXML: ``}, + {Value: nilStruct, ExpectXML: ``}, + + // Test value types (no tag name, so ???) + {Value: true, ExpectXML: `<???>true</???>`}, + {Value: int(42), ExpectXML: `<???>42</???>`}, + {Value: int8(42), ExpectXML: `<???>42</???>`}, + {Value: int16(42), ExpectXML: `<???>42</???>`}, + {Value: int32(42), ExpectXML: `<???>42</???>`}, + {Value: uint(42), ExpectXML: `<???>42</???>`}, + {Value: uint8(42), ExpectXML: `<???>42</???>`}, + {Value: uint16(42), ExpectXML: `<???>42</???>`}, + {Value: uint32(42), ExpectXML: `<???>42</???>`}, + {Value: float32(1.25), ExpectXML: `<???>1.25</???>`}, + {Value: float64(1.25), ExpectXML: `<???>1.25</???>`}, + {Value: uintptr(0xFFDD), ExpectXML: `<???>65501</???>`}, + {Value: "gopher", ExpectXML: `<???>gopher</???>`}, + {Value: []byte("gopher"), ExpectXML: `<???>gopher</???>`}, + {Value: "</>", ExpectXML: `<???></></???>`}, + {Value: []byte("</>"), ExpectXML: `<???></></???>`}, + {Value: [3]byte{'<', '/', '>'}, ExpectXML: `<???></></???>`}, + {Value: NamedType("potato"), ExpectXML: `<???>potato</???>`}, + {Value: []int{1, 2, 3}, ExpectXML: `<???>1</???><???>2</???><???>3</???>`}, + {Value: [3]int{1, 2, 3}, ExpectXML: `<???>1</???><???>2</???><???>3</???>`}, + + // Test innerxml + {Value: RawXML("</>"), ExpectXML: `</>`}, + { + Value: &SecretAgent{ + Handle: "007", + Identity: "James Bond", + Obfuscate: "<redacted/>", + }, + //ExpectXML: `<agent handle="007"><redacted/></agent>`, + ExpectXML: `<agent handle="007"><Identity>James Bond</Identity><redacted/></agent>`, + }, + + // Test structs + {Value: &Port{Type: "ssl", Number: "443"}, ExpectXML: `<port type="ssl">443</port>`}, + {Value: &Port{Number: "443"}, ExpectXML: `<port>443</port>`}, + {Value: &Port{Type: "<unix>"}, ExpectXML: `<port type="<unix>"></port>`}, + {Value: &Domain{Name: []byte("google.com&friends")}, ExpectXML: `<domain>google.com&friends</domain>`}, + {Value: atomValue, ExpectXML: atomXml}, + { + Value: &Ship{ + Name: "Heart of Gold", + Pilot: "Computer", + Age: 1, + Drive: ImprobabilityDrive, + Passenger: []*Passenger{ + &Passenger{ + Name: []string{"Zaphod", "Beeblebrox"}, + Weight: 7.25, + }, + &Passenger{ + Name: []string{"Trisha", "McMillen"}, + Weight: 5.5, + }, + &Passenger{ + Name: []string{"Ford", "Prefect"}, + Weight: 7, + }, + &Passenger{ + Name: []string{"Arthur", "Dent"}, + Weight: 6.75, + }, + }, + }, + ExpectXML: `<spaceship name="Heart of Gold" pilot="Computer">` + + `<drive>` + strconv.Itoa(int(ImprobabilityDrive)) + `</drive>` + + `<age>1</age>` + + `<passenger>` + + `<name>Zaphod</name>` + + `<name>Beeblebrox</name>` + + `<weight>7.25</weight>` + + `</passenger>` + + `<passenger>` + + `<name>Trisha</name>` + + `<name>McMillen</name>` + + `<weight>5.5</weight>` + + `</passenger>` + + `<passenger>` + + `<name>Ford</name>` + + `<name>Prefect</name>` + + `<weight>7</weight>` + + `</passenger>` + + `<passenger>` + + `<name>Arthur</name>` + + `<name>Dent</name>` + + `<weight>6.75</weight>` + + `</passenger>` + + `</spaceship>`, + }, +} + +func TestMarshal(t *testing.T) { + for idx, test := range marshalTests { + buf := bytes.NewBuffer(nil) + err := Marshal(buf, test.Value) + if err != nil { + t.Errorf("#%d: Error: %s", idx, err) + continue + } + if got, want := buf.String(), test.ExpectXML; got != want { + if strings.Contains(want, "\n") { + t.Errorf("#%d: marshal(%#v) - GOT:\n%s\nWANT:\n%s", idx, test.Value, got, want) + } else { + t.Errorf("#%d: marshal(%#v) = %#q want %#q", idx, test.Value, got, want) + } + } + } +} + +var marshalErrorTests = []struct { + Value interface{} + ExpectErr string + ExpectKind reflect.Kind +}{ + { + Value: make(chan bool), + ExpectErr: "xml: unsupported type: chan bool", + ExpectKind: reflect.Chan, + }, + { + Value: map[string]string{ + "question": "What do you get when you multiply six by nine?", + "answer": "42", + }, + ExpectErr: "xml: unsupported type: map[string] string", + ExpectKind: reflect.Map, + }, + { + Value: map[*Ship]bool{nil: false}, + ExpectErr: "xml: unsupported type: map[*xml.Ship] bool", + ExpectKind: reflect.Map, + }, +} + +func TestMarshalErrors(t *testing.T) { + for idx, test := range marshalErrorTests { + buf := bytes.NewBuffer(nil) + err := Marshal(buf, test.Value) + if got, want := err, test.ExpectErr; got == nil { + t.Errorf("#%d: want error %s", idx, want) + continue + } else if got.String() != want { + t.Errorf("#%d: marshal(%#v) = [error] %q, want %q", idx, test.Value, got, want) + } + if got, want := err.(*UnsupportedTypeError).Type.Kind(), test.ExpectKind; got != want { + t.Errorf("#%d: marshal(%#v) = [error kind] %s, want %s", idx, test.Value, got, want) + } + } +} + +// Do invertibility testing on the various structures that we test +func TestUnmarshal(t *testing.T) { + for i, test := range marshalTests { + // Skip the nil pointers + if i <= 1 { + continue + } + + var dest interface{} + + switch test.Value.(type) { + case *Ship, Ship: + dest = &Ship{} + case *Port, Port: + dest = &Port{} + case *Domain, Domain: + dest = &Domain{} + case *Feed, Feed: + dest = &Feed{} + default: + continue + } + + buffer := bytes.NewBufferString(test.ExpectXML) + err := Unmarshal(buffer, dest) + + // Don't compare XMLNames + switch fix := dest.(type) { + case *Ship: + fix.XMLName = Name{} + case *Port: + fix.XMLName = Name{} + case *Domain: + fix.XMLName = Name{} + case *Feed: + fix.XMLName = Name{} + fix.Author.InnerXML = "" + for i := range fix.Entry { + fix.Entry[i].Author.InnerXML = "" + } + } + + if err != nil { + t.Errorf("#%d: unexpected error: %#v", i, err) + } else if got, want := dest, test.Value; !reflect.DeepEqual(got, want) { + t.Errorf("#%d: unmarshal(%#s) = %#v, want %#v", i, test.ExpectXML, got, want) + } + } +} + +func BenchmarkMarshal(b *testing.B) { + idx := len(marshalTests) - 1 + test := marshalTests[idx] + + buf := bytes.NewBuffer(nil) + for i := 0; i < b.N; i++ { + Marshal(buf, test.Value) + buf.Truncate(0) + } +} + +func BenchmarkUnmarshal(b *testing.B) { + idx := len(marshalTests) - 1 + test := marshalTests[idx] + sm := &Ship{} + xml := []byte(test.ExpectXML) + + for i := 0; i < b.N; i++ { + buffer := bytes.NewBuffer(xml) + Unmarshal(buffer, sm) + } +} diff --git a/src/pkg/xml/read.go b/src/pkg/xml/read.go index e2b349c3f..786b69f5a 100644 --- a/src/pkg/xml/read.go +++ b/src/pkg/xml/read.go @@ -31,16 +31,16 @@ import ( // For example, given these definitions: // // type Email struct { -// Where string "attr" +// Where string `xml:"attr"` // Addr string // } // // type Result struct { -// XMLName xml.Name "result" +// XMLName xml.Name `xml:"result"` // Name string // Phone string // Email []Email -// Groups []string "group>value" +// Groups []string `xml:"group>value"` // } // // result := Result{Name: "name", Phone: "phone", Email: nil} @@ -79,11 +79,13 @@ import ( // Groups was assigned considering the element path provided in the // field tag. // -// Because Unmarshal uses the reflect package, it can only -// assign to upper case fields. Unmarshal uses a case-insensitive +// Because Unmarshal uses the reflect package, it can only assign +// to exported (upper case) fields. Unmarshal uses a case-insensitive // comparison to match XML element names to struct field names. // -// Unmarshal maps an XML element to a struct using the following rules: +// Unmarshal maps an XML element to a struct using the following rules. +// In the rules, the tag of a field refers to the value associated with the +// key 'xml' in the struct field's tag (see the example above). // // * If the struct has a field of type []byte or string with tag "innerxml", // Unmarshal accumulates the raw XML nested inside the element @@ -92,9 +94,9 @@ import ( // * If the struct has a field named XMLName of type xml.Name, // Unmarshal records the element name in that field. // -// * If the XMLName field has an associated tag string of the form -// "tag" or "namespace-URL tag", the XML element must have -// the given tag (and, optionally, name space) or else Unmarshal +// * If the XMLName field has an associated tag of the form +// "name" or "namespace-URL name", the XML element must have +// the given name (and, optionally, name space) or else Unmarshal // returns an error. // // * If the XML element has an attribute whose name matches a @@ -106,31 +108,41 @@ import ( // The struct field may have type []byte or string. // If there is no such field, the character data is discarded. // +// * If the XML element contains comments, they are accumulated in +// the first struct field that has tag "comments". The struct +// field may have type []byte or string. If there is no such +// field, the comments are discarded. +// // * If the XML element contains a sub-element whose name matches -// the prefix of a struct field tag formatted as "a>b>c", unmarshal +// the prefix of a tag formatted as "a>b>c", unmarshal // will descend into the XML structure looking for elements with the // given names, and will map the innermost elements to that struct field. -// A struct field tag starting with ">" is equivalent to one starting +// A tag starting with ">" is equivalent to one starting // with the field name followed by ">". // // * If the XML element contains a sub-element whose name -// matches a struct field whose tag is neither "attr" nor "chardata", +// matches a field whose tag is neither "attr" nor "chardata", // Unmarshal maps the sub-element to that struct field. // Otherwise, if the struct has a field named Any, unmarshal // maps the sub-element to that struct field. // // Unmarshal maps an XML element to a string or []byte by saving the -// concatenation of that element's character data in the string or []byte. +// concatenation of that element's character data in the string or +// []byte. +// +// Unmarshal maps an attribute value to a string or []byte by saving +// the value in the string or slice. // -// Unmarshal maps an XML element to a slice by extending the length -// of the slice and mapping the element to the newly created value. +// Unmarshal maps an XML element to a slice by extending the length of +// the slice and mapping the element to the newly created value. // -// Unmarshal maps an XML element to a bool by setting it to the boolean -// value represented by the string. +// Unmarshal maps an XML element or attribute value to a bool by +// setting it to the boolean value represented by the string. // -// Unmarshal maps an XML element to an integer or floating-point -// field by setting the field to the result of interpreting the string -// value in decimal. There is no check for overflow. +// Unmarshal maps an XML element or attribute value to an integer or +// floating-point field by setting the field to the result of +// interpreting the string value in decimal. There is no check for +// overflow. // // Unmarshal maps an XML element to an xml.Name by recording the // element name. @@ -241,7 +253,7 @@ func (p *Parser) unmarshal(val reflect.Value, start *StartElement) os.Error { switch v := val; v.Kind() { default: - return os.ErrorString("unknown type " + v.Type().String()) + return os.NewError("unknown type " + v.Type().String()) case reflect.Slice: typ := v.Type() @@ -287,8 +299,7 @@ func (p *Parser) unmarshal(val reflect.Value, start *StartElement) os.Error { // Assign name. if f, ok := typ.FieldByName("XMLName"); ok { // Validate element name. - if f.Tag != "" { - tag := f.Tag + if tag := f.Tag.Get("xml"); tag != "" { ns := "" i := strings.LastIndex(tag, " ") if i >= 0 { @@ -320,12 +331,9 @@ func (p *Parser) unmarshal(val reflect.Value, start *StartElement) os.Error { // Also, determine whether we need to save character data or comments. for i, n := 0, typ.NumField(); i < n; i++ { f := typ.Field(i) - switch f.Tag { + switch f.Tag.Get("xml") { case "attr": strv := sv.FieldByIndex(f.Index) - if strv.Kind() != reflect.String { - return UnmarshalError(sv.Type().String() + " field " + f.Name + " has attr tag but is not type string") - } // Look for attribute. val := "" k := strings.ToLower(f.Name) @@ -335,7 +343,7 @@ func (p *Parser) unmarshal(val reflect.Value, start *StartElement) os.Error { break } } - strv.SetString(val) + copyValue(strv, []byte(val)) case "comment": if !saveComment.IsValid() { @@ -359,15 +367,15 @@ func (p *Parser) unmarshal(val reflect.Value, start *StartElement) os.Error { } default: - if strings.Contains(f.Tag, ">") { + if tag := f.Tag.Get("xml"); strings.Contains(tag, ">") { if fieldPaths == nil { fieldPaths = make(map[string]pathInfo) } - path := strings.ToLower(f.Tag) - if strings.HasPrefix(f.Tag, ">") { + path := strings.ToLower(tag) + if strings.HasPrefix(tag, ">") { path = strings.ToLower(f.Name) + path } - if strings.HasSuffix(f.Tag, ">") { + if strings.HasSuffix(tag, ">") { path = path[:len(path)-1] } err := addFieldPath(sv, fieldPaths, path, f.Index) @@ -454,33 +462,54 @@ Loop: } } - var err os.Error + if err := copyValue(saveData, data); err != nil { + return err + } + + switch t := saveComment; t.Kind() { + case reflect.String: + t.SetString(string(comment)) + case reflect.Slice: + t.Set(reflect.ValueOf(comment)) + } + + switch t := saveXML; t.Kind() { + case reflect.String: + t.SetString(string(saveXMLData)) + case reflect.Slice: + t.Set(reflect.ValueOf(saveXMLData)) + } + + return nil +} + +func copyValue(dst reflect.Value, src []byte) (err os.Error) { // Helper functions for integer and unsigned integer conversions var itmp int64 getInt64 := func() bool { - itmp, err = strconv.Atoi64(string(data)) + itmp, err = strconv.Atoi64(string(src)) // TODO: should check sizes return err == nil } var utmp uint64 getUint64 := func() bool { - utmp, err = strconv.Atoui64(string(data)) + utmp, err = strconv.Atoui64(string(src)) // TODO: check for overflow? return err == nil } var ftmp float64 getFloat64 := func() bool { - ftmp, err = strconv.Atof64(string(data)) + ftmp, err = strconv.Atof64(string(src)) // TODO: check for overflow? return err == nil } // Save accumulated data and comments - switch t := saveData; t.Kind() { + switch t := dst; t.Kind() { case reflect.Invalid: // Probably a comment, handled below default: - return os.ErrorString("cannot happen: unknown type " + t.Type().String()) + return os.NewError("cannot happen: unknown type " + t.Type().String()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if !getInt64() { return err @@ -497,31 +526,16 @@ Loop: } t.SetFloat(ftmp) case reflect.Bool: - value, err := strconv.Atob(strings.TrimSpace(string(data))) + value, err := strconv.Atob(strings.TrimSpace(string(src))) if err != nil { return err } t.SetBool(value) case reflect.String: - t.SetString(string(data)) + t.SetString(string(src)) case reflect.Slice: - t.Set(reflect.ValueOf(data)) + t.Set(reflect.ValueOf(src)) } - - switch t := saveComment; t.Kind() { - case reflect.String: - t.SetString(string(comment)) - case reflect.Slice: - t.Set(reflect.ValueOf(comment)) - } - - switch t := saveXML; t.Kind() { - case reflect.String: - t.SetString(string(saveXMLData)) - case reflect.Slice: - t.Set(reflect.ValueOf(saveXMLData)) - } - return nil } @@ -561,7 +575,7 @@ func tagError(sv reflect.Value, idx1 []int, idx2 []int) os.Error { t := sv.Type() f1 := t.FieldByIndex(idx1) f2 := t.FieldByIndex(idx2) - return &TagPathError{t, f1.Name, f1.Tag, f2.Name, f2.Tag} + return &TagPathError{t, f1.Name, f1.Tag.Get("xml"), f2.Name, f2.Tag.Get("xml")} } // unmarshalPaths walks down an XML structure looking for diff --git a/src/pkg/xml/read_test.go b/src/pkg/xml/read_test.go index d4ae3700d..2126da3c7 100644 --- a/src/pkg/xml/read_test.go +++ b/src/pkg/xml/read_test.go @@ -78,7 +78,7 @@ not being used from outside intra_region_diff.py. </summary></entry></feed> ` type Feed struct { - XMLName Name "http://www.w3.org/2005/Atom feed" + XMLName Name `xml:"http://www.w3.org/2005/Atom feed"` Title string Id string Link []Link @@ -97,20 +97,20 @@ type Entry struct { } type Link struct { - Rel string "attr" - Href string "attr" + Rel string `xml:"attr"` + Href string `xml:"attr"` } type Person struct { Name string URI string Email string - InnerXML string "innerxml" + InnerXML string `xml:"innerxml"` } type Text struct { - Type string "attr" - Body string "chardata" + Type string `xml:"attr"` + Body string `xml:"chardata"` } type Time string @@ -255,18 +255,18 @@ type PathTestItem struct { } type PathTestA struct { - Items []PathTestItem ">item1" + Items []PathTestItem `xml:">item1"` Before, After string } type PathTestB struct { - Other []PathTestItem "items>Item1" + Other []PathTestItem `xml:"items>Item1"` Before, After string } type PathTestC struct { - Values1 []string "items>item1>value" - Values2 []string "items>item2>value" + Values1 []string `xml:"items>item1>value"` + Values2 []string `xml:"items>item2>value"` Before, After string } @@ -275,7 +275,7 @@ type PathTestSet struct { } type PathTestD struct { - Other PathTestSet "items>" + Other PathTestSet `xml:"items>"` Before, After string } @@ -299,15 +299,15 @@ func TestUnmarshalPaths(t *testing.T) { } type BadPathTestA struct { - First string "items>item1" - Other string "items>item2" - Second string "items>" + First string `xml:"items>item1"` + Other string `xml:"items>item2"` + Second string `xml:"items>"` } type BadPathTestB struct { - Other string "items>item2>value" - First string "items>item1" - Second string "items>item1>value" + Other string `xml:"items>item2>value"` + First string `xml:"items>item1"` + Second string `xml:"items>item1>value"` } var badPathTests = []struct { @@ -325,3 +325,47 @@ func TestUnmarshalBadPaths(t *testing.T) { } } } + +func TestUnmarshalAttrs(t *testing.T) { + var f AttrTest + if err := Unmarshal(StringReader(attrString), &f); err != nil { + t.Fatalf("Unmarshal: %s", err) + } + if !reflect.DeepEqual(f, attrStruct) { + t.Fatalf("have %#v\nwant %#v", f, attrStruct) + } +} + +type AttrTest struct { + Test1 Test1 + Test2 Test2 +} + +type Test1 struct { + Int int `xml:"attr"` + Float float64 `xml:"attr"` + Uint8 uint8 `xml:"attr"` +} + +type Test2 struct { + Bool bool `xml:"attr"` +} + +const attrString = ` +<?xml version="1.0" charset="utf-8"?> +<attrtest> + <test1 int="8" float="23.5" uint8="255"/> + <test2 bool="true"/> +</attrtest> +` + +var attrStruct = AttrTest{ + Test1: Test1{ + Int: 8, + Float: 23.5, + Uint8: 255, + }, + Test2: Test2{ + Bool: true, + }, +} diff --git a/src/pkg/xml/xml.go b/src/pkg/xml/xml.go index 2cebbce75..e5d73dd02 100644 --- a/src/pkg/xml/xml.go +++ b/src/pkg/xml/xml.go @@ -659,17 +659,22 @@ func (p *Parser) RawToken() (Token, os.Error) { return nil, p.err } if b != '=' { - p.err = p.syntaxError("attribute name without = in element") - return nil, p.err - } - p.space() - data := p.attrval() - if data == nil { - return nil, p.err + if p.Strict { + p.err = p.syntaxError("attribute name without = in element") + return nil, p.err + } else { + p.ungetc(b) + a.Value = a.Name.Local + } + } else { + p.space() + data := p.attrval() + if data == nil { + return nil, p.err + } + a.Value = string(data) } - a.Value = string(data) } - if empty { p.needClose = true p.toClose = name diff --git a/src/pkg/xml/xml_test.go b/src/pkg/xml/xml_test.go index 4e51cd53a..aba21a2b4 100644 --- a/src/pkg/xml/xml_test.go +++ b/src/pkg/xml/xml_test.go @@ -445,6 +445,33 @@ func TestUnquotedAttrs(t *testing.T) { } } +func TestValuelessAttrs(t *testing.T) { + tests := [][3]string{ + {"<p nowrap>", "p", "nowrap"}, + {"<p nowrap >", "p", "nowrap"}, + {"<input checked/>", "input", "checked"}, + {"<input checked />", "input", "checked"}, + } + for _, test := range tests { + p := NewParser(StringReader(test[0])) + p.Strict = false + token, err := p.Token() + if _, ok := err.(*SyntaxError); ok { + t.Errorf("Unexpected error: %v", err) + } + if token.(StartElement).Name.Local != test[1] { + t.Errorf("Unexpected tag name: %v", token.(StartElement).Name.Local) + } + attr := token.(StartElement).Attr[0] + if attr.Value != test[2] { + t.Errorf("Unexpected attribute value: %v", attr.Value) + } + if attr.Name.Local != test[2] { + t.Errorf("Unexpected attribute name: %v", attr.Name.Local) + } + } +} + func TestCopyTokenCharData(t *testing.T) { data := []byte("same data") var tok1 Token = CharData(data) |