golang.org/x/net@v0.25.1-0.20240516223405-c87a5b62e243/webdav/internal/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  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"reflect"
    12  	"strings"
    13  	"testing"
    14  	"time"
    15  )
    16  
    17  // Stripped down Atom feed data structures.
    18  
    19  func 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
    30  const atomFeedString = `
    31  <?xml version="1.0" encoding="utf-8"?>
    32  <feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us" updated="2009-10-04T01:35:58+00:00"><title>Code Review - My issues</title><link href="http://codereview.appspot.com/" rel="alternate"></link><link href="http://codereview.appspot.com/rss/mine/rsc" rel="self"></link><id>http://codereview.appspot.com/</id><author><name>rietveld&lt;&gt;</name></author><entry><title>rietveld: an attempt at pubsubhubbub
    33  </title><link href="http://codereview.appspot.com/126085" rel="alternate"></link><updated>2009-10-04T01:35:58+00:00</updated><author><name>email-address-removed</name></author><id>urn:md5:134d9179c41f806be79b3a5f7877d19a</id><summary type="html">
    34    An attempt at adding pubsubhubbub support to Rietveld.
    35  http://code.google.com/p/pubsubhubbub
    36  http://code.google.com/p/rietveld/issues/detail?id=155
    37  
    38  The server side of the protocol is trivial:
    39    1. add a &amp;lt;link rel=&amp;quot;hub&amp;quot; href=&amp;quot;hub-server&amp;quot;&amp;gt; tag to all
    40       feeds that will be pubsubhubbubbed.
    41    2. every time one of those feeds changes, tell the hub
    42       with a simple POST request.
    43  
    44  I have tested this by adding debug prints to a local hub
    45  server and checking that the server got the right publish
    46  requests.
    47  
    48  I can&amp;#39;t quite get the server to work, but I think the bug
    49  is not in my code.  I think that the server expects to be
    50  able to grab the feed and see the feed&amp;#39;s actual URL in
    51  the link rel=&amp;quot;self&amp;quot;, but the default value for that drops
    52  the :port from the URL, and I cannot for the life of me
    53  figure out how to get the Atom generator deep inside
    54  django not to do that, or even where it is doing that,
    55  or even what code is running to generate the Atom feed.
    56  (I thought I knew but I added some assert False statements
    57  and it kept running!)
    58  
    59  Ignoring that particular problem, I would appreciate
    60  feedback on the right way to get the two values at
    61  the 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
    67  http://codereview.appspot.com/116075/diff/1/2
    68  
    69  The fundamental problem was that the tab code was
    70  not being told what column the text began in, so it
    71  didn&amp;#39;t know where to put the tab stops.  Another problem
    72  was that some of the code assumed that string byte
    73  offsets were the same as column offsets, which is only
    74  true if there are no tabs.
    75  
    76  In the process of fixing this, I cleaned up the arguments
    77  to Fold and ExpandTabs and renamed them Break and
    78  _ExpandTabs so that I could be sure that I found all the
    79  call sites.  I also wanted to verify that ExpandTabs was
    80  not being used from outside intra_region_diff.py.
    81  
    82  
    83  </summary></entry></feed> 	   `
    84  
    85  type 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  
    95  type 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  
   104  type Link struct {
   105  	Rel  string `xml:"rel,attr,omitempty"`
   106  	Href string `xml:"href,attr"`
   107  }
   108  
   109  type Person struct {
   110  	Name     string `xml:"name"`
   111  	URI      string `xml:"uri"`
   112  	Email    string `xml:"email"`
   113  	InnerXML string `xml:",innerxml"`
   114  }
   115  
   116  type Text struct {
   117  	Type string `xml:"type,attr,omitempty"`
   118  	Body string `xml:",chardata"`
   119  }
   120  
   121  var atomFeed = Feed{
   122  	XMLName: Name{"http://www.w3.org/2005/Atom", "feed"},
   123  	Title:   "Code Review - My issues",
   124  	Link: []Link{
   125  		{Rel: "alternate", Href: "http://codereview.appspot.com/"},
   126  		{Rel: "self", Href: "http://codereview.appspot.com/rss/mine/rsc"},
   127  	},
   128  	Id:      "http://codereview.appspot.com/",
   129  	Updated: ParseTime("2009-10-04T01:35:58+00:00"),
   130  	Author: Person{
   131  		Name:     "rietveld<>",
   132  		InnerXML: "<name>rietveld&lt;&gt;</name>",
   133  	},
   134  	Entry: []Entry{
   135  		{
   136  			Title: "rietveld: an attempt at pubsubhubbub\n",
   137  			Link: []Link{
   138  				{Rel: "alternate", Href: "http://codereview.appspot.com/126085"},
   139  			},
   140  			Updated: ParseTime("2009-10-04T01:35:58+00:00"),
   141  			Author: Person{
   142  				Name:     "email-address-removed",
   143  				InnerXML: "<name>email-address-removed</name>",
   144  			},
   145  			Id: "urn:md5:134d9179c41f806be79b3a5f7877d19a",
   146  			Summary: Text{
   147  				Type: "html",
   148  				Body: `
   149    An attempt at adding pubsubhubbub support to Rietveld.
   150  http://code.google.com/p/pubsubhubbub
   151  http://code.google.com/p/rietveld/issues/detail?id=155
   152  
   153  The server side of the protocol is trivial:
   154    1. add a &lt;link rel=&quot;hub&quot; href=&quot;hub-server&quot;&gt; tag to all
   155       feeds that will be pubsubhubbubbed.
   156    2. every time one of those feeds changes, tell the hub
   157       with a simple POST request.
   158  
   159  I have tested this by adding debug prints to a local hub
   160  server and checking that the server got the right publish
   161  requests.
   162  
   163  I can&#39;t quite get the server to work, but I think the bug
   164  is not in my code.  I think that the server expects to be
   165  able to grab the feed and see the feed&#39;s actual URL in
   166  the link rel=&quot;self&quot;, but the default value for that drops
   167  the :port from the URL, and I cannot for the life of me
   168  figure out how to get the Atom generator deep inside
   169  django not to do that, or even where it is doing that,
   170  or even what code is running to generate the Atom feed.
   171  (I thought I knew but I added some assert False statements
   172  and it kept running!)
   173  
   174  Ignoring that particular problem, I would appreciate
   175  feedback on the right way to get the two values at
   176  the 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
   197  http://codereview.appspot.com/116075/diff/1/2
   198  
   199  The fundamental problem was that the tab code was
   200  not being told what column the text began in, so it
   201  didn&#39;t know where to put the tab stops.  Another problem
   202  was that some of the code assumed that string byte
   203  offsets were the same as column offsets, which is only
   204  true if there are no tabs.
   205  
   206  In the process of fixing this, I cleaned up the arguments
   207  to Fold and ExpandTabs and renamed them Break and
   208  _ExpandTabs so that I could be sure that I found all the
   209  call sites.  I also wanted to verify that ExpandTabs was
   210  not being used from outside intra_region_diff.py.
   211  
   212  
   213  `,
   214  			},
   215  		},
   216  	},
   217  }
   218  
   219  const 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  
   241  type PathTestItem struct {
   242  	Value string
   243  }
   244  
   245  type PathTestA struct {
   246  	Items         []PathTestItem `xml:">Item1"`
   247  	Before, After string
   248  }
   249  
   250  type PathTestB struct {
   251  	Other         []PathTestItem `xml:"Items>Item1"`
   252  	Before, After string
   253  }
   254  
   255  type PathTestC struct {
   256  	Values1       []string `xml:"Items>Item1>Value"`
   257  	Values2       []string `xml:"Items>Item2>Value"`
   258  	Before, After string
   259  }
   260  
   261  type PathTestSet struct {
   262  	Item1 []PathTestItem
   263  }
   264  
   265  type PathTestD struct {
   266  	Other         PathTestSet `xml:"Items"`
   267  	Before, After string
   268  }
   269  
   270  type PathTestE struct {
   271  	Underline     string `xml:"Items>_>Value"`
   272  	Before, After string
   273  }
   274  
   275  var 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  
   283  func 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  
   295  type BadPathTestA struct {
   296  	First  string `xml:"items>item1"`
   297  	Other  string `xml:"items>item2"`
   298  	Second string `xml:"items"`
   299  }
   300  
   301  type BadPathTestB struct {
   302  	Other  string `xml:"items>item2>value"`
   303  	First  string `xml:"items>item1"`
   304  	Second string `xml:"items>item1>value"`
   305  }
   306  
   307  type BadPathTestC struct {
   308  	First  string
   309  	Second string `xml:"First"`
   310  }
   311  
   312  type BadPathTestD struct {
   313  	BadPathEmbeddedA
   314  	BadPathEmbeddedB
   315  }
   316  
   317  type BadPathEmbeddedA struct {
   318  	First string
   319  }
   320  
   321  type BadPathEmbeddedB struct {
   322  	Second string `xml:"First"`
   323  }
   324  
   325  var 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  
   334  func 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  
   343  const OK = "OK"
   344  const withoutNameTypeData = `
   345  <?xml version="1.0" charset="utf-8"?>
   346  <Test3 Attr="OK" />`
   347  
   348  type TestThree struct {
   349  	XMLName Name   `xml:"Test3"`
   350  	Attr    string `xml:",attr"`
   351  }
   352  
   353  func 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  
   363  func 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  
   407  type 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  
   412  var 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  
   467  func 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  
   489  func 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  
   517  func 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  
   530  type TableAttrs struct {
   531  	TAttr TAttr
   532  }
   533  
   534  type 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  
   544  var 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  
   612  func 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  
   634  func 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  
   656  type MyCharData struct {
   657  	body string
   658  }
   659  
   660  func (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  
   676  var _ Unmarshaler = (*MyCharData)(nil)
   677  
   678  func (m *MyCharData) UnmarshalXMLAttr(attr Attr) error {
   679  	panic("must not call")
   680  }
   681  
   682  type MyAttr struct {
   683  	attr string
   684  }
   685  
   686  func (m *MyAttr) UnmarshalXMLAttr(attr Attr) error {
   687  	m.attr = attr.Value
   688  	return nil
   689  }
   690  
   691  var _ UnmarshalerAttr = (*MyAttr)(nil)
   692  
   693  type MyStruct struct {
   694  	Data *MyCharData
   695  	Attr *MyAttr `xml:",attr"`
   696  
   697  	Data2 MyCharData
   698  	Attr2 MyAttr `xml:",attr"`
   699  }
   700  
   701  func 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  
   719  type Pea struct {
   720  	Cotelydon string
   721  }
   722  
   723  type Pod struct {
   724  	Pea interface{} `xml:"Pea"`
   725  }
   726  
   727  // https://golang.org/issue/6836
   728  func 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  }