Home | History | Annotate | Download | only in xml
      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 
      5 package xml
      6 
      7 import (
      8 	"io"
      9 	"reflect"
     10 	"strings"
     11 	"testing"
     12 	"time"
     13 )
     14 
     15 // Stripped down Atom feed data structures.
     16 
     17 func 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
     28 const 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.
     33 http://code.google.com/p/pubsubhubbub
     34 http://code.google.com/p/rietveld/issues/detail?id=155
     35 
     36 The 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 
     42 I have tested this by adding debug prints to a local hub
     43 server and checking that the server got the right publish
     44 requests.
     45 
     46 I can&amp;#39;t quite get the server to work, but I think the bug
     47 is not in my code.  I think that the server expects to be
     48 able to grab the feed and see the feed&amp;#39;s actual URL in
     49 the link rel=&amp;quot;self&amp;quot;, but the default value for that drops
     50 the :port from the URL, and I cannot for the life of me
     51 figure out how to get the Atom generator deep inside
     52 django not to do that, or even where it is doing that,
     53 or even what code is running to generate the Atom feed.
     54 (I thought I knew but I added some assert False statements
     55 and it kept running!)
     56 
     57 Ignoring that particular problem, I would appreciate
     58 feedback on the right way to get the two values at
     59 the 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
     65 http://codereview.appspot.com/116075/diff/1/2
     66 
     67 The fundamental problem was that the tab code was
     68 not being told what column the text began in, so it
     69 didn&amp;#39;t know where to put the tab stops.  Another problem
     70 was that some of the code assumed that string byte
     71 offsets were the same as column offsets, which is only
     72 true if there are no tabs.
     73 
     74 In the process of fixing this, I cleaned up the arguments
     75 to Fold and ExpandTabs and renamed them Break and
     76 _ExpandTabs so that I could be sure that I found all the
     77 call sites.  I also wanted to verify that ExpandTabs was
     78 not being used from outside intra_region_diff.py.
     79 
     80 
     81 </summary></entry></feed> 	   `
     82 
     83 type 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 
     93 type 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 
    102 type Link struct {
    103 	Rel  string `xml:"rel,attr,omitempty"`
    104 	Href string `xml:"href,attr"`
    105 }
    106 
    107 type Person struct {
    108 	Name     string `xml:"name"`
    109 	URI      string `xml:"uri"`
    110 	Email    string `xml:"email"`
    111 	InnerXML string `xml:",innerxml"`
    112 }
    113 
    114 type Text struct {
    115 	Type string `xml:"type,attr,omitempty"`
    116 	Body string `xml:",chardata"`
    117 }
    118 
    119 var 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.
    148 http://code.google.com/p/pubsubhubbub
    149 http://code.google.com/p/rietveld/issues/detail?id=155
    150 
    151 The 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 
    157 I have tested this by adding debug prints to a local hub
    158 server and checking that the server got the right publish
    159 requests.
    160 
    161 I can&#39;t quite get the server to work, but I think the bug
    162 is not in my code.  I think that the server expects to be
    163 able to grab the feed and see the feed&#39;s actual URL in
    164 the link rel=&quot;self&quot;, but the default value for that drops
    165 the :port from the URL, and I cannot for the life of me
    166 figure out how to get the Atom generator deep inside
    167 django not to do that, or even where it is doing that,
    168 or even what code is running to generate the Atom feed.
    169 (I thought I knew but I added some assert False statements
    170 and it kept running!)
    171 
    172 Ignoring that particular problem, I would appreciate
    173 feedback on the right way to get the two values at
    174 the 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
    195 http://codereview.appspot.com/116075/diff/1/2
    196 
    197 The fundamental problem was that the tab code was
    198 not being told what column the text began in, so it
    199 didn&#39;t know where to put the tab stops.  Another problem
    200 was that some of the code assumed that string byte
    201 offsets were the same as column offsets, which is only
    202 true if there are no tabs.
    203 
    204 In the process of fixing this, I cleaned up the arguments
    205 to Fold and ExpandTabs and renamed them Break and
    206 _ExpandTabs so that I could be sure that I found all the
    207 call sites.  I also wanted to verify that ExpandTabs was
    208 not being used from outside intra_region_diff.py.
    209 
    210 
    211 `,
    212 			},
    213 		},
    214 	},
    215 }
    216 
    217 const 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 
    239 type PathTestItem struct {
    240 	Value string
    241 }
    242 
    243 type PathTestA struct {
    244 	Items         []PathTestItem `xml:">Item1"`
    245 	Before, After string
    246 }
    247 
    248 type PathTestB struct {
    249 	Other         []PathTestItem `xml:"Items>Item1"`
    250 	Before, After string
    251 }
    252 
    253 type PathTestC struct {
    254 	Values1       []string `xml:"Items>Item1>Value"`
    255 	Values2       []string `xml:"Items>Item2>Value"`
    256 	Before, After string
    257 }
    258 
    259 type PathTestSet struct {
    260 	Item1 []PathTestItem
    261 }
    262 
    263 type PathTestD struct {
    264 	Other         PathTestSet `xml:"Items"`
    265 	Before, After string
    266 }
    267 
    268 type PathTestE struct {
    269 	Underline     string `xml:"Items>_>Value"`
    270 	Before, After string
    271 }
    272 
    273 var 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 
    281 func 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 
    293 type BadPathTestA struct {
    294 	First  string `xml:"items>item1"`
    295 	Other  string `xml:"items>item2"`
    296 	Second string `xml:"items"`
    297 }
    298 
    299 type BadPathTestB struct {
    300 	Other  string `xml:"items>item2>value"`
    301 	First  string `xml:"items>item1"`
    302 	Second string `xml:"items>item1>value"`
    303 }
    304 
    305 type BadPathTestC struct {
    306 	First  string
    307 	Second string `xml:"First"`
    308 }
    309 
    310 type BadPathTestD struct {
    311 	BadPathEmbeddedA
    312 	BadPathEmbeddedB
    313 }
    314 
    315 type BadPathEmbeddedA struct {
    316 	First string
    317 }
    318 
    319 type BadPathEmbeddedB struct {
    320 	Second string `xml:"First"`
    321 }
    322 
    323 var 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 
    332 func 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 
    341 const OK = "OK"
    342 const withoutNameTypeData = `
    343 <?xml version="1.0" charset="utf-8"?>
    344 <Test3 Attr="OK" />`
    345 
    346 type TestThree struct {
    347 	XMLName Name   `xml:"Test3"`
    348 	Attr    string `xml:",attr"`
    349 }
    350 
    351 func 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 
    361 func 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 
    405 type 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 
    410 var 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 
    465 func 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 
    487 func 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 
    500 type TableAttrs struct {
    501 	TAttr TAttr
    502 }
    503 
    504 type 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 
    514 var 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 
    582 func 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 
    604 func 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 
    626 type MyCharData struct {
    627 	body string
    628 }
    629 
    630 func (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 
    646 var _ Unmarshaler = (*MyCharData)(nil)
    647 
    648 func (m *MyCharData) UnmarshalXMLAttr(attr Attr) error {
    649 	panic("must not call")
    650 }
    651 
    652 type MyAttr struct {
    653 	attr string
    654 }
    655 
    656 func (m *MyAttr) UnmarshalXMLAttr(attr Attr) error {
    657 	m.attr = attr.Value
    658 	return nil
    659 }
    660 
    661 var _ UnmarshalerAttr = (*MyAttr)(nil)
    662 
    663 type MyStruct struct {
    664 	Data *MyCharData
    665 	Attr *MyAttr `xml:",attr"`
    666 
    667 	Data2 MyCharData
    668 	Attr2 MyAttr `xml:",attr"`
    669 }
    670 
    671 func 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 
    689 type Pea struct {
    690 	Cotelydon string
    691 }
    692 
    693 type Pod struct {
    694 	Pea interface{} `xml:"Pea"`
    695 }
    696 
    697 // https://golang.org/issue/6836
    698 func TestUnmarshalIntoInterface(t *testing.T) {
    699 	pod := new(Pod)
    700 	pod.Pea = new(Pea)
    701 	xml := `<Pod><Pea><Cotelydon>Green stuff</Cotelydon></Pea></Pod>`
    702 	err := Unmarshal([]byte(xml), pod)
    703 	if err != nil {
    704 		t.Fatalf("failed to unmarshal %q: %v", xml, err)
    705 	}
    706 	pea, ok := pod.Pea.(*Pea)
    707 	if !ok {
    708 		t.Fatalf("unmarshalled into wrong type: have %T want *Pea", pod.Pea)
    709 	}
    710 	have, want := pea.Cotelydon, "Green stuff"
    711 	if have != want {
    712 		t.Errorf("failed to unmarshal into interface, have %q want %q", have, want)
    713 	}
    714 }
    715