1 // Copyright 2012-2016, 2018-2019 Charles Banning. 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 // xml.go - basically the core of X2j for map[string]interface{} values.
6 // NewMapXml, NewMapXmlReader, mv.Xml, mv.XmlWriter
7 // see x2j and j2x for wrappers to provide end-to-end transformation of XML and JSON messages.
8 9 package mxj
10 11 import (
12 "bytes"
13 "encoding/json"
14 "encoding/xml"
15 "errors"
16 "fmt"
17 "io"
18 "reflect"
19 "sort"
20 "strconv"
21 "strings"
22 "time"
23 )
24 25 var (
26 textK = "#text"
27 seqK = "#seq"
28 commentK = "#comment"
29 attrK = "#attr"
30 directiveK = "#directive"
31 procinstK = "#procinst"
32 targetK = "#target"
33 instK = "#inst"
34 )
35 36 // Support overriding default Map keys prefix
37 38 func SetGlobalKeyMapPrefix(s string) {
39 textK = strings.ReplaceAll(textK, textK[0:1], s)
40 seqK = strings.ReplaceAll(seqK, seqK[0:1], s)
41 commentK = strings.ReplaceAll(commentK, commentK[0:1], s)
42 directiveK = strings.ReplaceAll(directiveK, directiveK[0:1], s)
43 procinstK = strings.ReplaceAll(procinstK, procinstK[0:1], s)
44 targetK = strings.ReplaceAll(targetK, targetK[0:1], s)
45 instK = strings.ReplaceAll(instK, instK[0:1], s)
46 attrK = strings.ReplaceAll(attrK, attrK[0:1], s)
47 }
48 49 // ------------------- NewMapXml & NewMapXmlReader ... -------------------------
50 51 // If XmlCharsetReader != nil, it will be used to decode the XML, if required.
52 // Note: if CustomDecoder != nil, then XmlCharsetReader is ignored;
53 // set the CustomDecoder attribute instead.
54 // import (
55 // charset "code.google.com/p/go-charset/charset"
56 // github.com/clbanning/mxj
57 // )
58 // ...
59 // mxj.XmlCharsetReader = charset.NewReader
60 // m, merr := mxj.NewMapXml(xmlValue)
61 var XmlCharsetReader func(charset string, input io.Reader) (io.Reader, error)
62 63 // NewMapXml - convert a XML doc into a Map
64 // (This is analogous to unmarshalling a JSON string to map[string]interface{} using json.Unmarshal().)
65 // If the optional argument 'cast' is 'true', then values will be converted to boolean or float64 if possible.
66 //
67 // Converting XML to JSON is a simple as:
68 // ...
69 // mapVal, merr := mxj.NewMapXml(xmlVal)
70 // if merr != nil {
71 // // handle error
72 // }
73 // jsonVal, jerr := mapVal.Json()
74 // if jerr != nil {
75 // // handle error
76 // }
77 //
78 // NOTES:
79 // 1. Declarations, directives, process instructions and comments are NOT parsed.
80 // 2. The 'xmlVal' will be parsed looking for an xml.StartElement, so BOM and other
81 // extraneous xml.CharData will be ignored unless io.EOF is reached first.
82 // 3. If CoerceKeysToLower() has been called, then all key values will be lower case.
83 // 4. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case.
84 // 5. If DisableTrimWhiteSpace(b bool) has been called, then all values will be trimmed or not. 'true' by default.
85 func NewMapXml(xmlVal []byte, cast ...bool) (Map, error) {
86 var r bool
87 if len(cast) == 1 {
88 r = cast[0]
89 }
90 return xmlToMap(xmlVal, r)
91 }
92 93 // Get next XML doc from an io.Reader as a Map value. Returns Map value.
94 // NOTES:
95 // 1. Declarations, directives, process instructions and comments are NOT parsed.
96 // 2. The 'xmlReader' will be parsed looking for an xml.StartElement, so BOM and other
97 // extraneous xml.CharData will be ignored unless io.EOF is reached first.
98 // 3. If CoerceKeysToLower() has been called, then all key values will be lower case.
99 // 4. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case.
100 func NewMapXmlReader(xmlReader io.Reader, cast ...bool) (Map, error) {
101 var r bool
102 if len(cast) == 1 {
103 r = cast[0]
104 }
105 106 // We need to put an *os.File reader in a ByteReader or the xml.NewDecoder
107 // will wrap it in a bufio.Reader and seek on the file beyond where the
108 // xml.Decoder parses!
109 if _, ok := xmlReader.(io.ByteReader); !ok {
110 xmlReader = myByteReader(xmlReader) // see code at EOF
111 }
112 113 // build the map
114 return xmlReaderToMap(xmlReader, r)
115 }
116 117 // Get next XML doc from an io.Reader as a Map value. Returns Map value and slice with the raw XML.
118 // NOTES:
119 // 1. Declarations, directives, process instructions and comments are NOT parsed.
120 // 2. Due to the implementation of xml.Decoder, the raw XML off the reader is buffered to []byte
121 // using a ByteReader. If the io.Reader is an os.File, there may be significant performance impact.
122 // See the examples - getmetrics1.go through getmetrics4.go - for comparative use cases on a large
123 // data set. If the io.Reader is wrapping a []byte value in-memory, however, such as http.Request.Body
124 // you CAN use it to efficiently unmarshal a XML doc and retrieve the raw XML in a single call.
125 // 3. The 'raw' return value may be larger than the XML text value.
126 // 4. The 'xmlReader' will be parsed looking for an xml.StartElement, so BOM and other
127 // extraneous xml.CharData will be ignored unless io.EOF is reached first.
128 // 5. If CoerceKeysToLower() has been called, then all key values will be lower case.
129 // 6. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case.
130 func NewMapXmlReaderRaw(xmlReader io.Reader, cast ...bool) (Map, []byte, error) {
131 var r bool
132 if len(cast) == 1 {
133 r = cast[0]
134 }
135 // create TeeReader so we can retrieve raw XML
136 buf := make([]byte, 0)
137 wb := bytes.NewBuffer(buf)
138 trdr := myTeeReader(xmlReader, wb) // see code at EOF
139 140 m, err := xmlReaderToMap(trdr, r)
141 142 // retrieve the raw XML that was decoded
143 b := wb.Bytes()
144 145 if err != nil {
146 return nil, b, err
147 }
148 149 return m, b, nil
150 }
151 152 // xmlReaderToMap() - parse a XML io.Reader to a map[string]interface{} value
153 func xmlReaderToMap(rdr io.Reader, r bool) (map[string]interface{}, error) {
154 // parse the Reader
155 p := xml.NewDecoder(rdr)
156 if CustomDecoder != nil {
157 useCustomDecoder(p)
158 } else {
159 p.CharsetReader = XmlCharsetReader
160 }
161 return xmlToMapParser("", nil, p, r)
162 }
163 164 // xmlToMap - convert a XML doc into map[string]interface{} value
165 func xmlToMap(doc []byte, r bool) (map[string]interface{}, error) {
166 b := bytes.NewReader(doc)
167 p := xml.NewDecoder(b)
168 if CustomDecoder != nil {
169 useCustomDecoder(p)
170 } else {
171 p.CharsetReader = XmlCharsetReader
172 }
173 return xmlToMapParser("", nil, p, r)
174 }
175 176 // ===================================== where the work happens =============================
177 178 // PrependAttrWithHyphen. Prepend attribute tags with a hyphen.
179 // Default is 'true'. (Not applicable to NewMapXmlSeq(), mv.XmlSeq(), etc.)
180 // Note:
181 // If 'false', unmarshaling and marshaling is not symmetric. Attributes will be
182 // marshal'd as <attr_tag>attr</attr_tag> and may be part of a list.
183 func PrependAttrWithHyphen(v bool) {
184 if v {
185 attrPrefix = "-"
186 lenAttrPrefix = len(attrPrefix)
187 return
188 }
189 attrPrefix = ""
190 lenAttrPrefix = len(attrPrefix)
191 }
192 193 // Include sequence id with inner tags. - per Sean Murphy, murphysean84@gmail.com.
194 var includeTagSeqNum bool
195 196 // IncludeTagSeqNum - include a "_seq":N key:value pair with each inner tag, denoting
197 // its position when parsed. This is of limited usefulness, since list values cannot
198 // be tagged with "_seq" without changing their depth in the Map.
199 // So THIS SHOULD BE USED WITH CAUTION - see the test cases. Here's a sample of what
200 // you get.
201 /*
202 <Obj c="la" x="dee" h="da">
203 <IntObj id="3"/>
204 <IntObj1 id="1"/>
205 <IntObj id="2"/>
206 <StrObj>hello</StrObj>
207 </Obj>
208 209 parses as:
210 211 {
212 Obj:{
213 "-c":"la",
214 "-h":"da",
215 "-x":"dee",
216 "intObj":[
217 {
218 "-id"="3",
219 "_seq":"0" // if mxj.Cast is passed, then: "_seq":0
220 },
221 {
222 "-id"="2",
223 "_seq":"2"
224 }],
225 "intObj1":{
226 "-id":"1",
227 "_seq":"1"
228 },
229 "StrObj":{
230 "#text":"hello", // simple element value gets "#text" tag
231 "_seq":"3"
232 }
233 }
234 }
235 */
236 func IncludeTagSeqNum(b ...bool) {
237 if len(b) == 0 {
238 includeTagSeqNum = !includeTagSeqNum
239 } else if len(b) == 1 {
240 includeTagSeqNum = b[0]
241 }
242 }
243 244 // all keys will be "lower case"
245 var lowerCase bool
246 247 // Coerce all tag values to keys in lower case. This is useful if you've got sources with variable
248 // tag capitalization, and you want to use m.ValuesForKeys(), etc., with the key or path spec
249 // in lower case.
250 // CoerceKeysToLower() will toggle the coercion flag true|false - on|off
251 // CoerceKeysToLower(true|false) will set the coercion flag on|off
252 //
253 // NOTE: only recognized by NewMapXml, NewMapXmlReader, and NewMapXmlReaderRaw functions as well as
254 // the associated HandleXmlReader and HandleXmlReaderRaw.
255 func CoerceKeysToLower(b ...bool) {
256 if len(b) == 0 {
257 lowerCase = !lowerCase
258 } else if len(b) == 1 {
259 lowerCase = b[0]
260 }
261 }
262 263 // disableTrimWhiteSpace sets if the white space should be removed or not
264 var disableTrimWhiteSpace bool
265 var trimRunes = "\t\r\b\n "
266 267 // DisableTrimWhiteSpace set if the white space should be trimmed or not. By default white space is always trimmed. If
268 // no argument is provided, trim white space will be disabled.
269 func DisableTrimWhiteSpace(b ...bool) {
270 if len(b) == 0 {
271 disableTrimWhiteSpace = true
272 } else {
273 disableTrimWhiteSpace = b[0]
274 }
275 276 if disableTrimWhiteSpace {
277 trimRunes = "\t\r\b\n"
278 } else {
279 trimRunes = "\t\r\b\n "
280 }
281 }
282 283 // 25jun16: Allow user to specify the "prefix" character for XML attribute key labels.
284 // We do this by replacing '`' constant with attrPrefix var, replacing useHyphen with attrPrefix = "",
285 // and adding a SetAttrPrefix(s string) function.
286 287 var attrPrefix string = `-` // the default
288 var lenAttrPrefix int = 1 // the default
289 290 // SetAttrPrefix changes the default, "-", to the specified value, s.
291 // SetAttrPrefix("") is the same as PrependAttrWithHyphen(false).
292 // (Not applicable for NewMapXmlSeq(), mv.XmlSeq(), etc.)
293 func SetAttrPrefix(s string) {
294 attrPrefix = s
295 lenAttrPrefix = len(attrPrefix)
296 }
297 298 // 18jan17: Allows user to specify if the map keys should be in snake case instead
299 // of the default hyphenated notation.
300 var snakeCaseKeys bool
301 302 // CoerceKeysToSnakeCase changes the default, false, to the specified value, b.
303 // Note: the attribute prefix will be a hyphen, '-', or what ever string value has
304 // been specified using SetAttrPrefix.
305 func CoerceKeysToSnakeCase(b ...bool) {
306 if len(b) == 0 {
307 snakeCaseKeys = !snakeCaseKeys
308 } else if len(b) == 1 {
309 snakeCaseKeys = b[0]
310 }
311 }
312 313 // 10jan19: use of pull request #57 should be conditional - legacy code assumes
314 // numeric values are float64.
315 var castToInt bool
316 317 // CastValuesToInt tries to coerce numeric valus to int64 or uint64 instead of the
318 // default float64. Repeated calls with no argument will toggle this on/off, or this
319 // handling will be set with the value of 'b'.
320 func CastValuesToInt(b ...bool) {
321 if len(b) == 0 {
322 castToInt = !castToInt
323 } else if len(b) == 1 {
324 castToInt = b[0]
325 }
326 }
327 328 // 05feb17: support processing XMPP streams (issue #36)
329 var handleXMPPStreamTag bool
330 331 // HandleXMPPStreamTag causes decoder to parse XMPP <stream:stream> elements.
332 // If called with no argument, XMPP stream element handling is toggled on/off.
333 // (See xmppStream_test.go for example.)
334 // If called with NewMapXml, NewMapXmlReader, New MapXmlReaderRaw the "stream"
335 // element will be returned as:
336 // map["stream"]interface{}{map[-<attrs>]interface{}}.
337 // If called with NewMapSeq, NewMapSeqReader, NewMapSeqReaderRaw the "stream"
338 // element will be returned as:
339 // map["stream:stream"]interface{}{map["#attr"]interface{}{map[string]interface{}}}
340 // where the "#attr" values have "#text" and "#seq" keys. (See NewMapXmlSeq.)
341 func HandleXMPPStreamTag(b ...bool) {
342 if len(b) == 0 {
343 handleXMPPStreamTag = !handleXMPPStreamTag
344 } else if len(b) == 1 {
345 handleXMPPStreamTag = b[0]
346 }
347 }
348 349 // 21jan18 - decode all values as map["#text":value] (issue #56)
350 var decodeSimpleValuesAsMap bool
351 352 // DecodeSimpleValuesAsMap forces all values to be decoded as map["#text":<value>].
353 // If called with no argument, the decoding is toggled on/off.
354 //
355 // By default the NewMapXml functions decode simple values without attributes as
356 // map[<tag>:<value>]. This function causes simple values without attributes to be
357 // decoded the same as simple values with attributes - map[<tag>:map["#text":<value>]].
358 func DecodeSimpleValuesAsMap(b ...bool) {
359 if len(b) == 0 {
360 decodeSimpleValuesAsMap = !decodeSimpleValuesAsMap
361 } else if len(b) == 1 {
362 decodeSimpleValuesAsMap = b[0]
363 }
364 }
365 366 // xmlToMapParser (2015.11.12) - load a 'clean' XML doc into a map[string]interface{} directly.
367 // A refactoring of xmlToTreeParser(), markDuplicate() and treeToMap() - here, all-in-one.
368 // We've removed the intermediate *node tree with the allocation and subsequent rescanning.
369 func xmlToMapParser(skey string, a []xml.Attr, p *xml.Decoder, r bool) (map[string]interface{}, error) {
370 if lowerCase {
371 skey = strings.ToLower(skey)
372 }
373 if snakeCaseKeys {
374 skey = strings.Replace(skey, "-", "_", -1)
375 }
376 377 // NOTE: all attributes and sub-elements parsed into 'na', 'na' is returned as value for 'skey' in 'n'.
378 // Unless 'skey' is a simple element w/o attributes, in which case the xml.CharData value is the value.
379 var n, na map[string]interface{}
380 var seq int // for includeTagSeqNum
381 382 // Allocate maps and load attributes, if any.
383 // NOTE: on entry from NewMapXml(), etc., skey=="", and we fall through
384 // to get StartElement then recurse with skey==xml.StartElement.Name.Local
385 // where we begin allocating map[string]interface{} values 'n' and 'na'.
386 if skey != "" {
387 n = make(map[string]interface{}) // old n
388 na = make(map[string]interface{}) // old n.nodes
389 if len(a) > 0 {
390 for _, v := range a {
391 if snakeCaseKeys {
392 v.Name.Local = strings.Replace(v.Name.Local, "-", "_", -1)
393 }
394 var key string
395 key = attrPrefix + v.Name.Local
396 if lowerCase {
397 key = strings.ToLower(key)
398 }
399 if xmlEscapeCharsDecoder { // per issue#84
400 v.Value = escapeChars(v.Value)
401 }
402 na[key] = cast(v.Value, r, key)
403 }
404 }
405 }
406 // Return XMPP <stream:stream> message.
407 if handleXMPPStreamTag && skey == "stream" {
408 n[skey] = na
409 return n, nil
410 }
411 412 for {
413 t, err := p.Token()
414 if err != nil {
415 if err != io.EOF {
416 return nil, errors.New("xml.Decoder.Token() - " + err.Error())
417 }
418 return nil, err
419 }
420 switch t.(type) {
421 case xml.StartElement:
422 tt := t.(xml.StartElement)
423 424 // First call to xmlToMapParser() doesn't pass xml.StartElement - the map key.
425 // So when the loop is first entered, the first token is the root tag along
426 // with any attributes, which we process here.
427 //
428 // Subsequent calls to xmlToMapParser() will pass in tag+attributes for
429 // processing before getting the next token which is the element value,
430 // which is done above.
431 if skey == "" {
432 return xmlToMapParser(tt.Name.Local, tt.Attr, p, r)
433 }
434 435 // If not initializing the map, parse the element.
436 // len(nn) == 1, necessarily - it is just an 'n'.
437 nn, err := xmlToMapParser(tt.Name.Local, tt.Attr, p, r)
438 if err != nil {
439 return nil, err
440 }
441 442 // The nn map[string]interface{} value is a na[nn_key] value.
443 // We need to see if nn_key already exists - means we're parsing a list.
444 // This may require converting na[nn_key] value into []interface{} type.
445 // First, extract the key:val for the map - it's a singleton.
446 // Note:
447 // * if CoerceKeysToLower() called, then key will be lower case.
448 // * if CoerceKeysToSnakeCase() called, then key will be converted to snake case.
449 var key string
450 var val interface{}
451 for key, val = range nn {
452 break
453 }
454 455 // IncludeTagSeqNum requests that the element be augmented with a "_seq" sub-element.
456 // In theory, we don't need this if len(na) == 1. But, we don't know what might
457 // come next - we're only parsing forward. So if you ask for 'includeTagSeqNum' you
458 // get it on every element. (Personally, I never liked this, but I added it on request
459 // and did get a $50 Amazon gift card in return - now we support it for backwards compatibility!)
460 if includeTagSeqNum {
461 switch val.(type) {
462 case []interface{}:
463 // noop - There's no clean way to handle this w/o changing message structure.
464 case map[string]interface{}:
465 val.(map[string]interface{})["_seq"] = seq // will overwrite an "_seq" XML tag
466 seq++
467 case interface{}: // a non-nil simple element: string, float64, bool
468 v := map[string]interface{}{textK: val}
469 v["_seq"] = seq
470 seq++
471 val = v
472 }
473 }
474 475 // 'na' holding sub-elements of n.
476 // See if 'key' already exists.
477 // If 'key' exists, then this is a list, if not just add key:val to na.
478 if v, ok := na[key]; ok {
479 var a []interface{}
480 switch v.(type) {
481 case []interface{}:
482 a = v.([]interface{})
483 default: // anything else - note: v.(type) != nil
484 a = []interface{}{v}
485 }
486 a = append(a, val)
487 na[key] = a
488 } else {
489 na[key] = val // save it as a singleton
490 }
491 case xml.EndElement:
492 // len(n) > 0 if this is a simple element w/o xml.Attrs - see xml.CharData case.
493 if len(n) == 0 {
494 // If len(na)==0 we have an empty element == "";
495 // it has no xml.Attr nor xml.CharData.
496 // Note: in original node-tree parser, val defaulted to "";
497 // so we always had the default if len(node.nodes) == 0.
498 if len(na) > 0 {
499 n[skey] = na
500 } else {
501 n[skey] = "" // empty element
502 }
503 } else if len(n) == 1 && len(na) > 0 {
504 // it's a simple element w/ no attributes w/ subelements
505 for _, v := range n {
506 na[textK] = v
507 }
508 n[skey] = na
509 }
510 return n, nil
511 case xml.CharData:
512 // clean up possible noise
513 tt := strings.Trim(string(t.(xml.CharData)), trimRunes)
514 if xmlEscapeCharsDecoder { // issue#84
515 tt = escapeChars(tt)
516 }
517 if len(tt) > 0 {
518 if len(na) > 0 || decodeSimpleValuesAsMap {
519 na[textK] = cast(tt, r, textK)
520 } else if skey != "" {
521 n[skey] = cast(tt, r, skey)
522 } else {
523 // per Adrian (http://www.adrianlungu.com/) catch stray text
524 // in decoder stream -
525 // https://github.com/clbanning/mxj/pull/14#issuecomment-182816374
526 // NOTE: CharSetReader must be set to non-UTF-8 CharSet or you'll get
527 // a p.Token() decoding error when the BOM is UTF-16 or UTF-32.
528 continue
529 }
530 }
531 default:
532 // noop
533 }
534 }
535 }
536 537 var castNanInf bool
538 539 // Cast "Nan", "Inf", "-Inf" XML values to 'float64'.
540 // By default, these values will be decoded as 'string'.
541 func CastNanInf(b ...bool) {
542 if len(b) == 0 {
543 castNanInf = !castNanInf
544 } else if len(b) == 1 {
545 castNanInf = b[0]
546 }
547 }
548 549 // cast - try to cast string values to bool or float64
550 // 't' is the tag key that can be checked for 'not-casting'
551 func cast(s string, r bool, t string) interface{} {
552 if checkTagToSkip != nil && t != "" && checkTagToSkip(t) {
553 // call the check-function here with 't[0]'
554 // if 'true' return s
555 return s
556 }
557 558 if r {
559 // handle nan and inf
560 if !castNanInf {
561 switch strings.ToLower(s) {
562 case "nan", "inf", "-inf":
563 return s
564 }
565 }
566 567 // handle numeric strings ahead of boolean
568 if castToInt {
569 if f, err := strconv.ParseInt(s, 10, 64); err == nil {
570 return f
571 }
572 if f, err := strconv.ParseUint(s, 10, 64); err == nil {
573 return f
574 }
575 }
576 577 if castToFloat {
578 if f, err := strconv.ParseFloat(s, 64); err == nil {
579 return f
580 }
581 }
582 583 // ParseBool treats "1"==true & "0"==false, we've already scanned those
584 // values as float64. See if value has 't' or 'f' as initial screen to
585 // minimize calls to ParseBool; also, see if len(s) < 6.
586 if castToBool {
587 if len(s) > 0 && len(s) < 6 {
588 switch s[:1] {
589 case "t", "T", "f", "F":
590 if b, err := strconv.ParseBool(s); err == nil {
591 return b
592 }
593 }
594 }
595 }
596 }
597 return s
598 }
599 600 // pull request, #59
601 var castToFloat = true
602 603 // CastValuesToFloat can be used to skip casting to float64 when
604 // "cast" argument is 'true' in NewMapXml, etc.
605 // Default is true.
606 func CastValuesToFloat(b ...bool) {
607 if len(b) == 0 {
608 castToFloat = !castToFloat
609 } else if len(b) == 1 {
610 castToFloat = b[0]
611 }
612 }
613 614 var castToBool = true
615 616 // CastValuesToBool can be used to skip casting to bool when
617 // "cast" argument is 'true' in NewMapXml, etc.
618 // Default is true.
619 func CastValuesToBool(b ...bool) {
620 if len(b) == 0 {
621 castToBool = !castToBool
622 } else if len(b) == 1 {
623 castToBool = b[0]
624 }
625 }
626 627 // checkTagToSkip - switch to address Issue #58
628 629 var checkTagToSkip func(string) bool
630 631 // SetCheckTagToSkipFunc registers function to test whether the value
632 // for a tag should be cast to bool or float64 when "cast" argument is 'true'.
633 // (Dot tag path notation is not supported.)
634 // NOTE: key may be "#text" if it's a simple element with attributes
635 // or "decodeSimpleValuesAsMap == true".
636 // NOTE: does not apply to NewMapXmlSeq... functions.
637 func SetCheckTagToSkipFunc(fn func(string) bool) {
638 checkTagToSkip = fn
639 }
640 641 // ------------------ END: NewMapXml & NewMapXmlReader -------------------------
642 643 // ------------------ mv.Xml & mv.XmlWriter - from j2x ------------------------
644 645 const (
646 DefaultRootTag = "doc"
647 )
648 649 var useGoXmlEmptyElemSyntax bool
650 651 // XmlGoEmptyElemSyntax() - <tag ...></tag> rather than <tag .../>.
652 // Go's encoding/xml package marshals empty XML elements as <tag ...></tag>. By default this package
653 // encodes empty elements as <tag .../>. If you're marshaling Map values that include structures
654 // (which are passed to xml.Marshal for encoding), this will let you conform to the standard package.
655 func XmlGoEmptyElemSyntax() {
656 useGoXmlEmptyElemSyntax = true
657 }
658 659 // XmlDefaultEmptyElemSyntax() - <tag .../> rather than <tag ...></tag>.
660 // Return XML encoding for empty elements to the default package setting.
661 // Reverses effect of XmlGoEmptyElemSyntax().
662 func XmlDefaultEmptyElemSyntax() {
663 useGoXmlEmptyElemSyntax = false
664 }
665 666 // ------- issue #88 ----------
667 // xmlCheckIsValid set switch to force decoding the encoded XML to
668 // see if it is valid XML.
669 var xmlCheckIsValid bool
670 671 // XmlCheckIsValid forces the encoded XML to be checked for validity.
672 func XmlCheckIsValid(b ...bool) {
673 if len(b) == 1 {
674 xmlCheckIsValid = b[0]
675 return
676 }
677 xmlCheckIsValid = !xmlCheckIsValid
678 }
679 680 // Encode a Map as XML. The companion of NewMapXml().
681 // The following rules apply.
682 // - The key label "#text" is treated as the value for a simple element with attributes.
683 // - Map keys that begin with a hyphen, '-', are interpreted as attributes.
684 // It is an error if the attribute doesn't have a []byte, string, number, or boolean value.
685 // - Map value type encoding:
686 // > string, bool, float64, int, int32, int64, float32: per "%v" formating
687 // > []bool, []uint8: by casting to string
688 // > structures, etc.: handed to xml.Marshal() - if there is an error, the element
689 // value is "UNKNOWN"
690 // - Elements with only attribute values or are null are terminated using "/>".
691 // - If len(mv) == 1 and no rootTag is provided, then the map key is used as the root tag, possible.
692 // Thus, `{ "key":"value" }` encodes as "<key>value</key>".
693 // - To encode empty elements in a syntax consistent with encoding/xml call UseGoXmlEmptyElementSyntax().
694 // The attributes tag=value pairs are alphabetized by "tag". Also, when encoding map[string]interface{} values -
695 // complex elements, etc. - the key:value pairs are alphabetized by key so the resulting tags will appear sorted.
696 func (mv Map) Xml(rootTag ...string) ([]byte, error) {
697 m := map[string]interface{}(mv)
698 var err error
699 b := new(bytes.Buffer)
700 p := new(pretty) // just a stub
701 702 if len(m) == 1 && len(rootTag) == 0 {
703 for key, value := range m {
704 // if it an array, see if all values are map[string]interface{}
705 // we force a new root tag if we'll end up with no key:value in the list
706 // so: key:[string_val, bool:true] --> <doc><key>string_val</key><bool>true</bool></doc>
707 switch value.(type) {
708 case []interface{}:
709 for _, v := range value.([]interface{}) {
710 switch v.(type) {
711 case map[string]interface{}: // noop
712 default: // anything else
713 err = marshalMapToXmlIndent(false, b, DefaultRootTag, m, p)
714 goto done
715 }
716 }
717 }
718 err = marshalMapToXmlIndent(false, b, key, value, p)
719 }
720 } else if len(rootTag) == 1 {
721 err = marshalMapToXmlIndent(false, b, rootTag[0], m, p)
722 } else {
723 err = marshalMapToXmlIndent(false, b, DefaultRootTag, m, p)
724 }
725 done:
726 if xmlCheckIsValid {
727 d := xml.NewDecoder(bytes.NewReader(b.Bytes()))
728 for {
729 _, err = d.Token()
730 if err == io.EOF {
731 err = nil
732 break
733 } else if err != nil {
734 return nil, err
735 }
736 }
737 }
738 return b.Bytes(), err
739 }
740 741 // The following implementation is provided only for symmetry with NewMapXmlReader[Raw]
742 // The names will also provide a key for the number of return arguments.
743 744 // Writes the Map as XML on the Writer.
745 // See Xml() for encoding rules.
746 func (mv Map) XmlWriter(xmlWriter io.Writer, rootTag ...string) error {
747 x, err := mv.Xml(rootTag...)
748 if err != nil {
749 return err
750 }
751 752 _, err = xmlWriter.Write(x)
753 return err
754 }
755 756 // Writes the Map as XML on the Writer. []byte is the raw XML that was written.
757 // See Xml() for encoding rules.
758 /*
759 func (mv Map) XmlWriterRaw(xmlWriter io.Writer, rootTag ...string) ([]byte, error) {
760 x, err := mv.Xml(rootTag...)
761 if err != nil {
762 return x, err
763 }
764 765 _, err = xmlWriter.Write(x)
766 return x, err
767 }
768 */
769 770 // Writes the Map as pretty XML on the Writer.
771 // See Xml() for encoding rules.
772 func (mv Map) XmlIndentWriter(xmlWriter io.Writer, prefix, indent string, rootTag ...string) error {
773 x, err := mv.XmlIndent(prefix, indent, rootTag...)
774 if err != nil {
775 return err
776 }
777 778 _, err = xmlWriter.Write(x)
779 return err
780 }
781 782 // Writes the Map as pretty XML on the Writer. []byte is the raw XML that was written.
783 // See Xml() for encoding rules.
784 /*
785 func (mv Map) XmlIndentWriterRaw(xmlWriter io.Writer, prefix, indent string, rootTag ...string) ([]byte, error) {
786 x, err := mv.XmlIndent(prefix, indent, rootTag...)
787 if err != nil {
788 return x, err
789 }
790 791 _, err = xmlWriter.Write(x)
792 return x, err
793 }
794 */
795 796 // -------------------- END: mv.Xml & mv.XmlWriter -------------------------------
797 798 // -------------- Handle XML stream by processing Map value --------------------
799 800 // Default poll delay to keep Handler from spinning on an open stream
801 // like sitting on os.Stdin waiting for imput.
802 var xhandlerPollInterval = time.Millisecond
803 804 // Bulk process XML using handlers that process a Map value.
805 // 'rdr' is an io.Reader for XML (stream)
806 // 'mapHandler' is the Map processor. Return of 'false' stops io.Reader processing.
807 // 'errHandler' is the error processor. Return of 'false' stops io.Reader processing and returns the error.
808 // Note: mapHandler() and errHandler() calls are blocking, so reading and processing of messages is serialized.
809 // This means that you can stop reading the file on error or after processing a particular message.
810 // To have reading and handling run concurrently, pass argument to a go routine in handler and return 'true'.
811 func HandleXmlReader(xmlReader io.Reader, mapHandler func(Map) bool, errHandler func(error) bool) error {
812 var n int
813 for {
814 m, merr := NewMapXmlReader(xmlReader)
815 n++
816 817 // handle error condition with errhandler
818 if merr != nil && merr != io.EOF {
819 merr = fmt.Errorf("[xmlReader: %d] %s", n, merr.Error())
820 if ok := errHandler(merr); !ok {
821 // caused reader termination
822 return merr
823 }
824 continue
825 }
826 827 // pass to maphandler
828 if len(m) != 0 {
829 if ok := mapHandler(m); !ok {
830 break
831 }
832 } else if merr != io.EOF {
833 time.Sleep(xhandlerPollInterval)
834 }
835 836 if merr == io.EOF {
837 break
838 }
839 }
840 return nil
841 }
842 843 // Bulk process XML using handlers that process a Map value and the raw XML.
844 // 'rdr' is an io.Reader for XML (stream)
845 // 'mapHandler' is the Map and raw XML - []byte - processor. Return of 'false' stops io.Reader processing.
846 // 'errHandler' is the error and raw XML processor. Return of 'false' stops io.Reader processing and returns the error.
847 // Note: mapHandler() and errHandler() calls are blocking, so reading and processing of messages is serialized.
848 // This means that you can stop reading the file on error or after processing a particular message.
849 // To have reading and handling run concurrently, pass argument(s) to a go routine in handler and return 'true'.
850 // See NewMapXmlReaderRaw for comment on performance associated with retrieving raw XML from a Reader.
851 func HandleXmlReaderRaw(xmlReader io.Reader, mapHandler func(Map, []byte) bool, errHandler func(error, []byte) bool) error {
852 var n int
853 for {
854 m, raw, merr := NewMapXmlReaderRaw(xmlReader)
855 n++
856 857 // handle error condition with errhandler
858 if merr != nil && merr != io.EOF {
859 merr = fmt.Errorf("[xmlReader: %d] %s", n, merr.Error())
860 if ok := errHandler(merr, raw); !ok {
861 // caused reader termination
862 return merr
863 }
864 continue
865 }
866 867 // pass to maphandler
868 if len(m) != 0 {
869 if ok := mapHandler(m, raw); !ok {
870 break
871 }
872 } else if merr != io.EOF {
873 time.Sleep(xhandlerPollInterval)
874 }
875 876 if merr == io.EOF {
877 break
878 }
879 }
880 return nil
881 }
882 883 // ----------------- END: Handle XML stream by processing Map value --------------
884 885 // -------- a hack of io.TeeReader ... need one that's an io.ByteReader for xml.NewDecoder() ----------
886 887 // This is a clone of io.TeeReader with the additional method t.ReadByte().
888 // Thus, this TeeReader is also an io.ByteReader.
889 // This is necessary because xml.NewDecoder uses a ByteReader not a Reader. It appears to have been written
890 // with bufio.Reader or bytes.Reader in mind ... not a generic io.Reader, which doesn't have to have ReadByte()..
891 // If NewDecoder is passed a Reader that does not satisfy ByteReader() it wraps the Reader with
892 // bufio.NewReader and uses ReadByte rather than Read that runs the TeeReader pipe logic.
893 894 type teeReader struct {
895 r io.Reader
896 w io.Writer
897 b []byte
898 }
899 900 func myTeeReader(r io.Reader, w io.Writer) io.Reader {
901 b := make([]byte, 1)
902 return &teeReader{r, w, b}
903 }
904 905 // need for io.Reader - but we don't use it ...
906 func (t *teeReader) Read(p []byte) (int, error) {
907 return 0, nil
908 }
909 910 func (t *teeReader) ReadByte() (byte, error) {
911 n, err := t.r.Read(t.b)
912 if n > 0 {
913 if _, err := t.w.Write(t.b[:1]); err != nil {
914 return t.b[0], err
915 }
916 }
917 return t.b[0], err
918 }
919 920 // For use with NewMapXmlReader & NewMapXmlSeqReader.
921 type byteReader struct {
922 r io.Reader
923 b []byte
924 }
925 926 func myByteReader(r io.Reader) io.Reader {
927 b := make([]byte, 1)
928 return &byteReader{r, b}
929 }
930 931 // Need for io.Reader interface ...
932 // Needed if reading a malformed http.Request.Body - issue #38.
933 func (b *byteReader) Read(p []byte) (int, error) {
934 return b.r.Read(p)
935 }
936 937 func (b *byteReader) ReadByte() (byte, error) {
938 _, err := b.r.Read(b.b)
939 if len(b.b) > 0 {
940 // issue #38
941 return b.b[0], err
942 }
943 var c byte
944 return c, err
945 }
946 947 // ----------------------- END: io.TeeReader hack -----------------------------------
948 949 // ---------------------- XmlIndent - from j2x package ----------------------------
950 951 // Encode a map[string]interface{} as a pretty XML string.
952 // See Xml for encoding rules.
953 func (mv Map) XmlIndent(prefix, indent string, rootTag ...string) ([]byte, error) {
954 m := map[string]interface{}(mv)
955 956 var err error
957 b := new(bytes.Buffer)
958 p := new(pretty)
959 p.indent = indent
960 p.padding = prefix
961 962 if len(m) == 1 && len(rootTag) == 0 {
963 // this can extract the key for the single map element
964 // use it if it isn't a key for a list
965 for key, value := range m {
966 if _, ok := value.([]interface{}); ok {
967 err = marshalMapToXmlIndent(true, b, DefaultRootTag, m, p)
968 } else {
969 err = marshalMapToXmlIndent(true, b, key, value, p)
970 }
971 }
972 } else if len(rootTag) == 1 {
973 err = marshalMapToXmlIndent(true, b, rootTag[0], m, p)
974 } else {
975 err = marshalMapToXmlIndent(true, b, DefaultRootTag, m, p)
976 }
977 if xmlCheckIsValid {
978 d := xml.NewDecoder(bytes.NewReader(b.Bytes()))
979 for {
980 _, err = d.Token()
981 if err == io.EOF {
982 err = nil
983 break
984 } else if err != nil {
985 return nil, err
986 }
987 }
988 }
989 return b.Bytes(), err
990 }
991 992 type pretty struct {
993 indent string
994 cnt int
995 padding string
996 mapDepth int
997 start int
998 }
999 1000 func (p *pretty) Indent() {
1001 p.padding += p.indent
1002 p.cnt++
1003 }
1004 1005 func (p *pretty) Outdent() {
1006 if p.cnt > 0 {
1007 p.padding = p.padding[:len(p.padding)-len(p.indent)]
1008 p.cnt--
1009 }
1010 }
1011 1012 // where the work actually happens
1013 // returns an error if an attribute is not atomic
1014 // NOTE: 01may20 - replaces mapToXmlIndent(); uses bytes.Buffer instead for string appends.
1015 func marshalMapToXmlIndent(doIndent bool, b *bytes.Buffer, key string, value interface{}, pp *pretty) error {
1016 var err error
1017 var endTag bool
1018 var isSimple bool
1019 var elen int
1020 p := &pretty{pp.indent, pp.cnt, pp.padding, pp.mapDepth, pp.start}
1021 1022 // per issue #48, 18apr18 - try and coerce maps to map[string]interface{}
1023 // Don't need for mapToXmlSeqIndent, since maps there are decoded by NewMapXmlSeq().
1024 if reflect.ValueOf(value).Kind() == reflect.Map {
1025 switch value.(type) {
1026 case map[string]interface{}:
1027 default:
1028 val := make(map[string]interface{})
1029 vv := reflect.ValueOf(value)
1030 keys := vv.MapKeys()
1031 for _, k := range keys {
1032 val[fmt.Sprint(k)] = vv.MapIndex(k).Interface()
1033 }
1034 value = val
1035 }
1036 }
1037 1038 // 14jul20. The following block of code has become something of a catch all for odd stuff
1039 // that might be passed in as a result of casting an arbitrary map[<T>]<T> to an mxj.Map
1040 // value and then call m.Xml or m.XmlIndent. See issue #71 (and #73) for such edge cases.
1041 switch value.(type) {
1042 // these types are handled during encoding
1043 case map[string]interface{}, []byte, string, float64, bool, int, int32, int64, float32, json.Number:
1044 case []map[string]interface{}, []string, []float64, []bool, []int, []int32, []int64, []float32, []json.Number:
1045 case []interface{}:
1046 case nil:
1047 value = ""
1048 default:
1049 // see if value is a struct, if so marshal using encoding/xml package
1050 if reflect.ValueOf(value).Kind() == reflect.Struct {
1051 if v, err := xml.Marshal(value); err != nil {
1052 return err
1053 } else {
1054 value = string(v)
1055 }
1056 } else {
1057 // coerce eveything else into a string value
1058 value = fmt.Sprint(value)
1059 }
1060 }
1061 1062 // start the XML tag with required indentaton and padding
1063 if doIndent {
1064 switch value.(type) {
1065 case []interface{}, []string:
1066 // list processing handles indentation for all elements
1067 default:
1068 if _, err = b.WriteString(p.padding); err != nil {
1069 return err
1070 }
1071 }
1072 }
1073 switch value.(type) {
1074 case []interface{}:
1075 default:
1076 if _, err = b.WriteString(`<` + key); err != nil {
1077 return err
1078 }
1079 }
1080 1081 switch value.(type) {
1082 case map[string]interface{}:
1083 vv := value.(map[string]interface{})
1084 lenvv := len(vv)
1085 // scan out attributes - attribute keys have prepended attrPrefix
1086 attrlist := make([][2]string, len(vv))
1087 var n int
1088 var ss string
1089 for k, v := range vv {
1090 if lenAttrPrefix > 0 && lenAttrPrefix < len(k) && k[:lenAttrPrefix] == attrPrefix {
1091 switch v.(type) {
1092 case string:
1093 if xmlEscapeChars {
1094 ss = escapeChars(v.(string))
1095 } else {
1096 ss = v.(string)
1097 }
1098 attrlist[n][0] = k[lenAttrPrefix:]
1099 attrlist[n][1] = ss
1100 case float64, bool, int, int32, int64, float32, json.Number:
1101 attrlist[n][0] = k[lenAttrPrefix:]
1102 attrlist[n][1] = fmt.Sprintf("%v", v)
1103 case []byte:
1104 if xmlEscapeChars {
1105 ss = escapeChars(string(v.([]byte)))
1106 } else {
1107 ss = string(v.([]byte))
1108 }
1109 attrlist[n][0] = k[lenAttrPrefix:]
1110 attrlist[n][1] = ss
1111 default:
1112 return fmt.Errorf("invalid attribute value for: %s:<%T>", k, v)
1113 }
1114 n++
1115 }
1116 }
1117 if n > 0 {
1118 attrlist = attrlist[:n]
1119 sort.Sort(attrList(attrlist))
1120 for _, v := range attrlist {
1121 if _, err = b.WriteString(` ` + v[0] + `="` + v[1] + `"`); err != nil {
1122 return err
1123 }
1124 }
1125 }
1126 // only attributes?
1127 if n == lenvv {
1128 if useGoXmlEmptyElemSyntax {
1129 if _, err = b.WriteString(`</` + key + ">"); err != nil {
1130 return err
1131 }
1132 } else {
1133 if _, err = b.WriteString(`/>`); err != nil {
1134 return err
1135 }
1136 }
1137 break
1138 }
1139 1140 // simple element? Note: '#text" is an invalid XML tag.
1141 isComplex := false
1142 if v, ok := vv[textK]; ok && n+1 == lenvv {
1143 // just the value and attributes
1144 switch v.(type) {
1145 case string:
1146 if xmlEscapeChars {
1147 v = escapeChars(v.(string))
1148 } else {
1149 v = v.(string)
1150 }
1151 case []byte:
1152 if xmlEscapeChars {
1153 v = escapeChars(string(v.([]byte)))
1154 } else {
1155 v = string(v.([]byte))
1156 }
1157 }
1158 if _, err = b.WriteString(">" + fmt.Sprintf("%v", v)); err != nil {
1159 return err
1160 }
1161 endTag = true
1162 elen = 1
1163 isSimple = true
1164 break
1165 } else if ok {
1166 // need to handle when there are subelements in addition to the simple element value
1167 // issue #90
1168 switch v.(type) {
1169 case string:
1170 if xmlEscapeChars {
1171 v = escapeChars(v.(string))
1172 } else {
1173 v = v.(string)
1174 }
1175 case []byte:
1176 if xmlEscapeChars {
1177 v = escapeChars(string(v.([]byte)))
1178 } else {
1179 v = string(v.([]byte))
1180 }
1181 }
1182 if _, err = b.WriteString(">" + fmt.Sprintf("%v", v)); err != nil {
1183 return err
1184 }
1185 isComplex = true
1186 }
1187 1188 // close tag with possible attributes
1189 if !isComplex {
1190 if _, err = b.WriteString(">"); err != nil {
1191 return err
1192 }
1193 }
1194 if doIndent {
1195 // *s += "\n"
1196 if _, err = b.WriteString("\n"); err != nil {
1197 return err
1198 }
1199 }
1200 // something more complex
1201 p.mapDepth++
1202 // extract the map k:v pairs and sort on key
1203 elemlist := make([][2]interface{}, len(vv))
1204 n = 0
1205 for k, v := range vv {
1206 if k == textK {
1207 // simple element handled above
1208 continue
1209 }
1210 if lenAttrPrefix > 0 && lenAttrPrefix < len(k) && k[:lenAttrPrefix] == attrPrefix {
1211 continue
1212 }
1213 elemlist[n][0] = k
1214 elemlist[n][1] = v
1215 n++
1216 }
1217 elemlist = elemlist[:n]
1218 sort.Sort(elemList(elemlist))
1219 var i int
1220 for _, v := range elemlist {
1221 switch v[1].(type) {
1222 case []interface{}:
1223 default:
1224 if i == 0 && doIndent {
1225 p.Indent()
1226 }
1227 }
1228 i++
1229 if err := marshalMapToXmlIndent(doIndent, b, v[0].(string), v[1], p); err != nil {
1230 return err
1231 }
1232 switch v[1].(type) {
1233 case []interface{}: // handled in []interface{} case
1234 default:
1235 if doIndent {
1236 p.Outdent()
1237 }
1238 }
1239 i--
1240 }
1241 p.mapDepth--
1242 endTag = true
1243 elen = 1 // we do have some content ...
1244 case []interface{}:
1245 // special case - found during implementing Issue #23
1246 if len(value.([]interface{})) == 0 {
1247 if doIndent {
1248 if _, err = b.WriteString(p.padding + p.indent); err != nil {
1249 return err
1250 }
1251 }
1252 if _, err = b.WriteString("<" + key); err != nil {
1253 return err
1254 }
1255 elen = 0
1256 endTag = true
1257 break
1258 }
1259 for _, v := range value.([]interface{}) {
1260 if doIndent {
1261 p.Indent()
1262 }
1263 if err := marshalMapToXmlIndent(doIndent, b, key, v, p); err != nil {
1264 return err
1265 }
1266 if doIndent {
1267 p.Outdent()
1268 }
1269 }
1270 return nil
1271 case []string:
1272 // This was added by https://github.com/slotix ... not a type that
1273 // would be encountered if mv generated from NewMapXml, NewMapJson.
1274 // Could be encountered in AnyXml(), so we'll let it stay, though
1275 // it should be merged with case []interface{}, above.
1276 //quick fix for []string type
1277 //[]string should be treated exaclty as []interface{}
1278 if len(value.([]string)) == 0 {
1279 if doIndent {
1280 if _, err = b.WriteString(p.padding + p.indent); err != nil {
1281 return err
1282 }
1283 }
1284 if _, err = b.WriteString("<" + key); err != nil {
1285 return err
1286 }
1287 elen = 0
1288 endTag = true
1289 break
1290 }
1291 for _, v := range value.([]string) {
1292 if doIndent {
1293 p.Indent()
1294 }
1295 if err := marshalMapToXmlIndent(doIndent, b, key, v, p); err != nil {
1296 return err
1297 }
1298 if doIndent {
1299 p.Outdent()
1300 }
1301 }
1302 return nil
1303 case nil:
1304 // terminate the tag
1305 if doIndent {
1306 // *s += p.padding
1307 if _, err = b.WriteString(p.padding); err != nil {
1308 return err
1309 }
1310 }
1311 if _, err = b.WriteString("<" + key); err != nil {
1312 return err
1313 }
1314 endTag, isSimple = true, true
1315 break
1316 default: // handle anything - even goofy stuff
1317 elen = 0
1318 switch value.(type) {
1319 case string:
1320 v := value.(string)
1321 if xmlEscapeChars {
1322 v = escapeChars(v)
1323 }
1324 elen = len(v)
1325 if elen > 0 {
1326 // *s += ">" + v
1327 if _, err = b.WriteString(">" + v); err != nil {
1328 return err
1329 }
1330 }
1331 case float64, bool, int, int32, int64, float32, json.Number:
1332 v := fmt.Sprintf("%v", value)
1333 elen = len(v) // always > 0
1334 if _, err = b.WriteString(">" + v); err != nil {
1335 return err
1336 }
1337 case []byte: // NOTE: byte is just an alias for uint8
1338 // similar to how xml.Marshal handles []byte structure members
1339 v := string(value.([]byte))
1340 if xmlEscapeChars {
1341 v = escapeChars(v)
1342 }
1343 elen = len(v)
1344 if elen > 0 {
1345 // *s += ">" + v
1346 if _, err = b.WriteString(">" + v); err != nil {
1347 return err
1348 }
1349 }
1350 default:
1351 if _, err = b.WriteString(">"); err != nil {
1352 return err
1353 }
1354 var v []byte
1355 var err error
1356 if doIndent {
1357 v, err = xml.MarshalIndent(value, p.padding, p.indent)
1358 } else {
1359 v, err = xml.Marshal(value)
1360 }
1361 if err != nil {
1362 if _, err = b.WriteString(">UNKNOWN"); err != nil {
1363 return err
1364 }
1365 } else {
1366 elen = len(v)
1367 if elen > 0 {
1368 if _, err = b.Write(v); err != nil {
1369 return err
1370 }
1371 }
1372 }
1373 }
1374 isSimple = true
1375 endTag = true
1376 }
1377 if endTag {
1378 if doIndent {
1379 if !isSimple {
1380 if _, err = b.WriteString(p.padding); err != nil {
1381 return err
1382 }
1383 }
1384 }
1385 if elen > 0 || useGoXmlEmptyElemSyntax {
1386 if elen == 0 {
1387 if _, err = b.WriteString(">"); err != nil {
1388 return err
1389 }
1390 }
1391 if _, err = b.WriteString(`</` + key + ">"); err != nil {
1392 return err
1393 }
1394 } else {
1395 if _, err = b.WriteString(`/>`); err != nil {
1396 return err
1397 }
1398 }
1399 }
1400 if doIndent {
1401 if p.cnt > p.start {
1402 if _, err = b.WriteString("\n"); err != nil {
1403 return err
1404 }
1405 }
1406 p.Outdent()
1407 }
1408 1409 return nil
1410 }
1411 1412 // ============================ sort interface implementation =================
1413 1414 type attrList [][2]string
1415 1416 func (a attrList) Len() int {
1417 return len(a)
1418 }
1419 1420 func (a attrList) Swap(i, j int) {
1421 a[i], a[j] = a[j], a[i]
1422 }
1423 1424 func (a attrList) Less(i, j int) bool {
1425 return a[i][0] <= a[j][0]
1426 }
1427 1428 type elemList [][2]interface{}
1429 1430 func (e elemList) Len() int {
1431 return len(e)
1432 }
1433 1434 func (e elemList) Swap(i, j int) {
1435 e[i], e[j] = e[j], e[i]
1436 }
1437 1438 func (e elemList) Less(i, j int) bool {
1439 return e[i][0].(string) <= e[j][0].(string)
1440 }
1441