LEFT | RIGHT |
1 // Copyright 2011 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 ( | 1 import ( |
8 "bytes" | 2 "bytes" |
9 "errors" | 3 "errors" |
10 "io" | 4 "io" |
11 "reflect" | 5 "reflect" |
12 "strconv" | 6 "strconv" |
13 "strings" | |
14 "testing" | |
15 "time" | |
16 ) | |
17 | |
18 type DriveType int | |
19 | |
20 const ( | |
21 HyperDrive DriveType = iota | |
22 ImprobabilityDrive | |
23 ) | |
24 | |
25 type Passenger struct { | |
26 Name []string `xml:"name"` | |
27 Weight float32 `xml:"weight"` | |
28 } | |
29 | |
30 type Ship struct { | |
31 XMLName struct{} `xml:"spaceship"` | |
32 | |
33 Name string `xml:"name,attr"` | |
34 Pilot string `xml:"pilot,attr"` | |
35 Drive DriveType `xml:"drive"` | |
36 Age uint `xml:"age"` | |
37 Passenger []*Passenger `xml:"passenger"` | |
38 secret string | |
39 } | |
40 | |
41 type NamedType string | |
42 | |
43 type Port struct { | |
44 XMLName struct{} `xml:"port"` | |
45 Type string `xml:"type,attr,omitempty"` | |
46 Comment string `xml:",comment"` | |
47 Number string `xml:",chardata"` | |
48 } | |
49 | |
50 type Domain struct { | |
51 XMLName struct{} `xml:"domain"` | |
52 Country string `xml:",attr,omitempty"` | |
53 Name []byte `xml:",chardata"` | |
54 Comment []byte `xml:",comment"` | |
55 } | |
56 | |
57 type Book struct { | |
58 XMLName struct{} `xml:"book"` | |
59 Title string `xml:",chardata"` | |
60 } | |
61 | |
62 type SecretAgent struct { | |
63 XMLName struct{} `xml:"agent"` | |
64 Handle string `xml:"handle,attr"` | |
65 Identity string | |
66 Obfuscate string `xml:",innerxml"` | |
67 } | |
68 | |
69 type NestedItems struct { | |
70 XMLName struct{} `xml:"result"` | |
71 Items []string `xml:">item"` | |
72 Item1 []string `xml:"Items>item1"` | |
73 } | |
74 | |
75 type NestedOrder struct { | |
76 XMLName struct{} `xml:"result"` | |
77 Field1 string `xml:"parent>c"` | |
78 Field2 string `xml:"parent>b"` | |
79 Field3 string `xml:"parent>a"` | |
80 } | |
81 | |
82 type MixedNested struct { | |
83 XMLName struct{} `xml:"result"` | |
84 A string `xml:"parent1>a"` | |
85 B string `xml:"b"` | |
86 C string `xml:"parent1>parent2>c"` | |
87 D string `xml:"parent1>d"` | |
88 } | |
89 | |
90 type NilTest struct { | |
91 A interface{} `xml:"parent1>parent2>a"` | |
92 B interface{} `xml:"parent1>b"` | |
93 C interface{} `xml:"parent1>parent2>c"` | |
94 } | |
95 | |
96 type Service struct { | |
97 XMLName struct{} `xml:"service"` | |
98 Domain *Domain `xml:"host>domain"` | |
99 Port *Port `xml:"host>port"` | |
100 Extra1 interface{} | |
101 Extra2 interface{} `xml:"host>extra2"` | |
102 } | |
103 | |
104 var nilStruct *Ship | |
105 | |
106 type EmbedA struct { | |
107 EmbedC | |
108 EmbedB EmbedB | |
109 FieldA string | |
110 } | |
111 | |
112 type EmbedB struct { | |
113 FieldB string | |
114 *EmbedC | |
115 } | |
116 | |
117 type EmbedC struct { | |
118 FieldA1 string `xml:"FieldA>A1"` | |
119 FieldA2 string `xml:"FieldA>A2"` | |
120 FieldB string | |
121 FieldC string | |
122 } | |
123 | |
124 type NameCasing struct { | |
125 XMLName struct{} `xml:"casing"` | |
126 Xy string | |
127 XY string | |
128 XyA string `xml:"Xy,attr"` | |
129 XYA string `xml:"XY,attr"` | |
130 } | |
131 | |
132 type NamePrecedence struct { | |
133 XMLName Name `xml:"Parent"` | |
134 FromTag XMLNameWithoutTag `xml:"InTag"` | |
135 FromNameVal XMLNameWithoutTag | |
136 FromNameTag XMLNameWithTag | |
137 InFieldName string | |
138 } | |
139 | |
140 type XMLNameWithTag struct { | |
141 XMLName Name `xml:"InXMLNameTag"` | |
142 Value string `xml:",chardata"` | |
143 } | |
144 | |
145 type XMLNameWithoutTag struct { | |
146 XMLName Name | |
147 Value string `xml:",chardata"` | |
148 } | |
149 | |
150 type NameInField struct { | |
151 Foo Name `xml:"ns foo"` | |
152 } | |
153 | |
154 type AttrTest struct { | |
155 Int int `xml:",attr"` | |
156 Named int `xml:"int,attr"` | |
157 Float float64 `xml:",attr"` | |
158 Uint8 uint8 `xml:",attr"` | |
159 Bool bool `xml:",attr"` | |
160 Str string `xml:",attr"` | |
161 Bytes []byte `xml:",attr"` | |
162 } | |
163 | |
164 type OmitAttrTest struct { | |
165 Int int `xml:",attr,omitempty"` | |
166 Named int `xml:"int,attr,omitempty"` | |
167 Float float64 `xml:",attr,omitempty"` | |
168 Uint8 uint8 `xml:",attr,omitempty"` | |
169 Bool bool `xml:",attr,omitempty"` | |
170 Str string `xml:",attr,omitempty"` | |
171 Bytes []byte `xml:",attr,omitempty"` | |
172 } | |
173 | |
174 type OmitFieldTest struct { | |
175 Int int `xml:",omitempty"` | |
176 Named int `xml:"int,omitempty"` | |
177 Float float64 `xml:",omitempty"` | |
178 Uint8 uint8 `xml:",omitempty"` | |
179 Bool bool `xml:",omitempty"` | |
180 Str string `xml:",omitempty"` | |
181 Bytes []byte `xml:",omitempty"` | |
182 Ptr *PresenceTest `xml:",omitempty"` | |
183 } | |
184 | |
185 type AnyTest struct { | |
186 XMLName struct{} `xml:"a"` | |
187 Nested string `xml:"nested>value"` | |
188 AnyField AnyHolder `xml:",any"` | |
189 } | |
190 | |
191 type AnyHolder struct { | |
192 XMLName Name | |
193 XML string `xml:",innerxml"` | |
194 } | |
195 | |
196 type RecurseA struct { | |
197 A string | |
198 B *RecurseB | |
199 } | |
200 | |
201 type RecurseB struct { | |
202 A *RecurseA | |
203 B string | |
204 } | |
205 | |
206 type PresenceTest struct { | |
207 Exists *struct{} | |
208 } | |
209 | |
210 type IgnoreTest struct { | |
211 PublicSecret string `xml:"-"` | |
212 } | |
213 | |
214 type MyBytes []byte | |
215 | |
216 type Data struct { | |
217 Bytes []byte | |
218 Attr []byte `xml:",attr"` | |
219 Custom MyBytes | |
220 } | |
221 | |
222 type Plain struct { | |
223 V interface{} | |
224 } | |
225 | |
226 type NsRoot struct { | |
227 HTable HtmlTable `xml:"h=http://www.w3.org/TR/html4/ table"` | |
228 FTable FurnTable `xml:"http://www.w3schools.com/furniture table"` | |
229 } | |
230 | |
231 type HtmlTable struct { | |
232 Rows []HtmlTr `xml:"tr"` | |
233 } | |
234 | |
235 type HtmlTr struct { | |
236 Td []string `xml:"td"` | |
237 } | |
238 | |
239 type FurnTable struct { | |
240 Name string `xml:"name"` | |
241 Width int `xml:"width"` | |
242 Length int `xml:"length"` | |
243 } | |
244 | |
245 // Unless explicitly stated as such (or *Plain), all of the | |
246 // tests below are two-way tests. When introducing new tests, | |
247 // please try to make them two-way as well to ensure that | |
248 // marshalling and unmarshalling are as symmetrical as feasible. | |
249 var marshalTests = []struct { | |
250 Value interface{} | |
251 ExpectXML string | |
252 MarshalOnly bool | |
253 UnmarshalOnly bool | |
254 Namespaces map[string]string | |
255 Xmlns string | |
256 }{ | |
257 // Test nil marshals to nothing | |
258 {Value: nil, ExpectXML: ``, MarshalOnly: true}, | |
259 {Value: nilStruct, ExpectXML: ``, MarshalOnly: true}, | |
260 | |
261 // Test value types | |
262 {Value: &Plain{true}, ExpectXML: `<Plain><V>true</V></Plain>`}, | |
263 {Value: &Plain{false}, ExpectXML: `<Plain><V>false</V></Plain>`}, | |
264 {Value: &Plain{int(42)}, ExpectXML: `<Plain><V>42</V></Plain>`}, | |
265 {Value: &Plain{int8(42)}, ExpectXML: `<Plain><V>42</V></Plain>`}, | |
266 {Value: &Plain{int16(42)}, ExpectXML: `<Plain><V>42</V></Plain>`}, | |
267 {Value: &Plain{int32(42)}, ExpectXML: `<Plain><V>42</V></Plain>`}, | |
268 {Value: &Plain{uint(42)}, ExpectXML: `<Plain><V>42</V></Plain>`}, | |
269 {Value: &Plain{uint8(42)}, ExpectXML: `<Plain><V>42</V></Plain>`}, | |
270 {Value: &Plain{uint16(42)}, ExpectXML: `<Plain><V>42</V></Plain>`}, | |
271 {Value: &Plain{uint32(42)}, ExpectXML: `<Plain><V>42</V></Plain>`}, | |
272 {Value: &Plain{float32(1.25)}, ExpectXML: `<Plain><V>1.25</V></Plain>`}, | |
273 {Value: &Plain{float64(1.25)}, ExpectXML: `<Plain><V>1.25</V></Plain>`}, | |
274 {Value: &Plain{uintptr(0xFFDD)}, ExpectXML: `<Plain><V>65501</V></Plain>
`}, | |
275 {Value: &Plain{"gopher"}, ExpectXML: `<Plain><V>gopher</V></Plain>`}, | |
276 {Value: &Plain{[]byte("gopher")}, ExpectXML: `<Plain><V>gopher</V></Plai
n>`}, | |
277 {Value: &Plain{"</>"}, ExpectXML: `<Plain><V></></V></Plain>`}, | |
278 {Value: &Plain{[]byte("</>")}, ExpectXML: `<Plain><V></></V></Plai
n>`}, | |
279 {Value: &Plain{[3]byte{'<', '/', '>'}}, ExpectXML: `<Plain><V></><
/V></Plain>`}, | |
280 {Value: &Plain{NamedType("potato")}, ExpectXML: `<Plain><V>potato</V></P
lain>`}, | |
281 {Value: &Plain{[]int{1, 2, 3}}, ExpectXML: `<Plain><V>1</V><V>2</V><V>3<
/V></Plain>`}, | |
282 {Value: &Plain{[3]int{1, 2, 3}}, ExpectXML: `<Plain><V>1</V><V>2</V><V>3
</V></Plain>`}, | |
283 | |
284 // Test time. | |
285 { | |
286 Value: &Plain{time.Unix(1e9, 123456789).UTC()}, | |
287 ExpectXML: `<Plain><V>2001-09-09T01:46:40.123456789Z</V></Plain>
`, | |
288 }, | |
289 | |
290 // A pointer to struct{} may be used to test for an element's presence. | |
291 { | |
292 Value: &PresenceTest{new(struct{})}, | |
293 ExpectXML: `<PresenceTest><Exists></Exists></PresenceTest>`, | |
294 }, | |
295 { | |
296 Value: &PresenceTest{}, | |
297 ExpectXML: `<PresenceTest></PresenceTest>`, | |
298 }, | |
299 | |
300 // A pointer to struct{} may be used to test for an element's presence. | |
301 { | |
302 Value: &PresenceTest{new(struct{})}, | |
303 ExpectXML: `<PresenceTest><Exists></Exists></PresenceTest>`, | |
304 }, | |
305 { | |
306 Value: &PresenceTest{}, | |
307 ExpectXML: `<PresenceTest></PresenceTest>`, | |
308 }, | |
309 | |
310 // A []byte field is only nil if the element was not found. | |
311 { | |
312 Value: &Data{}, | |
313 ExpectXML: `<Data></Data>`, | |
314 UnmarshalOnly: true, | |
315 }, | |
316 { | |
317 Value: &Data{Bytes: []byte{}, Custom: MyBytes{}, Attr: [
]byte{}}, | |
318 ExpectXML: `<Data Attr=""><Bytes></Bytes><Custom></Custom></
Data>`, | |
319 UnmarshalOnly: true, | |
320 }, | |
321 | |
322 // Check that []byte works, including named []byte types. | |
323 { | |
324 Value: &Data{Bytes: []byte("ab"), Custom: MyBytes("cd"), Att
r: []byte{'v'}}, | |
325 ExpectXML: `<Data Attr="v"><Bytes>ab</Bytes><Custom>cd</Custom><
/Data>`, | |
326 }, | |
327 | |
328 // Test innerxml | |
329 { | |
330 Value: &SecretAgent{ | |
331 Handle: "007", | |
332 Identity: "James Bond", | |
333 Obfuscate: "<redacted/>", | |
334 }, | |
335 ExpectXML: `<agent handle="007"><Identity>James Bond</Identity
><redacted/></agent>`, | |
336 MarshalOnly: true, | |
337 }, | |
338 { | |
339 Value: &SecretAgent{ | |
340 Handle: "007", | |
341 Identity: "James Bond", | |
342 Obfuscate: "<Identity>James Bond</Identity><redacted/>", | |
343 }, | |
344 ExpectXML: `<agent handle="007"><Identity>James Bond</Identi
ty><redacted/></agent>`, | |
345 UnmarshalOnly: true, | |
346 }, | |
347 | |
348 // Test structs | |
349 {Value: &Port{Type: "ssl", Number: "443"}, ExpectXML: `<port type="ssl">
443</port>`}, | |
350 {Value: &Port{Number: "443"}, ExpectXML: `<port>443</port>`}, | |
351 {Value: &Port{Type: "<unix>"}, ExpectXML: `<port type="<unix>"></p
ort>`}, | |
352 {Value: &Port{Number: "443", Comment: "https"}, ExpectXML: `<port><!--ht
tps-->443</port>`}, | |
353 {Value: &Port{Number: "443", Comment: "add space-"}, ExpectXML: `<port><
!--add space- -->443</port>`, MarshalOnly: true}, | |
354 {Value: &Domain{Name: []byte("google.com&friends")}, ExpectXML: `<domain
>google.com&friends</domain>`}, | |
355 {Value: &Domain{Name: []byte("google.com"), Comment: []byte(" &friends "
)}, ExpectXML: `<domain>google.com<!-- &friends --></domain>`}, | |
356 {Value: &Book{Title: "Pride & Prejudice"}, ExpectXML: `<book>Pride &
Prejudice</book>`}, | |
357 {Value: atomValue, ExpectXML: atomXml}, | |
358 { | |
359 Value: &Ship{ | |
360 Name: "Heart of Gold", | |
361 Pilot: "Computer", | |
362 Age: 1, | |
363 Drive: ImprobabilityDrive, | |
364 Passenger: []*Passenger{ | |
365 { | |
366 Name: []string{"Zaphod", "Beeblebrox"}
, | |
367 Weight: 7.25, | |
368 }, | |
369 { | |
370 Name: []string{"Trisha", "McMillen"}, | |
371 Weight: 5.5, | |
372 }, | |
373 { | |
374 Name: []string{"Ford", "Prefect"}, | |
375 Weight: 7, | |
376 }, | |
377 { | |
378 Name: []string{"Arthur", "Dent"}, | |
379 Weight: 6.75, | |
380 }, | |
381 }, | |
382 }, | |
383 ExpectXML: `<spaceship name="Heart of Gold" pilot="Computer">` + | |
384 `<drive>` + strconv.Itoa(int(ImprobabilityDrive)) + `</d
rive>` + | |
385 `<age>1</age>` + | |
386 `<passenger>` + | |
387 `<name>Zaphod</name>` + | |
388 `<name>Beeblebrox</name>` + | |
389 `<weight>7.25</weight>` + | |
390 `</passenger>` + | |
391 `<passenger>` + | |
392 `<name>Trisha</name>` + | |
393 `<name>McMillen</name>` + | |
394 `<weight>5.5</weight>` + | |
395 `</passenger>` + | |
396 `<passenger>` + | |
397 `<name>Ford</name>` + | |
398 `<name>Prefect</name>` + | |
399 `<weight>7</weight>` + | |
400 `</passenger>` + | |
401 `<passenger>` + | |
402 `<name>Arthur</name>` + | |
403 `<name>Dent</name>` + | |
404 `<weight>6.75</weight>` + | |
405 `</passenger>` + | |
406 `</spaceship>`, | |
407 }, | |
408 | |
409 // Test a>b | |
410 { | |
411 Value: &NestedItems{Items: nil, Item1: nil}, | |
412 ExpectXML: `<result>` + | |
413 `<Items>` + | |
414 `</Items>` + | |
415 `</result>`, | |
416 }, | |
417 { | |
418 Value: &NestedItems{Items: []string{}, Item1: []string{}}, | |
419 ExpectXML: `<result>` + | |
420 `<Items>` + | |
421 `</Items>` + | |
422 `</result>`, | |
423 MarshalOnly: true, | |
424 }, | |
425 { | |
426 Value: &NestedItems{Items: nil, Item1: []string{"A"}}, | |
427 ExpectXML: `<result>` + | |
428 `<Items>` + | |
429 `<item1>A</item1>` + | |
430 `</Items>` + | |
431 `</result>`, | |
432 }, | |
433 { | |
434 Value: &NestedItems{Items: []string{"A", "B"}, Item1: nil}, | |
435 ExpectXML: `<result>` + | |
436 `<Items>` + | |
437 `<item>A</item>` + | |
438 `<item>B</item>` + | |
439 `</Items>` + | |
440 `</result>`, | |
441 }, | |
442 { | |
443 Value: &NestedItems{Items: []string{"A", "B"}, Item1: []string{"
C"}}, | |
444 ExpectXML: `<result>` + | |
445 `<Items>` + | |
446 `<item>A</item>` + | |
447 `<item>B</item>` + | |
448 `<item1>C</item1>` + | |
449 `</Items>` + | |
450 `</result>`, | |
451 }, | |
452 { | |
453 Value: &NestedOrder{Field1: "C", Field2: "B", Field3: "A"}, | |
454 ExpectXML: `<result>` + | |
455 `<parent>` + | |
456 `<c>C</c>` + | |
457 `<b>B</b>` + | |
458 `<a>A</a>` + | |
459 `</parent>` + | |
460 `</result>`, | |
461 }, | |
462 { | |
463 Value: &NilTest{A: "A", B: nil, C: "C"}, | |
464 ExpectXML: `<NilTest>` + | |
465 `<parent1>` + | |
466 `<parent2><a>A</a></parent2>` + | |
467 `<parent2><c>C</c></parent2>` + | |
468 `</parent1>` + | |
469 `</NilTest>`, | |
470 MarshalOnly: true, // Uses interface{} | |
471 }, | |
472 { | |
473 Value: &MixedNested{A: "A", B: "B", C: "C", D: "D"}, | |
474 ExpectXML: `<result>` + | |
475 `<parent1><a>A</a></parent1>` + | |
476 `<b>B</b>` + | |
477 `<parent1>` + | |
478 `<parent2><c>C</c></parent2>` + | |
479 `<d>D</d>` + | |
480 `</parent1>` + | |
481 `</result>`, | |
482 }, | |
483 { | |
484 Value: &Service{Port: &Port{Number: "80"}}, | |
485 ExpectXML: `<service><host><port>80</port></host></service>`, | |
486 }, | |
487 { | |
488 Value: &Service{}, | |
489 ExpectXML: `<service></service>`, | |
490 }, | |
491 { | |
492 Value: &Service{Port: &Port{Number: "80"}, Extra1: "A", Extra2:
"B"}, | |
493 ExpectXML: `<service>` + | |
494 `<host><port>80</port></host>` + | |
495 `<Extra1>A</Extra1>` + | |
496 `<host><extra2>B</extra2></host>` + | |
497 `</service>`, | |
498 MarshalOnly: true, | |
499 }, | |
500 { | |
501 Value: &Service{Port: &Port{Number: "80"}, Extra2: "example"}, | |
502 ExpectXML: `<service>` + | |
503 `<host><port>80</port></host>` + | |
504 `<host><extra2>example</extra2></host>` + | |
505 `</service>`, | |
506 MarshalOnly: true, | |
507 }, | |
508 | |
509 // Test struct embedding | |
510 { | |
511 Value: &EmbedA{ | |
512 EmbedC: EmbedC{ | |
513 FieldA1: "", // Shadowed by A.A | |
514 FieldA2: "", // Shadowed by A.A | |
515 FieldB: "A.C.B", | |
516 FieldC: "A.C.C", | |
517 }, | |
518 EmbedB: EmbedB{ | |
519 FieldB: "A.B.B", | |
520 EmbedC: &EmbedC{ | |
521 FieldA1: "A.B.C.A1", | |
522 FieldA2: "A.B.C.A2", | |
523 FieldB: "", // Shadowed by A.B.B | |
524 FieldC: "A.B.C.C", | |
525 }, | |
526 }, | |
527 FieldA: "A.A", | |
528 }, | |
529 ExpectXML: `<EmbedA>` + | |
530 `<FieldB>A.C.B</FieldB>` + | |
531 `<FieldC>A.C.C</FieldC>` + | |
532 `<EmbedB>` + | |
533 `<FieldB>A.B.B</FieldB>` + | |
534 `<FieldA>` + | |
535 `<A1>A.B.C.A1</A1>` + | |
536 `<A2>A.B.C.A2</A2>` + | |
537 `</FieldA>` + | |
538 `<FieldC>A.B.C.C</FieldC>` + | |
539 `</EmbedB>` + | |
540 `<FieldA>A.A</FieldA>` + | |
541 `</EmbedA>`, | |
542 }, | |
543 | |
544 // Test that name casing matters | |
545 { | |
546 Value: &NameCasing{Xy: "mixed", XY: "upper", XyA: "mixedA",
XYA: "upperA"}, | |
547 ExpectXML: `<casing Xy="mixedA" XY="upperA"><Xy>mixed</Xy><XY>up
per</XY></casing>`, | |
548 }, | |
549 | |
550 // Test the order in which the XML element name is chosen | |
551 { | |
552 Value: &NamePrecedence{ | |
553 FromTag: XMLNameWithoutTag{Value: "A"}, | |
554 FromNameVal: XMLNameWithoutTag{XMLName: Name{Local: "InX
MLName"}, Value: "B"}, | |
555 FromNameTag: XMLNameWithTag{Value: "C"}, | |
556 InFieldName: "D", | |
557 }, | |
558 ExpectXML: `<Parent>` + | |
559 `<InTag>A</InTag>` + | |
560 `<InXMLName>B</InXMLName>` + | |
561 `<InXMLNameTag>C</InXMLNameTag>` + | |
562 `<InFieldName>D</InFieldName>` + | |
563 `</Parent>`, | |
564 MarshalOnly: true, | |
565 }, | |
566 { | |
567 Value: &NamePrecedence{ | |
568 XMLName: Name{Local: "Parent"}, | |
569 FromTag: XMLNameWithoutTag{XMLName: Name{Local: "InT
ag"}, Value: "A"}, | |
570 FromNameVal: XMLNameWithoutTag{XMLName: Name{Local: "Fro
mNameVal"}, Value: "B"}, | |
571 FromNameTag: XMLNameWithTag{XMLName: Name{Local: "InXMLN
ameTag"}, Value: "C"}, | |
572 InFieldName: "D", | |
573 }, | |
574 ExpectXML: `<Parent>` + | |
575 `<InTag>A</InTag>` + | |
576 `<FromNameVal>B</FromNameVal>` + | |
577 `<InXMLNameTag>C</InXMLNameTag>` + | |
578 `<InFieldName>D</InFieldName>` + | |
579 `</Parent>`, | |
580 UnmarshalOnly: true, | |
581 }, | |
582 | |
583 // xml.Name works in a plain field as well. | |
584 { | |
585 Value: &NameInField{Name{Space: "ns", Local: "foo"}}, | |
586 ExpectXML: `<NameInField><foo xmlns="ns"></foo></NameInField>`, | |
587 }, | |
588 { | |
589 Value: &NameInField{Name{Space: "ns", Local: "foo"}}, | |
590 ExpectXML: `<NameInField><foo xmlns="ns"><ignore></ignore></
foo></NameInField>`, | |
591 UnmarshalOnly: true, | |
592 }, | |
593 | |
594 // Marshaling zero xml.Name uses the tag or field name. | |
595 { | |
596 Value: &NameInField{}, | |
597 ExpectXML: `<NameInField><foo xmlns="ns"></foo></NameInField>`
, | |
598 MarshalOnly: true, | |
599 }, | |
600 | |
601 // Test attributes | |
602 { | |
603 Value: &AttrTest{ | |
604 Int: 8, | |
605 Named: 9, | |
606 Float: 23.5, | |
607 Uint8: 255, | |
608 Bool: true, | |
609 Str: "str", | |
610 Bytes: []byte("byt"), | |
611 }, | |
612 ExpectXML: `<AttrTest Int="8" int="9" Float="23.5" Uint8="255"`
+ | |
613 ` Bool="true" Str="str" Bytes="byt"></AttrTest>`, | |
614 }, | |
615 { | |
616 Value: &AttrTest{Bytes: []byte{}}, | |
617 ExpectXML: `<AttrTest Int="0" int="0" Float="0" Uint8="0"` + | |
618 ` Bool="false" Str="" Bytes=""></AttrTest>`, | |
619 }, | |
620 { | |
621 Value: &OmitAttrTest{ | |
622 Int: 8, | |
623 Named: 9, | |
624 Float: 23.5, | |
625 Uint8: 255, | |
626 Bool: true, | |
627 Str: "str", | |
628 Bytes: []byte("byt"), | |
629 }, | |
630 ExpectXML: `<OmitAttrTest Int="8" int="9" Float="23.5" Uint8="25
5"` + | |
631 ` Bool="true" Str="str" Bytes="byt"></OmitAttrTest>`, | |
632 }, | |
633 { | |
634 Value: &OmitAttrTest{}, | |
635 ExpectXML: `<OmitAttrTest></OmitAttrTest>`, | |
636 }, | |
637 | |
638 // omitempty on fields | |
639 { | |
640 Value: &OmitFieldTest{ | |
641 Int: 8, | |
642 Named: 9, | |
643 Float: 23.5, | |
644 Uint8: 255, | |
645 Bool: true, | |
646 Str: "str", | |
647 Bytes: []byte("byt"), | |
648 Ptr: &PresenceTest{}, | |
649 }, | |
650 ExpectXML: `<OmitFieldTest>` + | |
651 `<Int>8</Int>` + | |
652 `<int>9</int>` + | |
653 `<Float>23.5</Float>` + | |
654 `<Uint8>255</Uint8>` + | |
655 `<Bool>true</Bool>` + | |
656 `<Str>str</Str>` + | |
657 `<Bytes>byt</Bytes>` + | |
658 `<Ptr></Ptr>` + | |
659 `</OmitFieldTest>`, | |
660 }, | |
661 { | |
662 Value: &OmitFieldTest{}, | |
663 ExpectXML: `<OmitFieldTest></OmitFieldTest>`, | |
664 }, | |
665 | |
666 // Test ",any" | |
667 { | |
668 ExpectXML: `<a><nested><value>known</value></nested><other><sub>
unknown</sub></other></a>`, | |
669 Value: &AnyTest{ | |
670 Nested: "known", | |
671 AnyField: AnyHolder{ | |
672 XMLName: Name{Local: "other"}, | |
673 XML: "<sub>unknown</sub>", | |
674 }, | |
675 }, | |
676 UnmarshalOnly: true, | |
677 }, | |
678 { | |
679 Value: &AnyTest{Nested: "known", AnyField: AnyHolder{XML:
"<unknown/>"}}, | |
680 ExpectXML: `<a><nested><value>known</value></nested></a>`, | |
681 MarshalOnly: true, | |
682 }, | |
683 | |
684 // Test recursive types. | |
685 { | |
686 Value: &RecurseA{ | |
687 A: "a1", | |
688 B: &RecurseB{ | |
689 A: &RecurseA{"a2", nil}, | |
690 B: "b1", | |
691 }, | |
692 }, | |
693 ExpectXML: `<RecurseA><A>a1</A><B><A><A>a2</A></A><B>b1</B></B><
/RecurseA>`, | |
694 }, | |
695 | |
696 // Test ignoring fields via "-" tag | |
697 { | |
698 ExpectXML: `<IgnoreTest></IgnoreTest>`, | |
699 Value: &IgnoreTest{}, | |
700 }, | |
701 { | |
702 ExpectXML: `<IgnoreTest></IgnoreTest>`, | |
703 Value: &IgnoreTest{PublicSecret: "can't tell"}, | |
704 MarshalOnly: true, | |
705 }, | |
706 { | |
707 ExpectXML: `<IgnoreTest><PublicSecret>ignore me</PublicSecre
t></IgnoreTest>`, | |
708 Value: &IgnoreTest{}, | |
709 UnmarshalOnly: true, | |
710 }, | |
711 | |
712 // Test XML namespaces. | |
713 { | |
714 ExpectXML: `<NsRoot>` + | |
715 `<h:table xmlns:h="http://www.w3.org/TR/html4/">` + | |
716 `<h:tr>` + | |
717 `<h:td>Apples</h:td>` + | |
718 `<h:td>Bananas</h:td>` + | |
719 `</h:tr>` + | |
720 `</h:table>` + | |
721 `<table xmlns="http://www.w3schools.com/furniture">` + | |
722 `<name>African Coffee Table</name>` + | |
723 `<width>80</width>` + | |
724 `<length>120</length>` + | |
725 `</table>` + | |
726 `</NsRoot>`, | |
727 Value: &NsRoot{HTable: HtmlTable{Rows: []HtmlTr{HtmlTr{Td: []str
ing{"Apples", "Bananas"}}}}, | |
728 FTable: FurnTable{Name: "African Coffee Table", | |
729 Width: 80, Length: 120}, | |
730 }, | |
731 }, | |
732 { | |
733 ExpectXML: `<NsRoot>` + | |
734 `<h:table>` + | |
735 `<h:tr>` + | |
736 `<h:td>Apples</h:td>` + | |
737 `<h:td>Bananas</h:td>` + | |
738 `</h:tr>` + | |
739 `</h:table>` + | |
740 `<f:table>` + | |
741 `<f:name>African Coffee Table</f:name>` + | |
742 `<f:width>80</f:width>` + | |
743 `<f:length>120</f:length>` + | |
744 `</f:table>` + | |
745 `</NsRoot>`, | |
746 Value: &NsRoot{HTable: HtmlTable{Rows: []HtmlTr{HtmlTr{Td: []str
ing{"Apples", "Bananas"}}}}, | |
747 FTable: FurnTable{Name: "African Coffee Table", | |
748 Width: 80, Length: 120}, | |
749 }, | |
750 Namespaces: map[string]string{"h": "http://www.w3.org/TR/html4/"
, | |
751 "f": "http://www.w3schools.com/furniture"}, | |
752 }, | |
753 | |
754 // Test escaping. | |
755 { | |
756 ExpectXML: `<a><nested><value>dquote: "; squote: '; ampe
rsand: &; less: <; greater: >;</value></nested></a>`, | |
757 Value: &AnyTest{ | |
758 Nested: `dquote: "; squote: '; ampersand: &; less: <; gr
eater: >;`, | |
759 }, | |
760 }, | |
761 { | |
762 ExpectXML: `<a><nested><value>newline: 
; cr: 
; tab: &#
x9;;</value></nested></a>`, | |
763 Value: &AnyTest{ | |
764 Nested: "newline: \n; cr: \r; tab: \t;", | |
765 }, | |
766 }, | |
767 { | |
768 ExpectXML: "<a><nested><value>1\r2\r\n3\n\r4\n5</value></nested>
</a>", | |
769 Value: &AnyTest{ | |
770 Nested: "1\n2\n3\n\n4\n5", | |
771 }, | |
772 UnmarshalOnly: true, | |
773 }, | |
774 } | |
775 | |
776 func TestMarshal(t *testing.T) { | |
777 for idx, test := range marshalTests { | |
778 if test.UnmarshalOnly { | |
779 continue | |
780 } | |
781 var data []byte | |
782 var err error | |
783 if test.Namespaces == nil && test.Xmlns == "" { | |
784 data, err = Marshal(test.Value) | |
785 } else { | |
786 var w bytes.Buffer | |
787 enc := NewEncoder(&w) | |
788 enc.Context.Xmlns = test.Xmlns | |
789 for k, v := range test.Namespaces { | |
790 enc.Context.Map[v] = k | |
791 } | |
792 err = enc.Encode(test.Value) | |
793 data = w.Bytes() | |
794 } | |
795 if err != nil { | |
796 t.Errorf("#%d: Error: %s", idx, err) | |
797 continue | |
798 } | |
799 if got, want := string(data), test.ExpectXML; got != want { | |
800 if strings.Contains(want, "\n") { | |
801 t.Errorf("#%d: marshal(%#v):\nHAVE:\n%s\nWANT:\n
%s", idx, test.Value, got, want) | |
802 } else { | |
803 t.Errorf("#%d: marshal(%#v):\nhave %#q\nwant %#q
", idx, test.Value, got, want) | |
804 } | |
805 } | |
806 } | |
807 } | |
808 | |
809 var marshalErrorTests = []struct { | |
810 Value interface{} | |
811 Err string | |
812 Kind reflect.Kind | |
813 }{ | |
814 { | |
815 Value: make(chan bool), | |
816 Err: "xml: unsupported type: chan bool", | |
817 Kind: reflect.Chan, | |
818 }, | |
819 { | |
820 Value: map[string]string{ | |
821 "question": "What do you get when you multiply six by ni
ne?", | |
822 "answer": "42", | |
823 }, | |
824 Err: "xml: unsupported type: map[string]string", | |
825 Kind: reflect.Map, | |
826 }, | |
827 { | |
828 Value: map[*Ship]bool{nil: false}, | |
829 Err: "xml: unsupported type: map[*xml.Ship]bool", | |
830 Kind: reflect.Map, | |
831 }, | |
832 { | |
833 Value: &Domain{Comment: []byte("f--bar")}, | |
834 Err: `xml: comments must not contain "--"`, | |
835 }, | |
836 } | |
837 | |
838 func TestMarshalErrors(t *testing.T) { | |
839 for idx, test := range marshalErrorTests { | |
840 _, err := Marshal(test.Value) | |
841 if err == nil || err.Error() != test.Err { | |
842 t.Errorf("#%d: marshal(%#v) = [error] %v, want %v", idx,
test.Value, err, test.Err) | |
843 } | |
844 if test.Kind != reflect.Invalid { | |
845 if kind := err.(*UnsupportedTypeError).Type.Kind(); kind
!= test.Kind { | |
846 t.Errorf("#%d: marshal(%#v) = [error kind] %s, w
ant %s", idx, test.Value, kind, test.Kind) | |
847 } | |
848 } | |
849 } | |
850 } | |
851 | |
852 // Do invertibility testing on the various structures that we test | |
853 func TestUnmarshal(t *testing.T) { | |
854 for i, test := range marshalTests { | |
855 if test.MarshalOnly { | |
856 continue | |
857 } | |
858 if _, ok := test.Value.(*Plain); ok { | |
859 continue | |
860 } | |
861 | |
862 vt := reflect.TypeOf(test.Value) | |
863 dest := reflect.New(vt.Elem()).Interface() | |
864 var err error | |
865 if test.Namespaces == nil && test.Xmlns == "" { | |
866 err = Unmarshal([]byte(test.ExpectXML), dest) | |
867 } else { | |
868 buf := bytes.NewBuffer([]byte(test.ExpectXML)) | |
869 dec := NewDecoder(buf) | |
870 dec.Context.Xmlns = test.Xmlns | |
871 for k, v := range test.Namespaces { | |
872 dec.Context.Map[k] = v | |
873 } | |
874 err = dec.Decode(dest) | |
875 } | |
876 | |
877 switch fix := dest.(type) { | |
878 case *Feed: | |
879 fix.Author.InnerXML = "" | |
880 for i := range fix.Entry { | |
881 fix.Entry[i].Author.InnerXML = "" | |
882 } | |
883 } | |
884 | |
885 if err != nil { | |
886 t.Errorf("#%d: unexpected error: %#v", i, err) | |
887 } else if got, want := dest, test.Value; !reflect.DeepEqual(got,
want) { | |
888 t.Errorf("#%d: unmarshal(%q):\nhave %#v\nwant %#v", i, t
est.ExpectXML, got, want) | |
889 } | |
890 } | |
891 } | |
892 | |
893 type limitedBytesWriter struct { | |
894 w io.Writer | |
895 remain int // until writes fail | |
896 } | |
897 | |
898 func (lw *limitedBytesWriter) Write(p []byte) (n int, err error) { | |
899 if lw.remain <= 0 { | |
900 println("error") | |
901 return 0, errors.New("write limit hit") | |
902 } | |
903 if len(p) > lw.remain { | |
904 p = p[:lw.remain] | |
905 n, _ = lw.w.Write(p) | |
906 lw.remain = 0 | |
907 return n, errors.New("write limit hit") | |
908 } | |
909 n, err = lw.w.Write(p) | |
910 lw.remain -= n | |
911 return n, err | |
912 } | |
913 | |
914 func TestMarshalWriteErrors(t *testing.T) { | |
915 var buf bytes.Buffer | |
916 const writeCap = 1024 | |
917 w := &limitedBytesWriter{&buf, writeCap} | |
918 enc := NewEncoder(w) | |
919 var err error | |
920 var i int | |
921 const n = 4000 | |
922 for i = 1; i <= n; i++ { | |
923 err = enc.Encode(&Passenger{ | |
924 Name: []string{"Alice", "Bob"}, | |
925 Weight: 5, | |
926 }) | |
927 if err != nil { | |
928 break | |
929 } | |
930 } | |
931 if err == nil { | |
932 t.Error("expected an error") | |
933 } | |
934 if i == n { | |
935 t.Errorf("expected to fail before the end") | |
936 } | |
937 if buf.Len() != writeCap { | |
938 t.Errorf("buf.Len() = %d; want %d", buf.Len(), writeCap) | |
939 } | |
940 } | |
941 | |
942 func BenchmarkMarshal(b *testing.B) { | |
943 for i := 0; i < b.N; i++ { | |
944 Marshal(atomValue) | |
945 } | |
946 } | |
947 | |
948 func BenchmarkUnmarshal(b *testing.B) { | |
949 xml := []byte(atomXml) | |
950 for i := 0; i < b.N; i++ { | |
951 Unmarshal(xml, &Feed{}) | |
952 } | |
953 } | |
LEFT | RIGHT |