github.com/varialus/godfly@v0.0.0-20130904042352-1934f9f095ab/src/pkg/encoding/xml/read_test.go (about)

     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  }