...

Source file src/github.com/clbanning/mxj/v2/xml.go

Documentation: github.com/clbanning/mxj/v2

     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  

View as plain text