1// Copyright 2009 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package xml
6
7import (
8	"bytes"
9	"fmt"
10	"io"
11	"reflect"
12	"strings"
13	"testing"
14	"time"
15)
16
17// Stripped down Atom feed data structures.
18
19func TestUnmarshalFeed(t *testing.T) {
20	var f Feed
21	if err := Unmarshal([]byte(atomFeedString), &f); err != nil {
22		t.Fatalf("Unmarshal: %s", err)
23	}
24	if !reflect.DeepEqual(f, atomFeed) {
25		t.Fatalf("have %#v\nwant %#v", f, atomFeed)
26	}
27}
28
29// hget http://codereview.appspot.com/rss/mine/rsc
30const atomFeedString = `
31<?xml version="1.0" encoding="utf-8"?>
32<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us" updated="2009-10-04T01:35:58+00:00"><title>Code Review - My issues</title><link href="http://codereview.appspot.com/" rel="alternate"></link><link href="http://codereview.appspot.com/rss/mine/rsc" rel="self"></link><id>http://codereview.appspot.com/</id><author><name>rietveld&lt;&gt;</name></author><entry><title>rietveld: an attempt at pubsubhubbub
33</title><link href="http://codereview.appspot.com/126085" rel="alternate"></link><updated>2009-10-04T01:35:58+00:00</updated><author><name>email-address-removed</name></author><id>urn:md5:134d9179c41f806be79b3a5f7877d19a</id><summary type="html">
34  An attempt at adding pubsubhubbub support to Rietveld.
35http://code.google.com/p/pubsubhubbub
36http://code.google.com/p/rietveld/issues/detail?id=155
37
38The server side of the protocol is trivial:
39  1. add a &amp;lt;link rel=&amp;quot;hub&amp;quot; href=&amp;quot;hub-server&amp;quot;&amp;gt; tag to all
40     feeds that will be pubsubhubbubbed.
41  2. every time one of those feeds changes, tell the hub
42     with a simple POST request.
43
44I have tested this by adding debug prints to a local hub
45server and checking that the server got the right publish
46requests.
47
48I can&amp;#39;t quite get the server to work, but I think the bug
49is not in my code.  I think that the server expects to be
50able to grab the feed and see the feed&amp;#39;s actual URL in
51the link rel=&amp;quot;self&amp;quot;, but the default value for that drops
52the :port from the URL, and I cannot for the life of me
53figure out how to get the Atom generator deep inside
54django not to do that, or even where it is doing that,
55or even what code is running to generate the Atom feed.
56(I thought I knew but I added some assert False statements
57and it kept running!)
58
59Ignoring that particular problem, I would appreciate
60feedback on the right way to get the two values at
61the top of feeds.py marked NOTE(rsc).
62
63
64</summary></entry><entry><title>rietveld: correct tab handling
65</title><link href="http://codereview.appspot.com/124106" rel="alternate"></link><updated>2009-10-03T23:02:17+00:00</updated><author><name>email-address-removed</name></author><id>urn:md5:0a2a4f19bb815101f0ba2904aed7c35a</id><summary type="html">
66  This fixes the buggy tab rendering that can be seen at
67http://codereview.appspot.com/116075/diff/1/2
68
69The fundamental problem was that the tab code was
70not being told what column the text began in, so it
71didn&amp;#39;t know where to put the tab stops.  Another problem
72was that some of the code assumed that string byte
73offsets were the same as column offsets, which is only
74true if there are no tabs.
75
76In the process of fixing this, I cleaned up the arguments
77to Fold and ExpandTabs and renamed them Break and
78_ExpandTabs so that I could be sure that I found all the
79call sites.  I also wanted to verify that ExpandTabs was
80not being used from outside intra_region_diff.py.
81
82
83</summary></entry></feed> 	   `
84
85type Feed struct {
86	XMLName Name      `xml:"http://www.w3.org/2005/Atom feed"`
87	Title   string    `xml:"title"`
88	Id      string    `xml:"id"`
89	Link    []Link    `xml:"link"`
90	Updated time.Time `xml:"updated,attr"`
91	Author  Person    `xml:"author"`
92	Entry   []Entry   `xml:"entry"`
93}
94
95type Entry struct {
96	Title   string    `xml:"title"`
97	Id      string    `xml:"id"`
98	Link    []Link    `xml:"link"`
99	Updated time.Time `xml:"updated"`
100	Author  Person    `xml:"author"`
101	Summary Text      `xml:"summary"`
102}
103
104type Link struct {
105	Rel  string `xml:"rel,attr,omitempty"`
106	Href string `xml:"href,attr"`
107}
108
109type Person struct {
110	Name     string `xml:"name"`
111	URI      string `xml:"uri"`
112	Email    string `xml:"email"`
113	InnerXML string `xml:",innerxml"`
114}
115
116type Text struct {
117	Type string `xml:"type,attr,omitempty"`
118	Body string `xml:",chardata"`
119}
120
121var atomFeed = Feed{
122	XMLName: Name{"http://www.w3.org/2005/Atom", "feed"},
123	Title:   "Code Review - My issues",
124	Link: []Link{
125		{Rel: "alternate", Href: "http://codereview.appspot.com/"},
126		{Rel: "self", Href: "http://codereview.appspot.com/rss/mine/rsc"},
127	},
128	Id:      "http://codereview.appspot.com/",
129	Updated: ParseTime("2009-10-04T01:35:58+00:00"),
130	Author: Person{
131		Name:     "rietveld<>",
132		InnerXML: "<name>rietveld&lt;&gt;</name>",
133	},
134	Entry: []Entry{
135		{
136			Title: "rietveld: an attempt at pubsubhubbub\n",
137			Link: []Link{
138				{Rel: "alternate", Href: "http://codereview.appspot.com/126085"},
139			},
140			Updated: ParseTime("2009-10-04T01:35:58+00:00"),
141			Author: Person{
142				Name:     "email-address-removed",
143				InnerXML: "<name>email-address-removed</name>",
144			},
145			Id: "urn:md5:134d9179c41f806be79b3a5f7877d19a",
146			Summary: Text{
147				Type: "html",
148				Body: `
149  An attempt at adding pubsubhubbub support to Rietveld.
150http://code.google.com/p/pubsubhubbub
151http://code.google.com/p/rietveld/issues/detail?id=155
152
153The server side of the protocol is trivial:
154  1. add a &lt;link rel=&quot;hub&quot; href=&quot;hub-server&quot;&gt; tag to all
155     feeds that will be pubsubhubbubbed.
156  2. every time one of those feeds changes, tell the hub
157     with a simple POST request.
158
159I have tested this by adding debug prints to a local hub
160server and checking that the server got the right publish
161requests.
162
163I can&#39;t quite get the server to work, but I think the bug
164is not in my code.  I think that the server expects to be
165able to grab the feed and see the feed&#39;s actual URL in
166the link rel=&quot;self&quot;, but the default value for that drops
167the :port from the URL, and I cannot for the life of me
168figure out how to get the Atom generator deep inside
169django not to do that, or even where it is doing that,
170or even what code is running to generate the Atom feed.
171(I thought I knew but I added some assert False statements
172and it kept running!)
173
174Ignoring that particular problem, I would appreciate
175feedback on the right way to get the two values at
176the top of feeds.py marked NOTE(rsc).
177
178
179`,
180			},
181		},
182		{
183			Title: "rietveld: correct tab handling\n",
184			Link: []Link{
185				{Rel: "alternate", Href: "http://codereview.appspot.com/124106"},
186			},
187			Updated: ParseTime("2009-10-03T23:02:17+00:00"),
188			Author: Person{
189				Name:     "email-address-removed",
190				InnerXML: "<name>email-address-removed</name>",
191			},
192			Id: "urn:md5:0a2a4f19bb815101f0ba2904aed7c35a",
193			Summary: Text{
194				Type: "html",
195				Body: `
196  This fixes the buggy tab rendering that can be seen at
197http://codereview.appspot.com/116075/diff/1/2
198
199The fundamental problem was that the tab code was
200not being told what column the text began in, so it
201didn&#39;t know where to put the tab stops.  Another problem
202was that some of the code assumed that string byte
203offsets were the same as column offsets, which is only
204true if there are no tabs.
205
206In the process of fixing this, I cleaned up the arguments
207to Fold and ExpandTabs and renamed them Break and
208_ExpandTabs so that I could be sure that I found all the
209call sites.  I also wanted to verify that ExpandTabs was
210not being used from outside intra_region_diff.py.
211
212
213`,
214			},
215		},
216	},
217}
218
219const pathTestString = `
220<Result>
221    <Before>1</Before>
222    <Items>
223        <Item1>
224            <Value>A</Value>
225        </Item1>
226        <Item2>
227            <Value>B</Value>
228        </Item2>
229        <Item1>
230            <Value>C</Value>
231            <Value>D</Value>
232        </Item1>
233        <_>
234            <Value>E</Value>
235        </_>
236    </Items>
237    <After>2</After>
238</Result>
239`
240
241type PathTestItem struct {
242	Value string
243}
244
245type PathTestA struct {
246	Items         []PathTestItem `xml:">Item1"`
247	Before, After string
248}
249
250type PathTestB struct {
251	Other         []PathTestItem `xml:"Items>Item1"`
252	Before, After string
253}
254
255type PathTestC struct {
256	Values1       []string `xml:"Items>Item1>Value"`
257	Values2       []string `xml:"Items>Item2>Value"`
258	Before, After string
259}
260
261type PathTestSet struct {
262	Item1 []PathTestItem
263}
264
265type PathTestD struct {
266	Other         PathTestSet `xml:"Items"`
267	Before, After string
268}
269
270type PathTestE struct {
271	Underline     string `xml:"Items>_>Value"`
272	Before, After string
273}
274
275var pathTests = []interface{}{
276	&PathTestA{Items: []PathTestItem{{"A"}, {"D"}}, Before: "1", After: "2"},
277	&PathTestB{Other: []PathTestItem{{"A"}, {"D"}}, Before: "1", After: "2"},
278	&PathTestC{Values1: []string{"A", "C", "D"}, Values2: []string{"B"}, Before: "1", After: "2"},
279	&PathTestD{Other: PathTestSet{Item1: []PathTestItem{{"A"}, {"D"}}}, Before: "1", After: "2"},
280	&PathTestE{Underline: "E", Before: "1", After: "2"},
281}
282
283func TestUnmarshalPaths(t *testing.T) {
284	for _, pt := range pathTests {
285		v := reflect.New(reflect.TypeOf(pt).Elem()).Interface()
286		if err := Unmarshal([]byte(pathTestString), v); err != nil {
287			t.Fatalf("Unmarshal: %s", err)
288		}
289		if !reflect.DeepEqual(v, pt) {
290			t.Fatalf("have %#v\nwant %#v", v, pt)
291		}
292	}
293}
294
295type BadPathTestA struct {
296	First  string `xml:"items>item1"`
297	Other  string `xml:"items>item2"`
298	Second string `xml:"items"`
299}
300
301type BadPathTestB struct {
302	Other  string `xml:"items>item2>value"`
303	First  string `xml:"items>item1"`
304	Second string `xml:"items>item1>value"`
305}
306
307type BadPathTestC struct {
308	First  string
309	Second string `xml:"First"`
310}
311
312type BadPathTestD struct {
313	BadPathEmbeddedA
314	BadPathEmbeddedB
315}
316
317type BadPathEmbeddedA struct {
318	First string
319}
320
321type BadPathEmbeddedB struct {
322	Second string `xml:"First"`
323}
324
325var badPathTests = []struct {
326	v, e interface{}
327}{
328	{&BadPathTestA{}, &TagPathError{reflect.TypeOf(BadPathTestA{}), "First", "items>item1", "Second", "items"}},
329	{&BadPathTestB{}, &TagPathError{reflect.TypeOf(BadPathTestB{}), "First", "items>item1", "Second", "items>item1>value"}},
330	{&BadPathTestC{}, &TagPathError{reflect.TypeOf(BadPathTestC{}), "First", "", "Second", "First"}},
331	{&BadPathTestD{}, &TagPathError{reflect.TypeOf(BadPathTestD{}), "First", "", "Second", "First"}},
332}
333
334func TestUnmarshalBadPaths(t *testing.T) {
335	for _, tt := range badPathTests {
336		err := Unmarshal([]byte(pathTestString), tt.v)
337		if !reflect.DeepEqual(err, tt.e) {
338			t.Fatalf("Unmarshal with %#v didn't fail properly:\nhave %#v,\nwant %#v", tt.v, err, tt.e)
339		}
340	}
341}
342
343const OK = "OK"
344const withoutNameTypeData = `
345<?xml version="1.0" charset="utf-8"?>
346<Test3 Attr="OK" />`
347
348type TestThree struct {
349	XMLName Name   `xml:"Test3"`
350	Attr    string `xml:",attr"`
351}
352
353func TestUnmarshalWithoutNameType(t *testing.T) {
354	var x TestThree
355	if err := Unmarshal([]byte(withoutNameTypeData), &x); err != nil {
356		t.Fatalf("Unmarshal: %s", err)
357	}
358	if x.Attr != OK {
359		t.Fatalf("have %v\nwant %v", x.Attr, OK)
360	}
361}
362
363func TestUnmarshalAttr(t *testing.T) {
364	type ParamVal struct {
365		Int int `xml:"int,attr"`
366	}
367
368	type ParamPtr struct {
369		Int *int `xml:"int,attr"`
370	}
371
372	type ParamStringPtr struct {
373		Int *string `xml:"int,attr"`
374	}
375
376	x := []byte(`<Param int="1" />`)
377
378	p1 := &ParamPtr{}
379	if err := Unmarshal(x, p1); err != nil {
380		t.Fatalf("Unmarshal: %s", err)
381	}
382	if p1.Int == nil {
383		t.Fatalf("Unmarshal failed in to *int field")
384	} else if *p1.Int != 1 {
385		t.Fatalf("Unmarshal with %s failed:\nhave %#v,\n want %#v", x, p1.Int, 1)
386	}
387
388	p2 := &ParamVal{}
389	if err := Unmarshal(x, p2); err != nil {
390		t.Fatalf("Unmarshal: %s", err)
391	}
392	if p2.Int != 1 {
393		t.Fatalf("Unmarshal with %s failed:\nhave %#v,\n want %#v", x, p2.Int, 1)
394	}
395
396	p3 := &ParamStringPtr{}
397	if err := Unmarshal(x, p3); err != nil {
398		t.Fatalf("Unmarshal: %s", err)
399	}
400	if p3.Int == nil {
401		t.Fatalf("Unmarshal failed in to *string field")
402	} else if *p3.Int != "1" {
403		t.Fatalf("Unmarshal with %s failed:\nhave %#v,\n want %#v", x, p3.Int, 1)
404	}
405}
406
407type Tables struct {
408	HTable string `xml:"http://www.w3.org/TR/html4/ table"`
409	FTable string `xml:"http://www.w3schools.com/furniture table"`
410}
411
412var tables = []struct {
413	xml string
414	tab Tables
415	ns  string
416}{
417	{
418		xml: `<Tables>` +
419			`<table xmlns="http://www.w3.org/TR/html4/">hello</table>` +
420			`<table xmlns="http://www.w3schools.com/furniture">world</table>` +
421			`</Tables>`,
422		tab: Tables{"hello", "world"},
423	},
424	{
425		xml: `<Tables>` +
426			`<table xmlns="http://www.w3schools.com/furniture">world</table>` +
427			`<table xmlns="http://www.w3.org/TR/html4/">hello</table>` +
428			`</Tables>`,
429		tab: Tables{"hello", "world"},
430	},
431	{
432		xml: `<Tables xmlns:f="http://www.w3schools.com/furniture" xmlns:h="http://www.w3.org/TR/html4/">` +
433			`<f:table>world</f:table>` +
434			`<h:table>hello</h:table>` +
435			`</Tables>`,
436		tab: Tables{"hello", "world"},
437	},
438	{
439		xml: `<Tables>` +
440			`<table>bogus</table>` +
441			`</Tables>`,
442		tab: Tables{},
443	},
444	{
445		xml: `<Tables>` +
446			`<table>only</table>` +
447			`</Tables>`,
448		tab: Tables{HTable: "only"},
449		ns:  "http://www.w3.org/TR/html4/",
450	},
451	{
452		xml: `<Tables>` +
453			`<table>only</table>` +
454			`</Tables>`,
455		tab: Tables{FTable: "only"},
456		ns:  "http://www.w3schools.com/furniture",
457	},
458	{
459		xml: `<Tables>` +
460			`<table>only</table>` +
461			`</Tables>`,
462		tab: Tables{},
463		ns:  "something else entirely",
464	},
465}
466
467func TestUnmarshalNS(t *testing.T) {
468	for i, tt := range tables {
469		var dst Tables
470		var err error
471		if tt.ns != "" {
472			d := NewDecoder(strings.NewReader(tt.xml))
473			d.DefaultSpace = tt.ns
474			err = d.Decode(&dst)
475		} else {
476			err = Unmarshal([]byte(tt.xml), &dst)
477		}
478		if err != nil {
479			t.Errorf("#%d: Unmarshal: %v", i, err)
480			continue
481		}
482		want := tt.tab
483		if dst != want {
484			t.Errorf("#%d: dst=%+v, want %+v", i, dst, want)
485		}
486	}
487}
488
489func TestRoundTrip(t *testing.T) {
490	// From issue 7535
491	const s = `<ex:element xmlns:ex="http://example.com/schema"></ex:element>`
492	in := bytes.NewBufferString(s)
493	for i := 0; i < 10; i++ {
494		out := &bytes.Buffer{}
495		d := NewDecoder(in)
496		e := NewEncoder(out)
497
498		for {
499			t, err := d.Token()
500			if err == io.EOF {
501				break
502			}
503			if err != nil {
504				fmt.Println("failed:", err)
505				return
506			}
507			e.EncodeToken(t)
508		}
509		e.Flush()
510		in = out
511	}
512	if got := in.String(); got != s {
513		t.Errorf("have: %q\nwant: %q\n", got, s)
514	}
515}
516
517func TestMarshalNS(t *testing.T) {
518	dst := Tables{"hello", "world"}
519	data, err := Marshal(&dst)
520	if err != nil {
521		t.Fatalf("Marshal: %v", err)
522	}
523	want := `<Tables><table xmlns="http://www.w3.org/TR/html4/">hello</table><table xmlns="http://www.w3schools.com/furniture">world</table></Tables>`
524	str := string(data)
525	if str != want {
526		t.Errorf("have: %q\nwant: %q\n", str, want)
527	}
528}
529
530type TableAttrs struct {
531	TAttr TAttr
532}
533
534type TAttr struct {
535	HTable string `xml:"http://www.w3.org/TR/html4/ table,attr"`
536	FTable string `xml:"http://www.w3schools.com/furniture table,attr"`
537	Lang   string `xml:"http://www.w3.org/XML/1998/namespace lang,attr,omitempty"`
538	Other1 string `xml:"http://golang.org/xml/ other,attr,omitempty"`
539	Other2 string `xml:"http://golang.org/xmlfoo/ other,attr,omitempty"`
540	Other3 string `xml:"http://golang.org/json/ other,attr,omitempty"`
541	Other4 string `xml:"http://golang.org/2/json/ other,attr,omitempty"`
542}
543
544var tableAttrs = []struct {
545	xml string
546	tab TableAttrs
547	ns  string
548}{
549	{
550		xml: `<TableAttrs xmlns:f="http://www.w3schools.com/furniture" xmlns:h="http://www.w3.org/TR/html4/"><TAttr ` +
551			`h:table="hello" f:table="world" ` +
552			`/></TableAttrs>`,
553		tab: TableAttrs{TAttr{HTable: "hello", FTable: "world"}},
554	},
555	{
556		xml: `<TableAttrs><TAttr xmlns:f="http://www.w3schools.com/furniture" xmlns:h="http://www.w3.org/TR/html4/" ` +
557			`h:table="hello" f:table="world" ` +
558			`/></TableAttrs>`,
559		tab: TableAttrs{TAttr{HTable: "hello", FTable: "world"}},
560	},
561	{
562		xml: `<TableAttrs><TAttr ` +
563			`h:table="hello" f:table="world" xmlns:f="http://www.w3schools.com/furniture" xmlns:h="http://www.w3.org/TR/html4/" ` +
564			`/></TableAttrs>`,
565		tab: TableAttrs{TAttr{HTable: "hello", FTable: "world"}},
566	},
567	{
568		// Default space does not apply to attribute names.
569		xml: `<TableAttrs xmlns="http://www.w3schools.com/furniture" xmlns:h="http://www.w3.org/TR/html4/"><TAttr ` +
570			`h:table="hello" table="world" ` +
571			`/></TableAttrs>`,
572		tab: TableAttrs{TAttr{HTable: "hello", FTable: ""}},
573	},
574	{
575		// Default space does not apply to attribute names.
576		xml: `<TableAttrs xmlns:f="http://www.w3schools.com/furniture"><TAttr xmlns="http://www.w3.org/TR/html4/" ` +
577			`table="hello" f:table="world" ` +
578			`/></TableAttrs>`,
579		tab: TableAttrs{TAttr{HTable: "", FTable: "world"}},
580	},
581	{
582		xml: `<TableAttrs><TAttr ` +
583			`table="bogus" ` +
584			`/></TableAttrs>`,
585		tab: TableAttrs{},
586	},
587	{
588		// Default space does not apply to attribute names.
589		xml: `<TableAttrs xmlns:h="http://www.w3.org/TR/html4/"><TAttr ` +
590			`h:table="hello" table="world" ` +
591			`/></TableAttrs>`,
592		tab: TableAttrs{TAttr{HTable: "hello", FTable: ""}},
593		ns:  "http://www.w3schools.com/furniture",
594	},
595	{
596		// Default space does not apply to attribute names.
597		xml: `<TableAttrs xmlns:f="http://www.w3schools.com/furniture"><TAttr ` +
598			`table="hello" f:table="world" ` +
599			`/></TableAttrs>`,
600		tab: TableAttrs{TAttr{HTable: "", FTable: "world"}},
601		ns:  "http://www.w3.org/TR/html4/",
602	},
603	{
604		xml: `<TableAttrs><TAttr ` +
605			`table="bogus" ` +
606			`/></TableAttrs>`,
607		tab: TableAttrs{},
608		ns:  "something else entirely",
609	},
610}
611
612func TestUnmarshalNSAttr(t *testing.T) {
613	for i, tt := range tableAttrs {
614		var dst TableAttrs
615		var err error
616		if tt.ns != "" {
617			d := NewDecoder(strings.NewReader(tt.xml))
618			d.DefaultSpace = tt.ns
619			err = d.Decode(&dst)
620		} else {
621			err = Unmarshal([]byte(tt.xml), &dst)
622		}
623		if err != nil {
624			t.Errorf("#%d: Unmarshal: %v", i, err)
625			continue
626		}
627		want := tt.tab
628		if dst != want {
629			t.Errorf("#%d: dst=%+v, want %+v", i, dst, want)
630		}
631	}
632}
633
634func TestMarshalNSAttr(t *testing.T) {
635	src := TableAttrs{TAttr{"hello", "world", "en_US", "other1", "other2", "other3", "other4"}}
636	data, err := Marshal(&src)
637	if err != nil {
638		t.Fatalf("Marshal: %v", err)
639	}
640	want := `<TableAttrs><TAttr xmlns:json_1="http://golang.org/2/json/" xmlns:json="http://golang.org/json/" xmlns:_xmlfoo="http://golang.org/xmlfoo/" xmlns:_xml="http://golang.org/xml/" xmlns:furniture="http://www.w3schools.com/furniture" xmlns:html4="http://www.w3.org/TR/html4/" html4:table="hello" furniture:table="world" xml:lang="en_US" _xml:other="other1" _xmlfoo:other="other2" json:other="other3" json_1:other="other4"></TAttr></TableAttrs>`
641	str := string(data)
642	if str != want {
643		t.Errorf("Marshal:\nhave: %#q\nwant: %#q\n", str, want)
644	}
645
646	var dst TableAttrs
647	if err := Unmarshal(data, &dst); err != nil {
648		t.Errorf("Unmarshal: %v", err)
649	}
650
651	if dst != src {
652		t.Errorf("Unmarshal = %q, want %q", dst, src)
653	}
654}
655
656type MyCharData struct {
657	body string
658}
659
660func (m *MyCharData) UnmarshalXML(d *Decoder, start StartElement) error {
661	for {
662		t, err := d.Token()
663		if err == io.EOF { // found end of element
664			break
665		}
666		if err != nil {
667			return err
668		}
669		if char, ok := t.(CharData); ok {
670			m.body += string(char)
671		}
672	}
673	return nil
674}
675
676var _ Unmarshaler = (*MyCharData)(nil)
677
678func (m *MyCharData) UnmarshalXMLAttr(attr Attr) error {
679	panic("must not call")
680}
681
682type MyAttr struct {
683	attr string
684}
685
686func (m *MyAttr) UnmarshalXMLAttr(attr Attr) error {
687	m.attr = attr.Value
688	return nil
689}
690
691var _ UnmarshalerAttr = (*MyAttr)(nil)
692
693type MyStruct struct {
694	Data *MyCharData
695	Attr *MyAttr `xml:",attr"`
696
697	Data2 MyCharData
698	Attr2 MyAttr `xml:",attr"`
699}
700
701func TestUnmarshaler(t *testing.T) {
702	xml := `<?xml version="1.0" encoding="utf-8"?>
703		<MyStruct Attr="attr1" Attr2="attr2">
704		<Data>hello <!-- comment -->world</Data>
705		<Data2>howdy <!-- comment -->world</Data2>
706		</MyStruct>
707	`
708
709	var m MyStruct
710	if err := Unmarshal([]byte(xml), &m); err != nil {
711		t.Fatal(err)
712	}
713
714	if m.Data == nil || m.Attr == nil || m.Data.body != "hello world" || m.Attr.attr != "attr1" || m.Data2.body != "howdy world" || m.Attr2.attr != "attr2" {
715		t.Errorf("m=%#+v\n", m)
716	}
717}
718
719type Pea struct {
720	Cotelydon string
721}
722
723type Pod struct {
724	Pea interface{} `xml:"Pea"`
725}
726
727// https://golang.org/issue/6836
728func TestUnmarshalIntoInterface(t *testing.T) {
729	pod := new(Pod)
730	pod.Pea = new(Pea)
731	xml := `<Pod><Pea><Cotelydon>Green stuff</Cotelydon></Pea></Pod>`
732	err := Unmarshal([]byte(xml), pod)
733	if err != nil {
734		t.Fatalf("failed to unmarshal %q: %v", xml, err)
735	}
736	pea, ok := pod.Pea.(*Pea)
737	if !ok {
738		t.Fatalf("unmarshalled into wrong type: have %T want *Pea", pod.Pea)
739	}
740	have, want := pea.Cotelydon, "Green stuff"
741	if have != want {
742		t.Errorf("failed to unmarshal into interface, have %q want %q", have, want)
743	}
744}
745