summaryrefslogtreecommitdiff
path: root/src/pkg/xml
diff options
context:
space:
mode:
authorOndřej Surý <ondrej@sury.org>2011-02-14 13:23:51 +0100
committerOndřej Surý <ondrej@sury.org>2011-02-14 13:23:51 +0100
commit758ff64c69e34965f8af5b2d6ffd65e8d7ab2150 (patch)
tree6d6b34f8c678862fe9b56c945a7b63f68502c245 /src/pkg/xml
parent3e45412327a2654a77944249962b3652e6142299 (diff)
downloadgolang-upstream/2011-02-01.1.tar.gz
Imported Upstream version 2011-02-01.1upstream/2011-02-01.1
Diffstat (limited to 'src/pkg/xml')
-rw-r--r--src/pkg/xml/read.go133
-rw-r--r--src/pkg/xml/read_test.go97
-rw-r--r--src/pkg/xml/xml_test.go2
3 files changed, 227 insertions, 5 deletions
diff --git a/src/pkg/xml/read.go b/src/pkg/xml/read.go
index 9175659b2..9ae3bb8ee 100644
--- a/src/pkg/xml/read.go
+++ b/src/pkg/xml/read.go
@@ -6,6 +6,7 @@ package xml
import (
"bytes"
+ "fmt"
"io"
"os"
"reflect"
@@ -39,6 +40,7 @@ import (
// Name string
// Phone string
// Email []Email
+// Groups []string "group>value"
// }
//
// result := Result{Name: "name", Phone: "phone", Email: nil}
@@ -53,6 +55,10 @@ import (
// <addr>gre@work.com</addr>
// </email>
// <name>Grace R. Emlin</name>
+// <group>
+// <value>Friends</value>
+// <value>Squash</value>
+// </group>
// <address>123 Main Street</address>
// </result>
//
@@ -65,10 +71,13 @@ import (
// Email{"home", "gre@example.com"},
// Email{"work", "gre@work.com"},
// },
+// []string{"Friends", "Squash"},
// }
//
// Note that the field r.Phone has not been modified and
-// that the XML <address> element was discarded.
+// that the XML <address> element was discarded. Also, the field
+// 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
@@ -97,6 +106,13 @@ 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 a sub-element whose name matches
+// the prefix of a struct field 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
+// 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",
// Unmarshal maps the sub-element to that struct field.
@@ -104,7 +120,7 @@ import (
// maps the sub-element to that struct field.
//
// Unmarshal maps an XML element to a string or []byte by saving the
-// concatenation of that elements character data in the string or []byte.
+// concatenation of that element's character data in the string or []byte.
//
// Unmarshal maps an XML element to a slice by extending the length
// of the slice and mapping the element to the newly created value.
@@ -141,6 +157,18 @@ type UnmarshalError string
func (e UnmarshalError) String() string { return string(e) }
+// A TagPathError represents an error in the unmarshalling process
+// caused by the use of field tags with conflicting paths.
+type TagPathError struct {
+ Struct reflect.Type
+ Field1, Tag1 string
+ Field2, Tag2 string
+}
+
+func (e *TagPathError) String() string {
+ return fmt.Sprintf("%s field %q with tag %q conflicts with field %q with tag %q", e.Struct, e.Field1, e.Tag1, e.Field2, e.Tag2)
+}
+
// The Parser's Unmarshal method is like xml.Unmarshal
// except that it can be passed a pointer to the initial start element,
// useful when a client reads some raw XML tokens itself
@@ -211,7 +239,9 @@ func (p *Parser) unmarshal(val reflect.Value, start *StartElement) os.Error {
saveXMLData []byte
sv *reflect.StructValue
styp *reflect.StructType
+ fieldPaths map[string]pathInfo
)
+
switch v := val.(type) {
default:
return os.ErrorString("unknown type " + v.Type().String())
@@ -330,6 +360,24 @@ func (p *Parser) unmarshal(val reflect.Value, start *StartElement) os.Error {
saveXMLIndex = p.savedOffset()
}
}
+
+ default:
+ if strings.Contains(f.Tag, ">") {
+ if fieldPaths == nil {
+ fieldPaths = make(map[string]pathInfo)
+ }
+ path := strings.ToLower(f.Tag)
+ if strings.HasPrefix(f.Tag, ">") {
+ path = strings.ToLower(f.Name) + path
+ }
+ if strings.HasSuffix(f.Tag, ">") {
+ path = path[:len(path)-1]
+ }
+ err := addFieldPath(sv, fieldPaths, path, f.Index)
+ if err != nil {
+ return err
+ }
+ }
}
}
}
@@ -352,9 +400,19 @@ Loop:
// Look up by tag name.
if sv != nil {
k := fieldName(t.Name.Local)
+
+ if fieldPaths != nil {
+ if _, found := fieldPaths[k]; found {
+ if err := p.unmarshalPaths(sv, fieldPaths, k, &t); err != nil {
+ return err
+ }
+ continue Loop
+ }
+ }
+
match := func(s string) bool {
// check if the name matches ignoring case
- if strings.ToLower(s) != strings.ToLower(k) {
+ if strings.ToLower(s) != k {
return false
}
// now check that it's public
@@ -470,6 +528,75 @@ Loop:
return nil
}
+type pathInfo struct {
+ fieldIdx []int
+ complete bool
+}
+
+// addFieldPath takes an element path such as "a>b>c" and fills the
+// paths map with all paths leading to it ("a", "a>b", and "a>b>c").
+// It is okay for paths to share a common, shorter prefix but not ok
+// for one path to itself be a prefix of another.
+func addFieldPath(sv *reflect.StructValue, paths map[string]pathInfo, path string, fieldIdx []int) os.Error {
+ if info, found := paths[path]; found {
+ return tagError(sv, info.fieldIdx, fieldIdx)
+ }
+ paths[path] = pathInfo{fieldIdx, true}
+ for {
+ i := strings.LastIndex(path, ">")
+ if i < 0 {
+ break
+ }
+ path = path[:i]
+ if info, found := paths[path]; found {
+ if info.complete {
+ return tagError(sv, info.fieldIdx, fieldIdx)
+ }
+ } else {
+ paths[path] = pathInfo{fieldIdx, false}
+ }
+ }
+ return nil
+
+}
+
+func tagError(sv *reflect.StructValue, idx1 []int, idx2 []int) os.Error {
+ t := sv.Type().(*reflect.StructType)
+ f1 := t.FieldByIndex(idx1)
+ f2 := t.FieldByIndex(idx2)
+ return &TagPathError{t, f1.Name, f1.Tag, f2.Name, f2.Tag}
+}
+
+// unmarshalPaths walks down an XML structure looking for
+// wanted paths, and calls unmarshal on them.
+func (p *Parser) unmarshalPaths(sv *reflect.StructValue, paths map[string]pathInfo, path string, start *StartElement) os.Error {
+ if info, _ := paths[path]; info.complete {
+ return p.unmarshal(sv.FieldByIndex(info.fieldIdx), start)
+ }
+ for {
+ tok, err := p.Token()
+ if err != nil {
+ return err
+ }
+ switch t := tok.(type) {
+ case StartElement:
+ k := path + ">" + fieldName(t.Name.Local)
+ if _, found := paths[k]; found {
+ if err := p.unmarshalPaths(sv, paths, k, &t); err != nil {
+ return err
+ }
+ continue
+ }
+ if err := p.Skip(); err != nil {
+ return err
+ }
+ case EndElement:
+ return nil
+ }
+ }
+ panic("unreachable")
+}
+
// Have already read a start element.
// Read tokens until we find the end element.
// Token is taking care of making sure the
diff --git a/src/pkg/xml/read_test.go b/src/pkg/xml/read_test.go
index 9ec1065c2..71ceddce4 100644
--- a/src/pkg/xml/read_test.go
+++ b/src/pkg/xml/read_test.go
@@ -230,3 +230,100 @@ func TestFieldName(t *testing.T) {
}
}
}
+
+const pathTestString = `
+<result>
+ <before>1</before>
+ <items>
+ <item1>
+ <value>A</value>
+ </item1>
+ <item2>
+ <value>B</value>
+ </item2>
+ <Item1>
+ <Value>C</Value>
+ <Value>D</Value>
+ </Item1>
+ </items>
+ <after>2</after>
+</result>
+`
+
+type PathTestItem struct {
+ Value string
+}
+
+type PathTestA struct {
+ Items []PathTestItem ">item1"
+ Before, After string
+}
+
+type PathTestB struct {
+ Other []PathTestItem "items>Item1"
+ Before, After string
+}
+
+type PathTestC struct {
+ Values1 []string "items>item1>value"
+ Values2 []string "items>item2>value"
+ Before, After string
+}
+
+type PathTestSet struct {
+ Item1 []PathTestItem
+}
+
+type PathTestD struct {
+ Other PathTestSet "items>"
+ Before, After string
+}
+
+var pathTests = []interface{}{
+ &PathTestA{Items: []PathTestItem{{"A"}, {"D"}}, Before: "1", After: "2"},
+ &PathTestB{Other: []PathTestItem{{"A"}, {"D"}}, Before: "1", After: "2"},
+ &PathTestC{Values1: []string{"A", "C", "D"}, Values2: []string{"B"}, Before: "1", After: "2"},
+ &PathTestD{Other: PathTestSet{Item1: []PathTestItem{{"A"}, {"D"}}}, Before: "1", After: "2"},
+}
+
+func TestUnmarshalPaths(t *testing.T) {
+ for _, pt := range pathTests {
+ p := reflect.MakeZero(reflect.NewValue(pt).Type()).(*reflect.PtrValue)
+ p.PointTo(reflect.MakeZero(p.Type().(*reflect.PtrType).Elem()))
+ v := p.Interface()
+ if err := Unmarshal(StringReader(pathTestString), v); err != nil {
+ t.Fatalf("Unmarshal: %s", err)
+ }
+ if !reflect.DeepEqual(v, pt) {
+ t.Fatalf("have %#v\nwant %#v", v, pt)
+ }
+ }
+}
+
+type BadPathTestA struct {
+ First string "items>item1"
+ Other string "items>item2"
+ Second string "items>"
+}
+
+type BadPathTestB struct {
+ Other string "items>item2>value"
+ First string "items>item1"
+ Second string "items>item1>value"
+}
+
+var badPathTests = []struct {
+ v, e interface{}
+}{
+ {&BadPathTestA{}, &TagPathError{reflect.Typeof(BadPathTestA{}), "First", "items>item1", "Second", "items>"}},
+ {&BadPathTestB{}, &TagPathError{reflect.Typeof(BadPathTestB{}), "First", "items>item1", "Second", "items>item1>value"}},
+}
+
+func TestUnmarshalBadPaths(t *testing.T) {
+ for _, tt := range badPathTests {
+ err := Unmarshal(StringReader(pathTestString), tt.v)
+ if !reflect.DeepEqual(err, tt.e) {
+ t.Fatalf("Unmarshal with %#v didn't fail properly: %#v", tt.v, err)
+ }
+ }
+}
diff --git a/src/pkg/xml/xml_test.go b/src/pkg/xml/xml_test.go
index 9ab199a30..317ecabd9 100644
--- a/src/pkg/xml/xml_test.go
+++ b/src/pkg/xml/xml_test.go
@@ -227,7 +227,6 @@ type allScalars struct {
Uint32 uint32
Uint64 uint64
Uintptr uintptr
- Float float
Float32 float32
Float64 float64
String string
@@ -249,7 +248,6 @@ var all = allScalars{
Uint32: 9,
Uint64: 10,
Uintptr: 11,
- Float: 12.0,
Float32: 13.0,
Float64: 14.0,
String: "15",