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