read.go 16.4 KB
Newer Older
Russ Cox's avatar
Russ Cox committed
1 2 3 4 5 6 7
// Copyright 2009 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 (
8
	"bytes"
9
	"fmt"
10 11 12
	"io"
	"os"
	"reflect"
13
	"strconv"
14 15
	"strings"
	"unicode"
Raif S. Naffah's avatar
Raif S. Naffah committed
16
	"utf8"
Russ Cox's avatar
Russ Cox committed
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
)

// BUG(rsc): Mapping between XML elements and data structures is inherently flawed:
// an XML element is an order-dependent collection of anonymous
// values, while a data structure is an order-independent collection
// of named values.
// See package json for a textual representation more suitable
// to data structures.

// Unmarshal parses an XML element from r and uses the
// reflect library to fill in an arbitrary struct, slice, or string
// pointed at by val.  Well-formed data that does not fit
// into val is discarded.
//
// For example, given these definitions:
//
//	type Email struct {
34
//		Where string `xml:"attr"`
35
//		Addr  string
Russ Cox's avatar
Russ Cox committed
36 37 38
//	}
//
//	type Result struct {
39
//		XMLName xml.Name `xml:"result"`
40 41 42
//		Name	string
//		Phone	string
//		Email	[]Email
43
//		Groups  []string `xml:"group>value"`
Russ Cox's avatar
Russ Cox committed
44 45
//	}
//
46
//	result := Result{Name: "name", Phone: "phone", Email: nil}
Russ Cox's avatar
Russ Cox committed
47 48 49 50 51 52 53 54 55 56 57
//
// unmarshalling the XML input
//
//	<result>
//		<email where="home">
//			<addr>gre@example.com</addr>
//		</email>
//		<email where='work'>
//			<addr>gre@work.com</addr>
//		</email>
//		<name>Grace R. Emlin</name>
Gustavo Niemeyer's avatar
Gustavo Niemeyer committed
58 59 60 61
// 		<group>
// 			<value>Friends</value>
// 			<value>Squash</value>
// 		</group>
Russ Cox's avatar
Russ Cox committed
62 63 64 65 66
//		<address>123 Main Street</address>
//	</result>
//
// via Unmarshal(r, &result) is equivalent to assigning
//
67 68 69
//	r = Result{xml.Name{"", "result"},
//		"Grace R. Emlin", // name
//		"phone",	  // no phone given
Russ Cox's avatar
Russ Cox committed
70
//		[]Email{
71 72 73
//			Email{"home", "gre@example.com"},
//			Email{"work", "gre@work.com"},
//		},
Gustavo Niemeyer's avatar
Gustavo Niemeyer committed
74
//		[]string{"Friends", "Squash"},
Russ Cox's avatar
Russ Cox committed
75 76 77
//	}
//
// Note that the field r.Phone has not been modified and
Gustavo Niemeyer's avatar
Gustavo Niemeyer committed
78 79 80
// that the XML <address> element was discarded. Also, the field
// Groups was assigned considering the element path provided in the
// field tag.
Russ Cox's avatar
Russ Cox committed
81
//
82 83
// Because Unmarshal uses the reflect package, it can only assign
// to exported (upper case) fields.  Unmarshal uses a case-insensitive
Russ Cox's avatar
Russ Cox committed
84 85
// comparison to match XML element names to struct field names.
//
86 87 88
// 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).
Russ Cox's avatar
Russ Cox committed
89
//
90 91 92 93
//   * If the struct has a field of type []byte or string with tag "innerxml",
//      Unmarshal accumulates the raw XML nested inside the element
//      in that field.  The rest of the rules still apply.
//
Russ Cox's avatar
Russ Cox committed
94 95 96
//   * If the struct has a field named XMLName of type xml.Name,
//      Unmarshal records the element name in that field.
//
97 98 99
//   * 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
Russ Cox's avatar
Russ Cox committed
100 101 102 103 104 105 106 107 108 109 110
//      returns an error.
//
//   * If the XML element has an attribute whose name matches a
//      struct field of type string with tag "attr", Unmarshal records
//      the attribute value in that field.
//
//   * If the XML element contains character data, that data is
//      accumulated in the first struct field that has tag "chardata".
//      The struct field may have type []byte or string.
//      If there is no such field, the character data is discarded.
//
111 112 113 114 115
//   * 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.
//
Gustavo Niemeyer's avatar
Gustavo Niemeyer committed
116
//   * If the XML element contains a sub-element whose name matches
117
//      the prefix of a tag formatted as "a>b>c", unmarshal
Gustavo Niemeyer's avatar
Gustavo Niemeyer committed
118 119
//      will descend into the XML structure looking for elements with the
//      given names, and will map the innermost elements to that struct field.
120
//      A tag starting with ">" is equivalent to one starting
Gustavo Niemeyer's avatar
Gustavo Niemeyer committed
121 122
//      with the field name followed by ">".
//
Russ Cox's avatar
Russ Cox committed
123
//   * If the XML element contains a sub-element whose name
124
//      matches a field whose tag is neither "attr" nor "chardata",
Russ Cox's avatar
Russ Cox committed
125
//      Unmarshal maps the sub-element to that struct field.
Russ Cox's avatar
Russ Cox committed
126 127
//      Otherwise, if the struct has a field named Any, unmarshal
//      maps the sub-element to that struct field.
Russ Cox's avatar
Russ Cox committed
128 129
//
// Unmarshal maps an XML element to a string or []byte by saving the
130 131 132 133 134
// 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.
Russ Cox's avatar
Russ Cox committed
135
//
136 137
// Unmarshal maps an XML element to a slice by extending the length of
// the slice and mapping the element to the newly created value.
Russ Cox's avatar
Russ Cox committed
138
//
139 140
// Unmarshal maps an XML element or attribute value to a bool by
// setting it to the boolean value represented by the string.
Russ Cox's avatar
Russ Cox committed
141
//
142 143 144 145
// 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.
146
//
Russ Cox's avatar
Russ Cox committed
147 148 149 150 151 152
// Unmarshal maps an XML element to an xml.Name by recording the
// element name.
//
// Unmarshal maps an XML element to a pointer by setting the pointer
// to a freshly allocated value and then mapping the element to that value.
//
Russ Cox's avatar
Russ Cox committed
153
func Unmarshal(r io.Reader, val interface{}) os.Error {
Russ Cox's avatar
Russ Cox committed
154
	v := reflect.ValueOf(val)
Russ Cox's avatar
Russ Cox committed
155
	if v.Kind() != reflect.Ptr {
156
		return os.NewError("non-pointer passed to Unmarshal")
Russ Cox's avatar
Russ Cox committed
157
	}
158 159 160
	p := NewParser(r)
	elem := v.Elem()
	err := p.unmarshal(elem, nil)
Russ Cox's avatar
Russ Cox committed
161
	if err != nil {
162
		return err
Russ Cox's avatar
Russ Cox committed
163
	}
164
	return nil
Russ Cox's avatar
Russ Cox committed
165 166 167 168
}

// An UnmarshalError represents an error in the unmarshalling process.
type UnmarshalError string
Robert Griesemer's avatar
Robert Griesemer committed
169

170
func (e UnmarshalError) String() string { return string(e) }
Russ Cox's avatar
Russ Cox committed
171

172 173 174 175 176 177 178 179 180 181 182 183
// 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)
}

Russ Cox's avatar
Russ Cox committed
184 185 186 187 188 189 190
// 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
// but also defers to Unmarshal for some elements.
// Passing a nil start element indicates that Unmarshal should
// read the token stream to find the start element.
func (p *Parser) Unmarshal(val interface{}, start *StartElement) os.Error {
Russ Cox's avatar
Russ Cox committed
191
	v := reflect.ValueOf(val)
Russ Cox's avatar
Russ Cox committed
192
	if v.Kind() != reflect.Ptr {
193
		return os.NewError("non-pointer passed to Unmarshal")
Russ Cox's avatar
Russ Cox committed
194
	}
195
	return p.unmarshal(v.Elem(), start)
Russ Cox's avatar
Russ Cox committed
196 197
}

198 199 200 201
// fieldName strips invalid characters from an XML name
// to create a valid Go struct name.  It also converts the
// name to lower case letters.
func fieldName(original string) string {
202 203 204 205 206 207

	var i int
	//remove leading underscores
	for i = 0; i < len(original) && original[i] == '_'; i++ {
	}

208 209
	return strings.Map(
		func(x int) int {
210
			if x == '_' || unicode.IsDigit(x) || unicode.IsLetter(x) {
211 212
				return unicode.ToLower(x)
			}
213
			return -1
214
		},
215
		original[i:])
216 217
}

Russ Cox's avatar
Russ Cox committed
218 219 220 221 222
// Unmarshal a single XML element into val.
func (p *Parser) unmarshal(val reflect.Value, start *StartElement) os.Error {
	// Find start element if we need it.
	if start == nil {
		for {
223
			tok, err := p.Token()
Russ Cox's avatar
Russ Cox committed
224
			if err != nil {
225
				return err
Russ Cox's avatar
Russ Cox committed
226 227
			}
			if t, ok := tok.(StartElement); ok {
228 229
				start = &t
				break
Russ Cox's avatar
Russ Cox committed
230 231 232 233
			}
		}
	}

Russ Cox's avatar
Russ Cox committed
234
	if pv := val; pv.Kind() == reflect.Ptr {
Russ Cox's avatar
Russ Cox committed
235 236
		if pv.IsNil() {
			pv.Set(reflect.New(pv.Type().Elem()))
237
		}
Russ Cox's avatar
Russ Cox committed
238
		val = pv.Elem()
Russ Cox's avatar
Russ Cox committed
239 240
	}

Russ Cox's avatar
Russ Cox committed
241
	var (
242 243 244 245 246 247 248
		data         []byte
		saveData     reflect.Value
		comment      []byte
		saveComment  reflect.Value
		saveXML      reflect.Value
		saveXMLIndex int
		saveXMLData  []byte
Russ Cox's avatar
Russ Cox committed
249 250
		sv           reflect.Value
		styp         reflect.Type
251
		fieldPaths   map[string]pathInfo
Russ Cox's avatar
Russ Cox committed
252
	)
Gustavo Niemeyer's avatar
Gustavo Niemeyer committed
253

Russ Cox's avatar
Russ Cox committed
254
	switch v := val; v.Kind() {
255
	default:
256
		return os.NewError("unknown type " + v.Type().String())
257

Russ Cox's avatar
Russ Cox committed
258 259
	case reflect.Slice:
		typ := v.Type()
260
		if typ.Elem().Kind() == reflect.Uint8 {
Russ Cox's avatar
Russ Cox committed
261
			// []byte
262 263
			saveData = v
			break
Russ Cox's avatar
Russ Cox committed
264 265 266 267
		}

		// Slice of element values.
		// Grow slice.
268
		n := v.Len()
Russ Cox's avatar
Russ Cox committed
269
		if n >= v.Cap() {
270
			ncap := 2 * n
Russ Cox's avatar
Russ Cox committed
271
			if ncap < 4 {
272
				ncap = 4
Russ Cox's avatar
Russ Cox committed
273
			}
274
			new := reflect.MakeSlice(typ, n, ncap)
275
			reflect.Copy(new, v)
276
			v.Set(new)
Russ Cox's avatar
Russ Cox committed
277
		}
278
		v.SetLen(n + 1)
Russ Cox's avatar
Russ Cox committed
279 280

		// Recur to read element into slice.
Russ Cox's avatar
Russ Cox committed
281
		if err := p.unmarshal(v.Index(n), start); err != nil {
282 283
			v.SetLen(n)
			return err
Russ Cox's avatar
Russ Cox committed
284
		}
285
		return nil
Russ Cox's avatar
Russ Cox committed
286

Russ Cox's avatar
Russ Cox committed
287
	case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.String:
288
		saveData = v
Russ Cox's avatar
Russ Cox committed
289

Russ Cox's avatar
Russ Cox committed
290
	case reflect.Struct:
Russ Cox's avatar
Russ Cox committed
291
		if _, ok := v.Interface().(Name); ok {
Russ Cox's avatar
Russ Cox committed
292
			v.Set(reflect.ValueOf(start.Name))
293
			break
Russ Cox's avatar
Russ Cox committed
294 295
		}

296
		sv = v
Russ Cox's avatar
Russ Cox committed
297
		typ := sv.Type()
298
		styp = typ
Russ Cox's avatar
Russ Cox committed
299 300 301
		// Assign name.
		if f, ok := typ.FieldByName("XMLName"); ok {
			// Validate element name.
302
			if tag := f.Tag.Get("xml"); tag != "" {
303 304
				ns := ""
				i := strings.LastIndex(tag, " ")
Russ Cox's avatar
Russ Cox committed
305
				if i >= 0 {
306
					ns, tag = tag[0:i], tag[i+1:]
Russ Cox's avatar
Russ Cox committed
307 308
				}
				if tag != start.Name.Local {
309
					return UnmarshalError("expected element type <" + tag + "> but have <" + start.Name.Local + ">")
Russ Cox's avatar
Russ Cox committed
310 311
				}
				if ns != "" && ns != start.Name.Space {
312
					e := "expected element <" + tag + "> in name space " + ns + " but have "
Russ Cox's avatar
Russ Cox committed
313
					if start.Name.Space == "" {
314
						e += "no name space"
Russ Cox's avatar
Russ Cox committed
315
					} else {
316
						e += start.Name.Space
Russ Cox's avatar
Russ Cox committed
317
					}
318
					return UnmarshalError(e)
Russ Cox's avatar
Russ Cox committed
319 320 321 322
				}
			}

			// Save
323
			v := sv.FieldByIndex(f.Index)
324 325
			if _, ok := v.Interface().(Name); ok {
				v.Set(reflect.ValueOf(start.Name))
Russ Cox's avatar
Russ Cox committed
326 327 328 329
			}
		}

		// Assign attributes.
330
		// Also, determine whether we need to save character data or comments.
Russ Cox's avatar
Russ Cox committed
331
		for i, n := 0, typ.NumField(); i < n; i++ {
332
			f := typ.Field(i)
333
			switch f.Tag.Get("xml") {
Russ Cox's avatar
Russ Cox committed
334
			case "attr":
Russ Cox's avatar
Russ Cox committed
335
				strv := sv.FieldByIndex(f.Index)
Russ Cox's avatar
Russ Cox committed
336
				// Look for attribute.
337 338
				val := ""
				k := strings.ToLower(f.Name)
Russ Cox's avatar
Russ Cox committed
339
				for _, a := range start.Attr {
340
					if fieldName(a.Name.Local) == k {
341 342
						val = a.Value
						break
Russ Cox's avatar
Russ Cox committed
343 344
					}
				}
345
				copyValue(strv, []byte(val))
Russ Cox's avatar
Russ Cox committed
346

347
			case "comment":
Russ Cox's avatar
Russ Cox committed
348
				if !saveComment.IsValid() {
349
					saveComment = sv.FieldByIndex(f.Index)
350 351
				}

Russ Cox's avatar
Russ Cox committed
352
			case "chardata":
Russ Cox's avatar
Russ Cox committed
353
				if !saveData.IsValid() {
354
					saveData = sv.FieldByIndex(f.Index)
Russ Cox's avatar
Russ Cox committed
355
				}
356 357

			case "innerxml":
Russ Cox's avatar
Russ Cox committed
358
				if !saveXML.IsValid() {
359 360 361 362 363 364 365 366
					saveXML = sv.FieldByIndex(f.Index)
					if p.saved == nil {
						saveXMLIndex = 0
						p.saved = new(bytes.Buffer)
					} else {
						saveXMLIndex = p.savedOffset()
					}
				}
Gustavo Niemeyer's avatar
Gustavo Niemeyer committed
367 368

			default:
369
				if tag := f.Tag.Get("xml"); strings.Contains(tag, ">") {
Gustavo Niemeyer's avatar
Gustavo Niemeyer committed
370
					if fieldPaths == nil {
371
						fieldPaths = make(map[string]pathInfo)
Gustavo Niemeyer's avatar
Gustavo Niemeyer committed
372
					}
373 374
					path := strings.ToLower(tag)
					if strings.HasPrefix(tag, ">") {
Gustavo Niemeyer's avatar
Gustavo Niemeyer committed
375 376
						path = strings.ToLower(f.Name) + path
					}
377
					if strings.HasSuffix(tag, ">") {
Gustavo Niemeyer's avatar
Gustavo Niemeyer committed
378 379
						path = path[:len(path)-1]
					}
380 381 382 383
					err := addFieldPath(sv, fieldPaths, path, f.Index)
					if err != nil {
						return err
					}
Gustavo Niemeyer's avatar
Gustavo Niemeyer committed
384
				}
Russ Cox's avatar
Russ Cox committed
385 386 387 388 389 390 391 392
			}
		}
	}

	// Find end element.
	// Process sub-elements along the way.
Loop:
	for {
393
		var savedOffset int
Russ Cox's avatar
Russ Cox committed
394
		if saveXML.IsValid() {
395 396
			savedOffset = p.savedOffset()
		}
397
		tok, err := p.Token()
Russ Cox's avatar
Russ Cox committed
398
		if err != nil {
399
			return err
Russ Cox's avatar
Russ Cox committed
400 401 402 403
		}
		switch t := tok.(type) {
		case StartElement:
			// Sub-element.
Russ Cox's avatar
Russ Cox committed
404
			// Look up by tag name.
Russ Cox's avatar
Russ Cox committed
405
			if sv.IsValid() {
406
				k := fieldName(t.Name.Local)
Gustavo Niemeyer's avatar
Gustavo Niemeyer committed
407 408

				if fieldPaths != nil {
409 410
					if _, found := fieldPaths[k]; found {
						if err := p.unmarshalPaths(sv, fieldPaths, k, &t); err != nil {
Gustavo Niemeyer's avatar
Gustavo Niemeyer committed
411 412 413 414 415 416
							return err
						}
						continue Loop
					}
				}

Raif S. Naffah's avatar
Raif S. Naffah committed
417 418
				match := func(s string) bool {
					// check if the name matches ignoring case
Gustavo Niemeyer's avatar
Gustavo Niemeyer committed
419
					if strings.ToLower(s) != k {
Raif S. Naffah's avatar
Raif S. Naffah committed
420
						return false
Russ Cox's avatar
Russ Cox committed
421
					}
Raif S. Naffah's avatar
Raif S. Naffah committed
422 423 424 425 426 427 428 429
					// now check that it's public
					c, _ := utf8.DecodeRuneInString(s)
					return unicode.IsUpper(c)
				}

				f, found := styp.FieldByNameFunc(match)
				if !found { // fall back to mop-up field named "Any"
					f, found = styp.FieldByName("Any")
Russ Cox's avatar
Russ Cox committed
430
				}
Raif S. Naffah's avatar
Raif S. Naffah committed
431 432
				if found {
					if err := p.unmarshal(sv.FieldByIndex(f.Index), &t); err != nil {
433
						return err
Russ Cox's avatar
Russ Cox committed
434
					}
435
					continue Loop
Russ Cox's avatar
Russ Cox committed
436 437 438
				}
			}
			// Not saving sub-element but still have to skip over it.
Russ Cox's avatar
Russ Cox committed
439
			if err := p.Skip(); err != nil {
440
				return err
Russ Cox's avatar
Russ Cox committed
441 442 443
			}

		case EndElement:
Russ Cox's avatar
Russ Cox committed
444
			if saveXML.IsValid() {
445 446 447 448 449
				saveXMLData = p.saved.Bytes()[saveXMLIndex:savedOffset]
				if saveXMLIndex == 0 {
					p.saved = nil
				}
			}
450
			break Loop
Russ Cox's avatar
Russ Cox committed
451 452

		case CharData:
Russ Cox's avatar
Russ Cox committed
453
			if saveData.IsValid() {
454
				data = append(data, t...)
Russ Cox's avatar
Russ Cox committed
455
			}
456 457

		case Comment:
Russ Cox's avatar
Russ Cox committed
458
			if saveComment.IsValid() {
459
				comment = append(comment, t...)
460
			}
Russ Cox's avatar
Russ Cox committed
461 462 463
		}
	}

464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
	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) {
486 487 488
	// Helper functions for integer and unsigned integer conversions
	var itmp int64
	getInt64 := func() bool {
489
		itmp, err = strconv.Atoi64(string(src))
490 491 492 493 494
		// TODO: should check sizes
		return err == nil
	}
	var utmp uint64
	getUint64 := func() bool {
495
		utmp, err = strconv.Atoui64(string(src))
496 497 498 499 500
		// TODO: check for overflow?
		return err == nil
	}
	var ftmp float64
	getFloat64 := func() bool {
501
		ftmp, err = strconv.Atof64(string(src))
502 503 504 505 506
		// TODO: check for overflow?
		return err == nil
	}

	// Save accumulated data and comments
507
	switch t := dst; t.Kind() {
Russ Cox's avatar
Russ Cox committed
508
	case reflect.Invalid:
509 510
		// Probably a comment, handled below
	default:
511
		return os.NewError("cannot happen: unknown type " + t.Type().String())
Russ Cox's avatar
Russ Cox committed
512
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
513 514 515
		if !getInt64() {
			return err
		}
Russ Cox's avatar
Russ Cox committed
516 517
		t.SetInt(itmp)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
518 519 520
		if !getUint64() {
			return err
		}
Russ Cox's avatar
Russ Cox committed
521 522
		t.SetUint(utmp)
	case reflect.Float32, reflect.Float64:
523 524 525
		if !getFloat64() {
			return err
		}
Russ Cox's avatar
Russ Cox committed
526 527
		t.SetFloat(ftmp)
	case reflect.Bool:
528
		value, err := strconv.Atob(strings.TrimSpace(string(src)))
Rob Pike's avatar
Rob Pike committed
529 530 531
		if err != nil {
			return err
		}
Russ Cox's avatar
Russ Cox committed
532 533
		t.SetBool(value)
	case reflect.String:
534
		t.SetString(string(src))
Russ Cox's avatar
Russ Cox committed
535
	case reflect.Slice:
536
		t.Set(reflect.ValueOf(src))
537
	}
538
	return nil
Russ Cox's avatar
Russ Cox committed
539 540
}

541 542 543 544 545 546 547 548 549
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.
Russ Cox's avatar
Russ Cox committed
550
func addFieldPath(sv reflect.Value, paths map[string]pathInfo, path string, fieldIdx []int) os.Error {
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572
	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

}

Russ Cox's avatar
Russ Cox committed
573 574
func tagError(sv reflect.Value, idx1 []int, idx2 []int) os.Error {
	t := sv.Type()
575 576
	f1 := t.FieldByIndex(idx1)
	f2 := t.FieldByIndex(idx2)
577
	return &TagPathError{t, f1.Name, f1.Tag.Get("xml"), f2.Name, f2.Tag.Get("xml")}
Gustavo Niemeyer's avatar
Gustavo Niemeyer committed
578 579
}

580 581
// unmarshalPaths walks down an XML structure looking for
// wanted paths, and calls unmarshal on them.
Russ Cox's avatar
Russ Cox committed
582
func (p *Parser) unmarshalPaths(sv reflect.Value, paths map[string]pathInfo, path string, start *StartElement) os.Error {
583 584
	if info, _ := paths[path]; info.complete {
		return p.unmarshal(sv.FieldByIndex(info.fieldIdx), start)
Gustavo Niemeyer's avatar
Gustavo Niemeyer committed
585 586 587 588 589 590 591 592
	}
	for {
		tok, err := p.Token()
		if err != nil {
			return err
		}
		switch t := tok.(type) {
		case StartElement:
593 594 595
			k := path + ">" + fieldName(t.Name.Local)
			if _, found := paths[k]; found {
				if err := p.unmarshalPaths(sv, paths, k, &t); err != nil {
Gustavo Niemeyer's avatar
Gustavo Niemeyer committed
596 597 598 599 600 601 602 603 604 605 606 607 608 609
					return err
				}
				continue
			}
			if err := p.Skip(); err != nil {
				return err
			}
		case EndElement:
			return nil
		}
	}
	panic("unreachable")
}

Russ Cox's avatar
Russ Cox committed
610 611 612 613
// Have already read a start element.
// Read tokens until we find the end element.
// Token is taking care of making sure the
// end element matches the start element we saw.
Russ Cox's avatar
Russ Cox committed
614
func (p *Parser) Skip() os.Error {
Russ Cox's avatar
Russ Cox committed
615
	for {
616
		tok, err := p.Token()
Russ Cox's avatar
Russ Cox committed
617
		if err != nil {
618
			return err
Russ Cox's avatar
Russ Cox committed
619 620 621
		}
		switch t := tok.(type) {
		case StartElement:
Russ Cox's avatar
Russ Cox committed
622
			if err := p.Skip(); err != nil {
623
				return err
Russ Cox's avatar
Russ Cox committed
624 625
			}
		case EndElement:
626
			return nil
Russ Cox's avatar
Russ Cox committed
627 628
		}
	}
629
	panic("unreachable")
Russ Cox's avatar
Russ Cox committed
630
}