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<></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 &lt;link rel=&quot;hub&quot; href=&quot;hub-server&quot;&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&#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&#39;s actual URL in 51the link rel=&quot;self&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&#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<></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 <link rel="hub" href="hub-server"> 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'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's actual URL in 166the link rel="self", 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'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