Update mxj dependency to v2.7.0

This commit is contained in:
Cameron Moore 2024-03-29 13:15:05 -05:00
parent c6e6a2ac17
commit 843ee65d8c
No known key found for this signature in database
GPG key ID: AF96E12468D7553E
43 changed files with 811 additions and 341 deletions

3
go.mod
View file

@ -3,12 +3,13 @@ module github.com/adnanh/webhook
go 1.14
require (
github.com/clbanning/mxj v1.8.4
github.com/clbanning/mxj/v2 v2.7.0
github.com/dustin/go-humanize v1.0.1
github.com/fsnotify/fsnotify v1.4.7 // indirect
github.com/ghodss/yaml v1.0.0
github.com/go-chi/chi/v5 v5.0.12
github.com/gofrs/uuid/v5 v5.0.0
github.com/google/go-cmp v0.6.0 // indirect
github.com/gorilla/mux v1.8.1
github.com/kr/pretty v0.1.0 // indirect
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8

6
go.sum
View file

@ -1,5 +1,5 @@
github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
@ -10,6 +10,8 @@ github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=

View file

@ -8,7 +8,7 @@ import (
"net/url"
"unicode"
"github.com/clbanning/mxj"
"github.com/clbanning/mxj/v2"
)
// Request represents a webhook request.

View file

@ -1,55 +0,0 @@
Copyright (c) 2012-2016 Charles Banning <clbanning@gmail.com>. All rights reserved.
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
===============================================================================
Go Language Copyright & License -
Copyright 2009 The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -1,54 +0,0 @@
// Copyright 2016 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
package mxj
import (
"bytes"
)
var xmlEscapeChars bool
// XMLEscapeChars(true) forces escaping invalid characters in attribute and element values.
// NOTE: this is brute force with NO interrogation of '&' being escaped already; if it is
// then '&amp;' will be re-escaped as '&amp;amp;'.
//
/*
The values are:
" &quot;
' &apos;
< &lt;
> &gt;
& &amp;
*/
func XMLEscapeChars(b bool) {
xmlEscapeChars = b
}
// Scan for '&' first, since 's' may contain "&amp;" that is parsed to "&amp;amp;"
// - or "&lt;" that is parsed to "&amp;lt;".
var escapechars = [][2][]byte{
{[]byte(`&`), []byte(`&amp;`)},
{[]byte(`<`), []byte(`&lt;`)},
{[]byte(`>`), []byte(`&gt;`)},
{[]byte(`"`), []byte(`&quot;`)},
{[]byte(`'`), []byte(`&apos;`)},
}
func escapeChars(s string) string {
if len(s) == 0 {
return s
}
b := []byte(s)
for _, v := range escapechars {
n := bytes.Count(b, v[0])
if n == 0 {
continue
}
b = bytes.Replace(b, v[0], v[1], n)
}
return string(b)
}

View file

@ -1,7 +0,0 @@
package mxj
// Checks whether the path exists
func (mv Map) Exists(path string, subkeys ...string) bool {
v, err := mv.ValuesForPath(path, subkeys...)
return err == nil && len(v) > 0
}

4
vendor/github.com/clbanning/mxj/v2/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,4 @@
language: go
go:
- 1.x

22
vendor/github.com/clbanning/mxj/v2/LICENSE generated vendored Normal file
View file

@ -0,0 +1,22 @@
Copyright (c) 2012-2021 Charles Banning <clbanning@gmail.com>. All rights reserved.
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,6 +1,7 @@
package mxj
import (
"bytes"
"encoding/xml"
"reflect"
)
@ -50,6 +51,8 @@ const (
<element>3.14159265</element>
<element>true</element>
</mydoc>
An extreme example is available in examples/goofy_map.go.
*/
// Alternative values for DefaultRootTag and DefaultElementTag can be set as:
// AnyXml( v, myRootTag, myElementTag).
@ -77,40 +80,43 @@ func AnyXml(v interface{}, tags ...string) ([]byte, error) {
}
var err error
s := new(string)
s := new(bytes.Buffer)
p := new(pretty)
var ss string
var b []byte
switch v.(type) {
case []interface{}:
ss = "<" + rt + ">"
if _, err = s.WriteString("<" + rt + ">"); err != nil {
return nil, err
}
for _, vv := range v.([]interface{}) {
switch vv.(type) {
case map[string]interface{}:
m := vv.(map[string]interface{})
if len(m) == 1 {
for tag, val := range m {
err = mapToXmlIndent(false, s, tag, val, p)
err = marshalMapToXmlIndent(false, s, tag, val, p)
}
} else {
err = mapToXmlIndent(false, s, et, vv, p)
err = marshalMapToXmlIndent(false, s, et, vv, p)
}
default:
err = mapToXmlIndent(false, s, et, vv, p)
err = marshalMapToXmlIndent(false, s, et, vv, p)
}
if err != nil {
break
}
}
ss += *s + "</" + rt + ">"
b = []byte(ss)
if _, err = s.WriteString("</" + rt + ">"); err != nil {
return nil, err
}
b = s.Bytes()
case map[string]interface{}:
m := Map(v.(map[string]interface{}))
b, err = m.Xml(rt)
default:
err = mapToXmlIndent(false, s, rt, v, p)
b = []byte(*s)
err = marshalMapToXmlIndent(false, s, rt, v, p)
b = s.Bytes()
}
return b, err
@ -143,16 +149,17 @@ func AnyXmlIndent(v interface{}, prefix, indent string, tags ...string) ([]byte,
}
var err error
s := new(string)
s := new(bytes.Buffer)
p := new(pretty)
p.indent = indent
p.padding = prefix
var ss string
var b []byte
switch v.(type) {
case []interface{}:
ss = "<" + rt + ">\n"
if _, err = s.WriteString("<" + rt + ">\n"); err != nil {
return nil, err
}
p.Indent()
for _, vv := range v.([]interface{}) {
switch vv.(type) {
@ -160,29 +167,34 @@ func AnyXmlIndent(v interface{}, prefix, indent string, tags ...string) ([]byte,
m := vv.(map[string]interface{})
if len(m) == 1 {
for tag, val := range m {
err = mapToXmlIndent(true, s, tag, val, p)
err = marshalMapToXmlIndent(true, s, tag, val, p)
}
} else {
p.start = 1 // we 1 tag in
err = mapToXmlIndent(true, s, et, vv, p)
*s += "\n"
err = marshalMapToXmlIndent(true, s, et, vv, p)
// *s += "\n"
if _, err = s.WriteString("\n"); err != nil {
return nil, err
}
}
default:
p.start = 0 // in case trailing p.start = 1
err = mapToXmlIndent(true, s, et, vv, p)
err = marshalMapToXmlIndent(true, s, et, vv, p)
}
if err != nil {
break
}
}
ss += *s + "</" + rt + ">"
b = []byte(ss)
if _, err = s.WriteString(`</` + rt + `>`); err != nil {
return nil, err
}
b = s.Bytes()
case map[string]interface{}:
m := Map(v.(map[string]interface{}))
b, err = m.XmlIndent(prefix, indent, rt)
default:
err = mapToXmlIndent(true, s, rt, v, p)
b = []byte(*s)
err = marshalMapToXmlIndent(true, s, rt, v, p)
b = s.Bytes()
}
return b, err

View file

@ -1,6 +1,6 @@
// mxj - A collection of map[string]interface{} and associated XML and JSON utilities.
// Copyright 2012-2015, 2018 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// Copyright 2012-2019, Charles Banning. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file
/*
@ -14,6 +14,15 @@ Related Packages:
checkxml: github.com/clbanning/checkxml provides functions for validating XML data.
Notes:
2022.11.28: v2.7 - add SetGlobalKeyMapPrefix to change default prefix, '#', for default keys
2022.11.20: v2.6 - add NewMapForattedXmlSeq for XML docs formatted with whitespace character
2021.02.02: v2.5 - add XmlCheckIsValid toggle to force checking that the encoded XML is valid
2020.12.14: v2.4 - add XMLEscapeCharsDecoder to preserve XML escaped characters in Map values
2020.10.28: v2.3 - add TrimWhiteSpace option
2020.05.01: v2.2 - optimize map to XML encoding for large XML docs.
2019.07.04: v2.0 - remove unnecessary methods - mv.XmlWriterRaw, mv.XmlIndentWriterRaw - for Map and MapSeq.
2019.07.04: Add MapSeq type and move associated functions and methods from Map to MapSeq.
2019.01.21: DecodeSimpleValuesAsMap - decode to map[<tag>:map["#text":<value>]] rather than map[<tag>:<value>].
2018.04.18: mv.Xml/mv.XmlIndent encodes non-map[string]interface{} map values - map[string]string, map[int]uint, etc.
2018.03.29: mv.Gob/NewMapGob support gob encoding/decoding of Maps.
2018.03.26: Added mxj/x2j-wrapper sub-package for migrating from legacy x2j package.

93
vendor/github.com/clbanning/mxj/v2/escapechars.go generated vendored Normal file
View file

@ -0,0 +1,93 @@
// Copyright 2016 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
package mxj
import (
"bytes"
)
var xmlEscapeChars bool
// XMLEscapeChars(true) forces escaping invalid characters in attribute and element values.
// NOTE: this is brute force with NO interrogation of '&' being escaped already; if it is
// then '&amp;' will be re-escaped as '&amp;amp;'.
//
/*
The values are:
" &quot;
' &apos;
< &lt;
> &gt;
& &amp;
*/
//
// Note: if XMLEscapeCharsDecoder(true) has been called - or the default, 'false,' value
// has been toggled to 'true' - then XMLEscapeChars(true) is ignored. If XMLEscapeChars(true)
// has already been called before XMLEscapeCharsDecoder(true), XMLEscapeChars(false) is called
// to turn escape encoding on mv.Xml, etc., to prevent double escaping ampersands, '&'.
func XMLEscapeChars(b ...bool) {
var bb bool
if len(b) == 0 {
bb = !xmlEscapeChars
} else {
bb = b[0]
}
if bb == true && xmlEscapeCharsDecoder == false {
xmlEscapeChars = true
} else {
xmlEscapeChars = false
}
}
// Scan for '&' first, since 's' may contain "&amp;" that is parsed to "&amp;amp;"
// - or "&lt;" that is parsed to "&amp;lt;".
var escapechars = [][2][]byte{
{[]byte(`&`), []byte(`&amp;`)},
{[]byte(`<`), []byte(`&lt;`)},
{[]byte(`>`), []byte(`&gt;`)},
{[]byte(`"`), []byte(`&quot;`)},
{[]byte(`'`), []byte(`&apos;`)},
}
func escapeChars(s string) string {
if len(s) == 0 {
return s
}
b := []byte(s)
for _, v := range escapechars {
n := bytes.Count(b, v[0])
if n == 0 {
continue
}
b = bytes.Replace(b, v[0], v[1], n)
}
return string(b)
}
// per issue #84, escape CharData values from xml.Decoder
var xmlEscapeCharsDecoder bool
// XMLEscapeCharsDecoder(b ...bool) escapes XML characters in xml.CharData values
// returned by Decoder.Token. Thus, the internal Map values will contain escaped
// values, and you do not need to set XMLEscapeChars for proper encoding.
//
// By default, the Map values have the non-escaped values returned by Decoder.Token.
// XMLEscapeCharsDecoder(true) - or, XMLEscapeCharsDecoder() - will toggle escape
// encoding 'on.'
//
// Note: if XMLEscapeCharDecoder(true) is call then XMLEscapeChars(false) is
// called to prevent re-escaping the values on encoding using mv.Xml, etc.
func XMLEscapeCharsDecoder(b ...bool) {
if len(b) == 0 {
xmlEscapeCharsDecoder = !xmlEscapeCharsDecoder
} else {
xmlEscapeCharsDecoder = b[0]
}
if xmlEscapeCharsDecoder == true && xmlEscapeChars == true {
xmlEscapeChars = false
}
}

9
vendor/github.com/clbanning/mxj/v2/exists.go generated vendored Normal file
View file

@ -0,0 +1,9 @@
package mxj
// Checks whether the path exists. If err != nil then 'false' is returned
// along with the error encountered parsing either the "path" or "subkeys"
// argument.
func (mv Map) Exists(path string, subkeys ...string) (bool, error) {
v, err := mv.ValuesForPath(path, subkeys...)
return (err == nil && len(v) > 0), err
}

3
vendor/github.com/clbanning/mxj/v2/go.mod generated vendored Normal file
View file

@ -0,0 +1,3 @@
module github.com/clbanning/mxj/v2
go 1.15

View file

@ -21,7 +21,7 @@ const (
var defaultArraySize int = minArraySize
// Adjust the buffers for expected number of values to return from ValuesForKey() and ValuesForPath().
// SetArraySize adjust the buffers for expected number of values to return from ValuesForKey() and ValuesForPath().
// This can have the effect of significantly reducing memory allocation-copy functions for large data sets.
// Returns the initial buffer size.
func SetArraySize(size int) int {
@ -33,11 +33,12 @@ func SetArraySize(size int) int {
return defaultArraySize
}
// Return all values in Map, 'mv', associated with a 'key'. If len(returned_values) == 0, then no match.
// ValuesForKey return all values in Map, 'mv', associated with a 'key'. If len(returned_values) == 0, then no match.
// On error, the returned slice is 'nil'. NOTE: 'key' can be wildcard, "*".
// 'subkeys' (optional) are "key:val[:type]" strings representing attributes or elements in a list.
// - By default 'val' is of type string. "key:val:bool" and "key:val:float" to coerce them.
// - For attributes prefix the label with a hyphen, '-', e.g., "-seq:3".
// - For attributes prefix the label with the attribute prefix character, by default a
// hyphen, '-', e.g., "-seq:3". (See SetAttrPrefix function.)
// - If the 'key' refers to a list, then "key:value" could select a list member of the list.
// - The subkey can be wildcarded - "key:*" - to require that it's there with some value.
// - If a subkey is preceeded with the '!' character, the key:value[:type] entry is treated as an
@ -149,7 +150,7 @@ func hasKey(iv interface{}, key string, ret *[]interface{}, cnt *int, subkeys ma
// of wildcards and unindexed arrays. Embedding such logic into valuesForKeyPath() would have made the
// code much more complicated; this wrapper is straightforward, easy to debug, and doesn't add significant overhead.
// Retrieve all values for a path from the Map. If len(returned_values) == 0, then no match.
// ValuesForPatb retrieves all values for a path from the Map. If len(returned_values) == 0, then no match.
// On error, the returned array is 'nil'.
// 'path' is a dot-separated path of key values.
// - If a node in the path is '*', then everything beyond is walked.
@ -157,7 +158,8 @@ func hasKey(iv interface{}, key string, ret *[]interface{}, cnt *int, subkeys ma
// even "*[2].*[0].field".
// 'subkeys' (optional) are "key:val[:type]" strings representing attributes or elements in a list.
// - By default 'val' is of type string. "key:val:bool" and "key:val:float" to coerce them.
// - For attributes prefix the label with a hyphen, '-', e.g., "-seq:3".
// - For attributes prefix the label with the attribute prefix character, by default a
// hyphen, '-', e.g., "-seq:3". (See SetAttrPrefix function.)
// - If the 'path' refers to a list, then "tag:value" would return member of the list.
// - The subkey can be wildcarded - "key:*" - to require that it's there with some value.
// - If a subkey is preceeded with the '!' character, the key:value[:type] entry is treated as an
@ -545,7 +547,7 @@ func getSubKeyMap(kv ...string) (map[string]interface{}, error) {
//----------------------------- find all paths to a key --------------------------------
// Get all paths through Map, 'mv', (in dot-notation) that terminate with the specified key.
// PathsForKey returns all paths through Map, 'mv', (in dot-notation) that terminate with the specified key.
// Results can be used with ValuesForPath.
func (mv Map) PathsForKey(key string) []string {
m := map[string]interface{}(mv)
@ -568,7 +570,7 @@ func (mv Map) PathsForKey(key string) []string {
return res
}
// Extract the shortest path from all possible paths - from PathsForKey() - in Map, 'mv'..
// PathForKeyShortest extracts the shortest path from all possible paths - from PathsForKey() - in Map, 'mv'..
// Paths are strings using dot-notation.
func (mv Map) PathForKeyShortest(key string) string {
paths := mv.PathsForKey(key)
@ -632,7 +634,7 @@ func hasKeyPath(crumbs string, iv interface{}, key string, basket map[string]boo
var PathNotExistError = errors.New("Path does not exist")
// ValueForPath wrap ValuesFor Path and returns the first value returned.
// ValueForPath wraps ValuesFor Path and returns the first value returned.
// If no value is found it returns 'nil' and PathNotExistError.
func (mv Map) ValueForPath(path string) (interface{}, error) {
vals, err := mv.ValuesForPath(path)
@ -645,7 +647,7 @@ func (mv Map) ValueForPath(path string) (interface{}, error) {
return vals[0], nil
}
// Returns the first found value for the path as a string.
// ValuesForPathString returns the first found value for the path as a string.
func (mv Map) ValueForPathString(path string) (string, error) {
vals, err := mv.ValuesForPath(path)
if err != nil {
@ -655,15 +657,10 @@ func (mv Map) ValueForPathString(path string) (string, error) {
return "", errors.New("ValueForPath: path not found")
}
val := vals[0]
switch str := val.(type) {
case string:
return str, nil
default:
return "", fmt.Errorf("ValueForPath: unsupported type: %T", str)
}
return fmt.Sprintf("%v", val), nil
}
// Returns the first found value for the path as a string.
// ValueOrEmptyForPathString returns the first found value for the path as a string.
// If the path is not found then it returns an empty string.
func (mv Map) ValueOrEmptyForPathString(path string) string {
str, _ := mv.ValueForPathString(path)

View file

@ -44,7 +44,7 @@ func (mv Map) LeafNodes(no_attr ...bool) []LeafNode {
func getLeafNodes(path, node string, mv interface{}, l *[]LeafNode, noattr bool) {
// if stripping attributes, then also strip "#text" key
if !noattr || node != "#text" {
if !noattr || node != textK {
if path != "" && node[:1] != "[" {
path += "."
}

View file

@ -3,10 +3,31 @@ Decode/encode XML to/from map[string]interface{} (or JSON) values, and extract/m
mxj supplants the legacy x2j and j2x packages. If you want the old syntax, use mxj/x2j and mxj/j2x packages.
<h4>Installation</h4>
Using go.mod:
<pre>
go get github.com/clbanning/mxj/v2@v2.7
</pre>
<pre>
import "github.com/clbanning/mxj/v2"
</pre>
... or just vendor the package.
<h4>Related Packages</h4>
https://github.com/clbanning/checkxml provides functions for validating XML data.
<h4>Refactor Encoder - 2020.05.01</h4>
Issue #70 highlighted that encoding large maps does not scale well, since the original logic used string appends operations. Using bytes.Buffer results in linear scaling for very large XML docs. (Metrics based on MacBook Pro i7 w/ 16 GB.)
Nodes m.XML() time
54809 12.53708ms
109780 32.403183ms
164678 59.826412ms
482598 109.358007ms
<h4>Refactor Decoder - 2015.11.15</h4>
For over a year I've wanted to refactor the XML-to-map[string]interface{} decoder to make it more performant. I recently took the time to do that, since we were using github.com/clbanning/mxj in a production system that could be deployed on a Raspberry Pi. Now the decoder is comparable to the stdlib JSON-to-map[string]interface{} decoder in terms of its additional processing overhead relative to decoding to a structure value. As shown by:
@ -21,6 +42,15 @@ For over a year I've wanted to refactor the XML-to-map[string]interface{} decode
<h4>Notices</h4>
2022.11.28: v2.7 - add SetGlobalKeyMapPrefix to change default prefix, '#', for default keys
2022.11.20: v2.6 - add NewMapForattedXmlSeq for XML docs formatted with whitespace character
2021.02.02: v2.5 - add XmlCheckIsValid toggle to force checking that the encoded XML is valid
2020.12.14: v2.4 - add XMLEscapeCharsDecoder to preserve XML escaped characters in Map values
2020.10.28: v2.3 - add TrimWhiteSpace option
2020.05.01: v2.2 - optimize map to XML encoding for large XML docs.
2019.07.04: v2.0 - remove unnecessary methods - mv.XmlWriterRaw, mv.XmlIndentWriterRaw - for Map and MapSeq.
2019.07.04: Add MapSeq type and move associated functions and methods from Map to MapSeq.
2019.01.21: DecodeSimpleValuesAsMap - decode to map[<tag>:map["#text":<value>]] rather than map[<tag>:<value>]
2018.04.18: mv.Xml/mv.XmlIndent encodes non-map[string]interface{} map values - map[string]string, map[int]uint, etc.
2018.03.29: mv.Gob/NewMapGob support gob encoding/decoding of Maps.
2018.03.26: Added mxj/x2j-wrapper sub-package for migrating from legacy x2j package.

View file

@ -6,13 +6,20 @@ import (
)
// RenameKey renames a key in a Map.
// It works only for nested maps. It doesn't work for cases when it buried in a list.
// It works only for nested maps.
// It doesn't work for cases when the key is in a list.
func (mv Map) RenameKey(path string, newName string) error {
if !mv.Exists(path) {
var v bool
var err error
if v, err = mv.Exists(path); err == nil && !v {
return errors.New("RenameKey: path not found: " + path)
} else if err != nil {
return err
}
if mv.Exists(parentPath(path) + "." + newName) {
if v, err = mv.Exists(parentPath(path) + "." + newName); err == nil && v {
return errors.New("RenameKey: key already exists: " + newName)
} else if err != nil {
return err
}
m := map[string]interface{}(mv)

View file

@ -21,9 +21,11 @@ import (
// 'path' is dot-notation list of keys to traverse; last key in path can be newVal key
// NOTE: 'path' spec does not currently support indexed array references.
// 'subkeys' are "key:value[:type]" entries that must match for path node
// The subkey can be wildcarded - "key:*" - to require that it's there with some value.
// If a subkey is preceeded with the '!' character, the key:value[:type] entry is treated as an
// exclusion critera - e.g., "!author:William T. Gaddis".
// - For attributes prefix the label with the attribute prefix character, by default a
// hyphen, '-', e.g., "-seq:3". (See SetAttrPrefix function.)
// - The subkey can be wildcarded - "key:*" - to require that it's there with some value.
// - If a subkey is preceeded with the '!' character, the key:value[:type] entry is treated as an
// exclusion critera - e.g., "!author:William T. Gaddis".
//
// NOTES:
// 1. Simple elements with attributes need a path terminated as ".#text" to modify the actual value.

View file

@ -1,4 +1,4 @@
// Copyright 2012-2016 Charles Banning. All rights reserved.
// Copyright 2012-2016, 2018-2019 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
@ -22,6 +22,30 @@ import (
"time"
)
var (
textK = "#text"
seqK = "#seq"
commentK = "#comment"
attrK = "#attr"
directiveK = "#directive"
procinstK = "#procinst"
targetK = "#target"
instK = "#inst"
)
// Support overriding default Map keys prefix
func SetGlobalKeyMapPrefix(s string) {
textK = strings.ReplaceAll(textK, textK[0:1], s)
seqK = strings.ReplaceAll(seqK, seqK[0:1], s)
commentK = strings.ReplaceAll(commentK, commentK[0:1], s)
directiveK = strings.ReplaceAll(directiveK, directiveK[0:1], s)
procinstK = strings.ReplaceAll(procinstK, procinstK[0:1], s)
targetK = strings.ReplaceAll(targetK, targetK[0:1], s)
instK = strings.ReplaceAll(instK, instK[0:1], s)
attrK = strings.ReplaceAll(attrK, attrK[0:1], s)
}
// ------------------- NewMapXml & NewMapXmlReader ... -------------------------
// If XmlCharsetReader != nil, it will be used to decode the XML, if required.
@ -52,10 +76,12 @@ var XmlCharsetReader func(charset string, input io.Reader) (io.Reader, error)
// }
//
// NOTES:
// 1. The 'xmlVal' will be parsed looking for an xml.StartElement, so BOM and other
// 1. Declarations, directives, process instructions and comments are NOT parsed.
// 2. The 'xmlVal' will be parsed looking for an xml.StartElement, so BOM and other
// extraneous xml.CharData will be ignored unless io.EOF is reached first.
// 2. If CoerceKeysToLower() has been called, then all key values will be lower case.
// 3. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case.
// 3. If CoerceKeysToLower() has been called, then all key values will be lower case.
// 4. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case.
// 5. If DisableTrimWhiteSpace(b bool) has been called, then all values will be trimmed or not. 'true' by default.
func NewMapXml(xmlVal []byte, cast ...bool) (Map, error) {
var r bool
if len(cast) == 1 {
@ -66,10 +92,11 @@ func NewMapXml(xmlVal []byte, cast ...bool) (Map, error) {
// Get next XML doc from an io.Reader as a Map value. Returns Map value.
// NOTES:
// 1. The 'xmlReader' will be parsed looking for an xml.StartElement, so BOM and other
// 1. Declarations, directives, process instructions and comments are NOT parsed.
// 2. The 'xmlReader' will be parsed looking for an xml.StartElement, so BOM and other
// extraneous xml.CharData will be ignored unless io.EOF is reached first.
// 2. If CoerceKeysToLower() has been called, then all key values will be lower case.
// 3. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case.
// 3. If CoerceKeysToLower() has been called, then all key values will be lower case.
// 4. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case.
func NewMapXmlReader(xmlReader io.Reader, cast ...bool) (Map, error) {
var r bool
if len(cast) == 1 {
@ -89,16 +116,17 @@ func NewMapXmlReader(xmlReader io.Reader, cast ...bool) (Map, error) {
// Get next XML doc from an io.Reader as a Map value. Returns Map value and slice with the raw XML.
// NOTES:
// 1. Due to the implementation of xml.Decoder, the raw XML off the reader is buffered to []byte
// 1. Declarations, directives, process instructions and comments are NOT parsed.
// 2. Due to the implementation of xml.Decoder, the raw XML off the reader is buffered to []byte
// using a ByteReader. If the io.Reader is an os.File, there may be significant performance impact.
// See the examples - getmetrics1.go through getmetrics4.go - for comparative use cases on a large
// data set. If the io.Reader is wrapping a []byte value in-memory, however, such as http.Request.Body
// you CAN use it to efficiently unmarshal a XML doc and retrieve the raw XML in a single call.
// 2. The 'raw' return value may be larger than the XML text value.
// 3. The 'xmlReader' will be parsed looking for an xml.StartElement, so BOM and other
// 3. The 'raw' return value may be larger than the XML text value.
// 4. The 'xmlReader' will be parsed looking for an xml.StartElement, so BOM and other
// extraneous xml.CharData will be ignored unless io.EOF is reached first.
// 4. If CoerceKeysToLower() has been called, then all key values will be lower case.
// 5. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case.
// 5. If CoerceKeysToLower() has been called, then all key values will be lower case.
// 6. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case.
func NewMapXmlReaderRaw(xmlReader io.Reader, cast ...bool) (Map, []byte, error) {
var r bool
if len(cast) == 1 {
@ -205,8 +233,12 @@ var includeTagSeqNum bool
}
}
*/
func IncludeTagSeqNum(b bool) {
includeTagSeqNum = b
func IncludeTagSeqNum(b ...bool) {
if len(b) == 0 {
includeTagSeqNum = !includeTagSeqNum
} else if len(b) == 1 {
includeTagSeqNum = b[0]
}
}
// all keys will be "lower case"
@ -228,6 +260,26 @@ func CoerceKeysToLower(b ...bool) {
}
}
// disableTrimWhiteSpace sets if the white space should be removed or not
var disableTrimWhiteSpace bool
var trimRunes = "\t\r\b\n "
// DisableTrimWhiteSpace set if the white space should be trimmed or not. By default white space is always trimmed. If
// no argument is provided, trim white space will be disabled.
func DisableTrimWhiteSpace(b ...bool) {
if len(b) == 0 {
disableTrimWhiteSpace = true
} else {
disableTrimWhiteSpace = b[0]
}
if disableTrimWhiteSpace {
trimRunes = "\t\r\b\n"
} else {
trimRunes = "\t\r\b\n "
}
}
// 25jun16: Allow user to specify the "prefix" character for XML attribute key labels.
// We do this by replacing '`' constant with attrPrefix var, replacing useHyphen with attrPrefix = "",
// and adding a SetAttrPrefix(s string) function.
@ -258,6 +310,21 @@ func CoerceKeysToSnakeCase(b ...bool) {
}
}
// 10jan19: use of pull request #57 should be conditional - legacy code assumes
// numeric values are float64.
var castToInt bool
// CastValuesToInt tries to coerce numeric valus to int64 or uint64 instead of the
// default float64. Repeated calls with no argument will toggle this on/off, or this
// handling will be set with the value of 'b'.
func CastValuesToInt(b ...bool) {
if len(b) == 0 {
castToInt = !castToInt
} else if len(b) == 1 {
castToInt = b[0]
}
}
// 05feb17: support processing XMPP streams (issue #36)
var handleXMPPStreamTag bool
@ -329,7 +396,10 @@ func xmlToMapParser(skey string, a []xml.Attr, p *xml.Decoder, r bool) (map[stri
if lowerCase {
key = strings.ToLower(key)
}
na[key] = cast(v.Value, r)
if xmlEscapeCharsDecoder { // per issue#84
v.Value = escapeChars(v.Value)
}
na[key] = cast(v.Value, r, key)
}
}
}
@ -395,7 +465,7 @@ func xmlToMapParser(skey string, a []xml.Attr, p *xml.Decoder, r bool) (map[stri
val.(map[string]interface{})["_seq"] = seq // will overwrite an "_seq" XML tag
seq++
case interface{}: // a non-nil simple element: string, float64, bool
v := map[string]interface{}{"#text": val}
v := map[string]interface{}{textK: val}
v["_seq"] = seq
seq++
val = v
@ -430,16 +500,25 @@ func xmlToMapParser(skey string, a []xml.Attr, p *xml.Decoder, r bool) (map[stri
} else {
n[skey] = "" // empty element
}
} else if len(n) == 1 && len(na) > 0 {
// it's a simple element w/ no attributes w/ subelements
for _, v := range n {
na[textK] = v
}
n[skey] = na
}
return n, nil
case xml.CharData:
// clean up possible noise
tt := strings.Trim(string(t.(xml.CharData)), "\t\r\b\n ")
tt := strings.Trim(string(t.(xml.CharData)), trimRunes)
if xmlEscapeCharsDecoder { // issue#84
tt = escapeChars(tt)
}
if len(tt) > 0 {
if len(na) > 0 || decodeSimpleValuesAsMap {
na["#text"] = cast(tt, r)
na[textK] = cast(tt, r, textK)
} else if skey != "" {
n[skey] = cast(tt, r)
n[skey] = cast(tt, r, skey)
} else {
// per Adrian (http://www.adrianlungu.com/) catch stray text
// in decoder stream -
@ -459,12 +538,23 @@ var castNanInf bool
// Cast "Nan", "Inf", "-Inf" XML values to 'float64'.
// By default, these values will be decoded as 'string'.
func CastNanInf(b bool) {
castNanInf = b
func CastNanInf(b ...bool) {
if len(b) == 0 {
castNanInf = !castNanInf
} else if len(b) == 1 {
castNanInf = b[0]
}
}
// cast - try to cast string values to bool or float64
func cast(s string, r bool) interface{} {
// 't' is the tag key that can be checked for 'not-casting'
func cast(s string, r bool, t string) interface{} {
if checkTagToSkip != nil && t != "" && checkTagToSkip(t) {
// call the check-function here with 't[0]'
// if 'true' return s
return s
}
if r {
// handle nan and inf
if !castNanInf {
@ -475,17 +565,31 @@ func cast(s string, r bool) interface{} {
}
// handle numeric strings ahead of boolean
if f, err := strconv.ParseFloat(s, 64); err == nil {
return f
if castToInt {
if f, err := strconv.ParseInt(s, 10, 64); err == nil {
return f
}
if f, err := strconv.ParseUint(s, 10, 64); err == nil {
return f
}
}
if castToFloat {
if f, err := strconv.ParseFloat(s, 64); err == nil {
return f
}
}
// ParseBool treats "1"==true & "0"==false, we've already scanned those
// values as float64. See if value has 't' or 'f' as initial screen to
// minimize calls to ParseBool; also, see if len(s) < 6.
if len(s) > 0 && len(s) < 6 {
switch s[:1] {
case "t", "T", "f", "F":
if b, err := strconv.ParseBool(s); err == nil {
return b
if castToBool {
if len(s) > 0 && len(s) < 6 {
switch s[:1] {
case "t", "T", "f", "F":
if b, err := strconv.ParseBool(s); err == nil {
return b
}
}
}
}
@ -493,6 +597,47 @@ func cast(s string, r bool) interface{} {
return s
}
// pull request, #59
var castToFloat = true
// CastValuesToFloat can be used to skip casting to float64 when
// "cast" argument is 'true' in NewMapXml, etc.
// Default is true.
func CastValuesToFloat(b ...bool) {
if len(b) == 0 {
castToFloat = !castToFloat
} else if len(b) == 1 {
castToFloat = b[0]
}
}
var castToBool = true
// CastValuesToBool can be used to skip casting to bool when
// "cast" argument is 'true' in NewMapXml, etc.
// Default is true.
func CastValuesToBool(b ...bool) {
if len(b) == 0 {
castToBool = !castToBool
} else if len(b) == 1 {
castToBool = b[0]
}
}
// checkTagToSkip - switch to address Issue #58
var checkTagToSkip func(string) bool
// SetCheckTagToSkipFunc registers function to test whether the value
// for a tag should be cast to bool or float64 when "cast" argument is 'true'.
// (Dot tag path notation is not supported.)
// NOTE: key may be "#text" if it's a simple element with attributes
// or "decodeSimpleValuesAsMap == true".
// NOTE: does not apply to NewMapXmlSeq... functions.
func SetCheckTagToSkipFunc(fn func(string) bool) {
checkTagToSkip = fn
}
// ------------------ END: NewMapXml & NewMapXmlReader -------------------------
// ------------------ mv.Xml & mv.XmlWriter - from j2x ------------------------
@ -518,6 +663,20 @@ func XmlDefaultEmptyElemSyntax() {
useGoXmlEmptyElemSyntax = false
}
// ------- issue #88 ----------
// xmlCheckIsValid set switch to force decoding the encoded XML to
// see if it is valid XML.
var xmlCheckIsValid bool
// XmlCheckIsValid forces the encoded XML to be checked for validity.
func XmlCheckIsValid(b ...bool) {
if len(b) == 1 {
xmlCheckIsValid = b[0]
return
}
xmlCheckIsValid = !xmlCheckIsValid
}
// Encode a Map as XML. The companion of NewMapXml().
// The following rules apply.
// - The key label "#text" is treated as the value for a simple element with attributes.
@ -537,7 +696,7 @@ func XmlDefaultEmptyElemSyntax() {
func (mv Map) Xml(rootTag ...string) ([]byte, error) {
m := map[string]interface{}(mv)
var err error
s := new(string)
b := new(bytes.Buffer)
p := new(pretty) // just a stub
if len(m) == 1 && len(rootTag) == 0 {
@ -551,20 +710,32 @@ func (mv Map) Xml(rootTag ...string) ([]byte, error) {
switch v.(type) {
case map[string]interface{}: // noop
default: // anything else
err = mapToXmlIndent(false, s, DefaultRootTag, m, p)
err = marshalMapToXmlIndent(false, b, DefaultRootTag, m, p)
goto done
}
}
}
err = mapToXmlIndent(false, s, key, value, p)
err = marshalMapToXmlIndent(false, b, key, value, p)
}
} else if len(rootTag) == 1 {
err = mapToXmlIndent(false, s, rootTag[0], m, p)
err = marshalMapToXmlIndent(false, b, rootTag[0], m, p)
} else {
err = mapToXmlIndent(false, s, DefaultRootTag, m, p)
err = marshalMapToXmlIndent(false, b, DefaultRootTag, m, p)
}
done:
return []byte(*s), err
if xmlCheckIsValid {
d := xml.NewDecoder(bytes.NewReader(b.Bytes()))
for {
_, err = d.Token()
if err == io.EOF {
err = nil
break
} else if err != nil {
return nil, err
}
}
}
return b.Bytes(), err
}
// The following implementation is provided only for symmetry with NewMapXmlReader[Raw]
@ -584,6 +755,7 @@ func (mv Map) XmlWriter(xmlWriter io.Writer, rootTag ...string) error {
// Writes the Map as XML on the Writer. []byte is the raw XML that was written.
// See Xml() for encoding rules.
/*
func (mv Map) XmlWriterRaw(xmlWriter io.Writer, rootTag ...string) ([]byte, error) {
x, err := mv.Xml(rootTag...)
if err != nil {
@ -593,6 +765,7 @@ func (mv Map) XmlWriterRaw(xmlWriter io.Writer, rootTag ...string) ([]byte, erro
_, err = xmlWriter.Write(x)
return x, err
}
*/
// Writes the Map as pretty XML on the Writer.
// See Xml() for encoding rules.
@ -608,6 +781,7 @@ func (mv Map) XmlIndentWriter(xmlWriter io.Writer, prefix, indent string, rootTa
// Writes the Map as pretty XML on the Writer. []byte is the raw XML that was written.
// See Xml() for encoding rules.
/*
func (mv Map) XmlIndentWriterRaw(xmlWriter io.Writer, prefix, indent string, rootTag ...string) ([]byte, error) {
x, err := mv.XmlIndent(prefix, indent, rootTag...)
if err != nil {
@ -617,6 +791,7 @@ func (mv Map) XmlIndentWriterRaw(xmlWriter io.Writer, prefix, indent string, roo
_, err = xmlWriter.Write(x)
return x, err
}
*/
// -------------------- END: mv.Xml & mv.XmlWriter -------------------------------
@ -762,6 +937,7 @@ func (b *byteReader) Read(p []byte) (int, error) {
func (b *byteReader) ReadByte() (byte, error) {
_, err := b.r.Read(b.b)
if len(b.b) > 0 {
// issue #38
return b.b[0], err
}
var c byte
@ -778,7 +954,7 @@ func (mv Map) XmlIndent(prefix, indent string, rootTag ...string) ([]byte, error
m := map[string]interface{}(mv)
var err error
s := new(string)
b := new(bytes.Buffer)
p := new(pretty)
p.indent = indent
p.padding = prefix
@ -788,17 +964,29 @@ func (mv Map) XmlIndent(prefix, indent string, rootTag ...string) ([]byte, error
// use it if it isn't a key for a list
for key, value := range m {
if _, ok := value.([]interface{}); ok {
err = mapToXmlIndent(true, s, DefaultRootTag, m, p)
err = marshalMapToXmlIndent(true, b, DefaultRootTag, m, p)
} else {
err = mapToXmlIndent(true, s, key, value, p)
err = marshalMapToXmlIndent(true, b, key, value, p)
}
}
} else if len(rootTag) == 1 {
err = mapToXmlIndent(true, s, rootTag[0], m, p)
err = marshalMapToXmlIndent(true, b, rootTag[0], m, p)
} else {
err = mapToXmlIndent(true, s, DefaultRootTag, m, p)
err = marshalMapToXmlIndent(true, b, DefaultRootTag, m, p)
}
return []byte(*s), err
if xmlCheckIsValid {
d := xml.NewDecoder(bytes.NewReader(b.Bytes()))
for {
_, err = d.Token()
if err == io.EOF {
err = nil
break
} else if err != nil {
return nil, err
}
}
}
return b.Bytes(), err
}
type pretty struct {
@ -823,7 +1011,9 @@ func (p *pretty) Outdent() {
// where the work actually happens
// returns an error if an attribute is not atomic
func mapToXmlIndent(doIndent bool, s *string, key string, value interface{}, pp *pretty) error {
// NOTE: 01may20 - replaces mapToXmlIndent(); uses bytes.Buffer instead for string appends.
func marshalMapToXmlIndent(doIndent bool, b *bytes.Buffer, key string, value interface{}, pp *pretty) error {
var err error
var endTag bool
var isSimple bool
var elen int
@ -845,14 +1035,49 @@ func mapToXmlIndent(doIndent bool, s *string, key string, value interface{}, pp
}
}
// 14jul20. The following block of code has become something of a catch all for odd stuff
// that might be passed in as a result of casting an arbitrary map[<T>]<T> to an mxj.Map
// value and then call m.Xml or m.XmlIndent. See issue #71 (and #73) for such edge cases.
switch value.(type) {
// special handling of []interface{} values when len(value) == 0
// these types are handled during encoding
case map[string]interface{}, []byte, string, float64, bool, int, int32, int64, float32, json.Number:
if doIndent {
*s += p.padding
case []map[string]interface{}, []string, []float64, []bool, []int, []int32, []int64, []float32, []json.Number:
case []interface{}:
case nil:
value = ""
default:
// see if value is a struct, if so marshal using encoding/xml package
if reflect.ValueOf(value).Kind() == reflect.Struct {
if v, err := xml.Marshal(value); err != nil {
return err
} else {
value = string(v)
}
} else {
// coerce eveything else into a string value
value = fmt.Sprint(value)
}
*s += `<` + key
}
// start the XML tag with required indentaton and padding
if doIndent {
switch value.(type) {
case []interface{}, []string:
// list processing handles indentation for all elements
default:
if _, err = b.WriteString(p.padding); err != nil {
return err
}
}
}
switch value.(type) {
case []interface{}:
default:
if _, err = b.WriteString(`<` + key); err != nil {
return err
}
}
switch value.(type) {
case map[string]interface{}:
vv := value.(map[string]interface{})
@ -893,21 +1118,29 @@ func mapToXmlIndent(doIndent bool, s *string, key string, value interface{}, pp
attrlist = attrlist[:n]
sort.Sort(attrList(attrlist))
for _, v := range attrlist {
*s += ` ` + v[0] + `="` + v[1] + `"`
if _, err = b.WriteString(` ` + v[0] + `="` + v[1] + `"`); err != nil {
return err
}
}
}
// only attributes?
if n == lenvv {
if useGoXmlEmptyElemSyntax {
*s += `</` + key + ">"
if _, err = b.WriteString(`</` + key + ">"); err != nil {
return err
}
} else {
*s += `/>`
if _, err = b.WriteString(`/>`); err != nil {
return err
}
}
break
}
// simple element? Note: '#text" is an invalid XML tag.
if v, ok := vv["#text"]; ok && n+1 == lenvv {
isComplex := false
if v, ok := vv[textK]; ok && n+1 == lenvv {
// just the value and attributes
switch v.(type) {
case string:
if xmlEscapeChars {
@ -918,25 +1151,51 @@ func mapToXmlIndent(doIndent bool, s *string, key string, value interface{}, pp
case []byte:
if xmlEscapeChars {
v = escapeChars(string(v.([]byte)))
} else {
v = string(v.([]byte))
}
}
*s += ">" + fmt.Sprintf("%v", v)
if _, err = b.WriteString(">" + fmt.Sprintf("%v", v)); err != nil {
return err
}
endTag = true
elen = 1
isSimple = true
break
} else if ok {
// Handle edge case where simple element with attributes
// is unmarshal'd using NewMapXml() where attribute prefix
// has been set to "".
// TODO(clb): should probably scan all keys for invalid chars.
return fmt.Errorf("invalid attribute key label: #text - due to attributes not being prefixed")
// need to handle when there are subelements in addition to the simple element value
// issue #90
switch v.(type) {
case string:
if xmlEscapeChars {
v = escapeChars(v.(string))
} else {
v = v.(string)
}
case []byte:
if xmlEscapeChars {
v = escapeChars(string(v.([]byte)))
} else {
v = string(v.([]byte))
}
}
if _, err = b.WriteString(">" + fmt.Sprintf("%v", v)); err != nil {
return err
}
isComplex = true
}
// close tag with possible attributes
*s += ">"
if !isComplex {
if _, err = b.WriteString(">"); err != nil {
return err
}
}
if doIndent {
*s += "\n"
// *s += "\n"
if _, err = b.WriteString("\n"); err != nil {
return err
}
}
// something more complex
p.mapDepth++
@ -944,6 +1203,10 @@ func mapToXmlIndent(doIndent bool, s *string, key string, value interface{}, pp
elemlist := make([][2]interface{}, len(vv))
n = 0
for k, v := range vv {
if k == textK {
// simple element handled above
continue
}
if lenAttrPrefix > 0 && lenAttrPrefix < len(k) && k[:lenAttrPrefix] == attrPrefix {
continue
}
@ -963,7 +1226,7 @@ func mapToXmlIndent(doIndent bool, s *string, key string, value interface{}, pp
}
}
i++
if err := mapToXmlIndent(doIndent, s, v[0].(string), v[1], p); err != nil {
if err := marshalMapToXmlIndent(doIndent, b, v[0].(string), v[1], p); err != nil {
return err
}
switch v[1].(type) {
@ -982,9 +1245,13 @@ func mapToXmlIndent(doIndent bool, s *string, key string, value interface{}, pp
// special case - found during implementing Issue #23
if len(value.([]interface{})) == 0 {
if doIndent {
*s += p.padding + p.indent
if _, err = b.WriteString(p.padding + p.indent); err != nil {
return err
}
}
if _, err = b.WriteString("<" + key); err != nil {
return err
}
*s += "<" + key
elen = 0
endTag = true
break
@ -993,7 +1260,7 @@ func mapToXmlIndent(doIndent bool, s *string, key string, value interface{}, pp
if doIndent {
p.Indent()
}
if err := mapToXmlIndent(doIndent, s, key, v, p); err != nil {
if err := marshalMapToXmlIndent(doIndent, b, key, v, p); err != nil {
return err
}
if doIndent {
@ -1010,9 +1277,13 @@ func mapToXmlIndent(doIndent bool, s *string, key string, value interface{}, pp
//[]string should be treated exaclty as []interface{}
if len(value.([]string)) == 0 {
if doIndent {
*s += p.padding + p.indent
if _, err = b.WriteString(p.padding + p.indent); err != nil {
return err
}
}
if _, err = b.WriteString("<" + key); err != nil {
return err
}
*s += "<" + key
elen = 0
endTag = true
break
@ -1021,7 +1292,7 @@ func mapToXmlIndent(doIndent bool, s *string, key string, value interface{}, pp
if doIndent {
p.Indent()
}
if err := mapToXmlIndent(doIndent, s, key, v, p); err != nil {
if err := marshalMapToXmlIndent(doIndent, b, key, v, p); err != nil {
return err
}
if doIndent {
@ -1032,9 +1303,14 @@ func mapToXmlIndent(doIndent bool, s *string, key string, value interface{}, pp
case nil:
// terminate the tag
if doIndent {
*s += p.padding
// *s += p.padding
if _, err = b.WriteString(p.padding); err != nil {
return err
}
}
if _, err = b.WriteString("<" + key); err != nil {
return err
}
*s += "<" + key
endTag, isSimple = true, true
break
default: // handle anything - even goofy stuff
@ -1047,12 +1323,17 @@ func mapToXmlIndent(doIndent bool, s *string, key string, value interface{}, pp
}
elen = len(v)
if elen > 0 {
*s += ">" + v
// *s += ">" + v
if _, err = b.WriteString(">" + v); err != nil {
return err
}
}
case float64, bool, int, int32, int64, float32, json.Number:
v := fmt.Sprintf("%v", value)
elen = len(v) // always > 0
*s += ">" + v
if _, err = b.WriteString(">" + v); err != nil {
return err
}
case []byte: // NOTE: byte is just an alias for uint8
// similar to how xml.Marshal handles []byte structure members
v := string(value.([]byte))
@ -1061,9 +1342,15 @@ func mapToXmlIndent(doIndent bool, s *string, key string, value interface{}, pp
}
elen = len(v)
if elen > 0 {
*s += ">" + v
// *s += ">" + v
if _, err = b.WriteString(">" + v); err != nil {
return err
}
}
default:
if _, err = b.WriteString(">"); err != nil {
return err
}
var v []byte
var err error
if doIndent {
@ -1072,11 +1359,15 @@ func mapToXmlIndent(doIndent bool, s *string, key string, value interface{}, pp
v, err = xml.Marshal(value)
}
if err != nil {
*s += ">UNKNOWN"
if _, err = b.WriteString(">UNKNOWN"); err != nil {
return err
}
} else {
elen = len(v)
if elen > 0 {
*s += string(v)
if _, err = b.Write(v); err != nil {
return err
}
}
}
}
@ -1086,21 +1377,31 @@ func mapToXmlIndent(doIndent bool, s *string, key string, value interface{}, pp
if endTag {
if doIndent {
if !isSimple {
*s += p.padding
if _, err = b.WriteString(p.padding); err != nil {
return err
}
}
}
if elen > 0 || useGoXmlEmptyElemSyntax {
if elen == 0 {
*s += ">"
if _, err = b.WriteString(">"); err != nil {
return err
}
}
if _, err = b.WriteString(`</` + key + ">"); err != nil {
return err
}
*s += `</` + key + ">"
} else {
*s += `/>`
if _, err = b.WriteString(`/>`); err != nil {
return err
}
}
}
if doIndent {
if p.cnt > p.start {
*s += "\n"
if _, err = b.WriteString("\n"); err != nil {
return err
}
}
p.Outdent()
}

View file

@ -1,4 +1,4 @@
// Copyright 2012-2016 Charles Banning. All rights reserved.
// Copyright 2012-2016, 2019 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
@ -13,22 +13,29 @@ import (
"errors"
"fmt"
"io"
"regexp"
"sort"
"strings"
)
// MapSeq is like Map but contains seqencing indices to allow recovering the original order of
// the XML elements when the map[string]interface{} is marshaled. Element attributes are
// stored as a map["#attr"]map[<attr_key>]map[string]interface{}{"#text":"<value>", "#seq":<attr_index>}
// value instead of denoting the keys with a prefix character. Also, comments, directives and
// process instructions are preserved.
type MapSeq map[string]interface{}
// NoRoot is returned by NewXmlSeq, etc., when a comment, directive or procinstr element is parsed
// in the XML data stream and the element is not contained in an XML object with a root element.
var NoRoot = errors.New("no root key")
var NO_ROOT = NoRoot // maintain backwards compatibility
// ------------------- NewMapXmlSeq & NewMapXmlSeqReader ... -------------------------
// This is only useful if you want to re-encode the Map as XML using mv.XmlSeq(), etc., to preserve the original structure.
// The xml.Decoder.RawToken method is used to parse the XML, so there is no checking for appropriate xml.EndElement values;
// thus, it is assumed that the XML is valid.
//
// NewMapXmlSeq - convert a XML doc into a Map with elements id'd with decoding sequence int - #seq.
// NewMapXmlSeq converts a XML doc into a MapSeq value with elements id'd with decoding sequence key represented
// as map["#seq"]<int value>.
// If the optional argument 'cast' is 'true', then values will be converted to boolean or float64 if possible.
// NOTE: "#seq" key/value pairs are removed on encoding with mv.XmlSeq() / mv.XmlSeqIndent().
// NOTE: "#seq" key/value pairs are removed on encoding with msv.Xml() / msv.XmlIndent().
// • attributes are a map - map["#attr"]map["attr_key"]map[string]interface{}{"#text":<aval>, "#seq":<num>}
// • all simple elements are decoded as map["#text"]interface{} with a "#seq" k:v pair, as well.
// • lists always decode as map["list_tag"][]map[string]interface{} where the array elements are maps that
@ -50,7 +57,7 @@ var NO_ROOT = NoRoot // maintain backwards compatibility
// newtag :
// #seq :[int] 1
// #text :[string] value 2
// It will encode in proper sequence even though the Map representation merges all "ltag" elements in an array.
// It will encode in proper sequence even though the MapSeq representation merges all "ltag" elements in an array.
// • comments - "<!--comment-->" - are decoded as map["#comment"]map["#text"]"cmnt_text" with a "#seq" k:v pair.
// • directives - "<!text>" - are decoded as map["#directive"]map[#text"]"directive_text" with a "#seq" k:v pair.
// • process instructions - "<?instr?>" - are decoded as map["#procinst"]interface{} where the #procinst value
@ -68,10 +75,16 @@ var NO_ROOT = NoRoot // maintain backwards compatibility
// 3. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case.
//
// NAME SPACES:
// 1. Keys in the Map value that are parsed from a <name space prefix>:<local name> tag preserve the
// 1. Keys in the MapSeq value that are parsed from a <name space prefix>:<local name> tag preserve the
// "<prefix>:" notation rather than stripping it as with NewMapXml().
// 2. Attribute keys for name space prefix declarations preserve "xmlns:<prefix>" notation.
func NewMapXmlSeq(xmlVal []byte, cast ...bool) (Map, error) {
//
// ERRORS:
// 1. If a NoRoot error, "no root key," is returned, check the initial map key for a "#comment",
// "#directive" or #procinst" key.
// 2. Unmarshaling an XML doc that is formatted using the whitespace character, " ", will error, since
// Decoder.RawToken treats such occurances as significant. See NewMapFormattedXmlSeq().
func NewMapXmlSeq(xmlVal []byte, cast ...bool) (MapSeq, error) {
var r bool
if len(cast) == 1 {
r = cast[0]
@ -79,16 +92,40 @@ func NewMapXmlSeq(xmlVal []byte, cast ...bool) (Map, error) {
return xmlSeqToMap(xmlVal, r)
}
// This is only useful if you want to re-encode the Map as XML using mv.XmlSeq(), etc., to preserve the original structure.
// NewMapFormattedXmlSeq performs the same as NewMapXmlSeq but is useful for processing XML objects that
// are formatted using the whitespace character, " ". (The stdlib xml.Decoder, by default, treats all
// whitespace as significant; Decoder.Token() and Decoder.RawToken() will return strings of one or more
// whitespace characters and without alphanumeric or punctuation characters as xml.CharData values.)
//
// Get next XML doc from an io.Reader as a Map value. Returns Map value.
// If you're processing such XML, then this will convert all occurrences of whitespace-only strings
// into an empty string, "", prior to parsing the XML - irrespective of whether the occurrence is
// formatting or is a actual element value.
func NewMapFormattedXmlSeq(xmlVal []byte, cast ...bool) (MapSeq, error) {
var c bool
if len(cast) == 1 {
c = cast[0]
}
// Per PR #104 - clean out formatting characters so they don't show up in Decoder.RawToken() stream.
// NOTE: Also replaces element values that are solely comprised of formatting/whitespace characters
// with empty string, "".
r := regexp.MustCompile(`>[\n\t\r ]*<`)
xmlVal = r.ReplaceAll(xmlVal, []byte("><"))
return xmlSeqToMap(xmlVal, c)
}
// NewMpaXmlSeqReader returns next XML doc from an io.Reader as a MapSeq value.
// NOTES:
// 1. The 'xmlReader' will be parsed looking for an xml.StartElement, xml.Comment, etc., so BOM and other
// extraneous xml.CharData will be ignored unless io.EOF is reached first.
// 2. CoerceKeysToLower() is NOT recognized, since the intent here is to eventually call m.XmlSeq() to
// re-encode the message in its original structure.
// 3. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case.
func NewMapXmlSeqReader(xmlReader io.Reader, cast ...bool) (Map, error) {
//
// ERRORS:
// 1. If a NoRoot error, "no root key," is returned, check the initial map key for a "#comment",
// "#directive" or #procinst" key.
func NewMapXmlSeqReader(xmlReader io.Reader, cast ...bool) (MapSeq, error) {
var r bool
if len(cast) == 1 {
r = cast[0]
@ -105,9 +142,8 @@ func NewMapXmlSeqReader(xmlReader io.Reader, cast ...bool) (Map, error) {
return xmlSeqReaderToMap(xmlReader, r)
}
// This is only useful if you want to re-encode the Map as XML using mv.XmlSeq(), etc., to preserve the original structure.
//
// Get next XML doc from an io.Reader as a Map value. Returns Map value and slice with the raw XML.
// NewMapXmlSeqReaderRaw returns the next XML doc from an io.Reader as a MapSeq value.
// Returns MapSeq value, slice with the raw XML, and any error.
// NOTES:
// 1. Due to the implementation of xml.Decoder, the raw XML off the reader is buffered to []byte
// using a ByteReader. If the io.Reader is an os.File, there may be significant performance impact.
@ -120,7 +156,11 @@ func NewMapXmlSeqReader(xmlReader io.Reader, cast ...bool) (Map, error) {
// 4. CoerceKeysToLower() is NOT recognized, since the intent here is to eventually call m.XmlSeq() to
// re-encode the message in its original structure.
// 5. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case.
func NewMapXmlSeqReaderRaw(xmlReader io.Reader, cast ...bool) (Map, []byte, error) {
//
// ERRORS:
// 1. If a NoRoot error, "no root key," is returned, check if the initial map key is "#comment",
// "#directive" or #procinst" key.
func NewMapXmlSeqReaderRaw(xmlReader io.Reader, cast ...bool) (MapSeq, []byte, error) {
var r bool
if len(cast) == 1 {
r = cast[0]
@ -193,13 +233,16 @@ func xmlSeqToMapParser(skey string, a []xml.Attr, p *xml.Decoder, r bool) (map[s
if snakeCaseKeys {
v.Name.Local = strings.Replace(v.Name.Local, "-", "_", -1)
}
if xmlEscapeCharsDecoder { // per issue#84
v.Value = escapeChars(v.Value)
}
if len(v.Name.Space) > 0 {
aa[v.Name.Space+`:`+v.Name.Local] = map[string]interface{}{"#text": cast(v.Value, r), "#seq": i}
aa[v.Name.Space+`:`+v.Name.Local] = map[string]interface{}{textK: cast(v.Value, r, ""), seqK: i}
} else {
aa[v.Name.Local] = map[string]interface{}{"#text": cast(v.Value, r), "#seq": i}
aa[v.Name.Local] = map[string]interface{}{textK: cast(v.Value, r, ""), seqK: i}
}
}
na["#attr"] = aa
na[attrK] = aa
}
}
@ -267,10 +310,10 @@ func xmlSeqToMapParser(skey string, a []xml.Attr, p *xml.Decoder, r bool) (map[s
// where all the "list" subelements are decoded into an array.
switch val.(type) {
case map[string]interface{}:
val.(map[string]interface{})["#seq"] = seq
val.(map[string]interface{})[seqK] = seq
seq++
case interface{}: // a non-nil simple element: string, float64, bool
v := map[string]interface{}{"#text": val, "#seq": seq}
v := map[string]interface{}{textK: val, seqK: seq}
seq++
val = v
}
@ -323,7 +366,10 @@ func xmlSeqToMapParser(skey string, a []xml.Attr, p *xml.Decoder, r bool) (map[s
return n, nil
case xml.CharData:
// clean up possible noise
tt := strings.Trim(string(t.(xml.CharData)), "\t\r\b\n ")
tt := strings.Trim(string(t.(xml.CharData)), trimRunes)
if xmlEscapeCharsDecoder { // issue#84
tt = escapeChars(tt)
}
if skey == "" {
// per Adrian (http://www.adrianlungu.com/) catch stray text
// in decoder stream -
@ -334,42 +380,42 @@ func xmlSeqToMapParser(skey string, a []xml.Attr, p *xml.Decoder, r bool) (map[s
}
if len(tt) > 0 {
// every simple element is a #text and has #seq associated with it
na["#text"] = cast(tt, r)
na["#seq"] = seq
na[textK] = cast(tt, r, "")
na[seqK] = seq
seq++
}
case xml.Comment:
if n == nil { // no root 'key'
n = map[string]interface{}{"#comment": string(t.(xml.Comment))}
n = map[string]interface{}{commentK: string(t.(xml.Comment))}
return n, NoRoot
}
cm := make(map[string]interface{}, 2)
cm["#text"] = string(t.(xml.Comment))
cm["#seq"] = seq
cm[textK] = string(t.(xml.Comment))
cm[seqK] = seq
seq++
na["#comment"] = cm
na[commentK] = cm
case xml.Directive:
if n == nil { // no root 'key'
n = map[string]interface{}{"#directive": string(t.(xml.Directive))}
n = map[string]interface{}{directiveK: string(t.(xml.Directive))}
return n, NoRoot
}
dm := make(map[string]interface{}, 2)
dm["#text"] = string(t.(xml.Directive))
dm["#seq"] = seq
dm[textK] = string(t.(xml.Directive))
dm[seqK] = seq
seq++
na["#directive"] = dm
na[directiveK] = dm
case xml.ProcInst:
if n == nil {
na = map[string]interface{}{"#target": t.(xml.ProcInst).Target, "#inst": string(t.(xml.ProcInst).Inst)}
n = map[string]interface{}{"#procinst": na}
na = map[string]interface{}{targetK: t.(xml.ProcInst).Target, instK: string(t.(xml.ProcInst).Inst)}
n = map[string]interface{}{procinstK: na}
return n, NoRoot
}
pm := make(map[string]interface{}, 3)
pm["#target"] = t.(xml.ProcInst).Target
pm["#inst"] = string(t.(xml.ProcInst).Inst)
pm["#seq"] = seq
pm[targetK] = t.(xml.ProcInst).Target
pm[instK] = string(t.(xml.ProcInst).Inst)
pm[seqK] = seq
seq++
na["#procinst"] = pm
na[procinstK] = pm
default:
// noop - shouldn't ever get here, now, since we handle all token types
}
@ -380,12 +426,9 @@ func xmlSeqToMapParser(skey string, a []xml.Attr, p *xml.Decoder, r bool) (map[s
// --------------------- mv.XmlSeq & mv.XmlSeqWriter -------------------------
// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Encode a Map as XML with elements sorted on #seq. The companion of NewMapXmlSeq().
// Xml encodes a MapSeq as XML with elements sorted on #seq. The companion of NewMapXmlSeq().
// The following rules apply.
// - The key label "#text" is treated as the value for a simple element with attributes.
// - The "#seq" key is used to seqence the subelements or attributes but is ignored for writing.
// - The "#seq" key value is used to seqence the subelements or attributes only.
// - The "#attr" map key identifies the map of attribute map[string]interface{} values with "#text" key.
// - The "#comment" map key identifies a comment in the value "#text" map entry - <!--comment-->.
// - The "#directive" map key identifies a directive in the value "#text" map entry - <!directive>.
@ -399,7 +442,7 @@ func xmlSeqToMapParser(skey string, a []xml.Attr, p *xml.Decoder, r bool) (map[s
// - Elements with only attribute values or are null are terminated using "/>" unless XmlGoEmptyElemSystax() called.
// - If len(mv) == 1 and no rootTag is provided, then the map key is used as the root tag, possible.
// Thus, `{ "key":"value" }` encodes as "<key>value</key>".
func (mv Map) XmlSeq(rootTag ...string) ([]byte, error) {
func (mv MapSeq) Xml(rootTag ...string) ([]byte, error) {
m := map[string]interface{}(mv)
var err error
s := new(string)
@ -429,18 +472,28 @@ func (mv Map) XmlSeq(rootTag ...string) ([]byte, error) {
err = mapToXmlSeqIndent(false, s, DefaultRootTag, m, p)
}
done:
if xmlCheckIsValid {
d := xml.NewDecoder(bytes.NewReader([]byte(*s)))
for {
_, err = d.Token()
if err == io.EOF {
err = nil
break
} else if err != nil {
return nil, err
}
}
}
return []byte(*s), err
}
// The following implementation is provided only for symmetry with NewMapXmlReader[Raw]
// The names will also provide a key for the number of return arguments.
// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Writes the Map as XML on the Writer.
// See XmlSeq() for encoding rules.
func (mv Map) XmlSeqWriter(xmlWriter io.Writer, rootTag ...string) error {
x, err := mv.XmlSeq(rootTag...)
// XmlWriter Writes the MapSeq value as XML on the Writer.
// See MapSeq.Xml() for encoding rules.
func (mv MapSeq) XmlWriter(xmlWriter io.Writer, rootTag ...string) error {
x, err := mv.Xml(rootTag...)
if err != nil {
return err
}
@ -449,12 +502,11 @@ func (mv Map) XmlSeqWriter(xmlWriter io.Writer, rootTag ...string) error {
return err
}
// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Writes the Map as XML on the Writer. []byte is the raw XML that was written.
// See XmlSeq() for encoding rules.
func (mv Map) XmlSeqWriterRaw(xmlWriter io.Writer, rootTag ...string) ([]byte, error) {
x, err := mv.XmlSeq(rootTag...)
// XmlWriteRaw writes the MapSeq value as XML on the Writer. []byte is the raw XML that was written.
// See Map.XmlSeq() for encoding rules.
/*
func (mv MapSeq) XmlWriterRaw(xmlWriter io.Writer, rootTag ...string) ([]byte, error) {
x, err := mv.Xml(rootTag...)
if err != nil {
return x, err
}
@ -462,13 +514,12 @@ func (mv Map) XmlSeqWriterRaw(xmlWriter io.Writer, rootTag ...string) ([]byte, e
_, err = xmlWriter.Write(x)
return x, err
}
*/
// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Writes the Map as pretty XML on the Writer.
// See Xml() for encoding rules.
func (mv Map) XmlSeqIndentWriter(xmlWriter io.Writer, prefix, indent string, rootTag ...string) error {
x, err := mv.XmlSeqIndent(prefix, indent, rootTag...)
// XmlIndentWriter writes the MapSeq value as pretty XML on the Writer.
// See MapSeq.Xml() for encoding rules.
func (mv MapSeq) XmlIndentWriter(xmlWriter io.Writer, prefix, indent string, rootTag ...string) error {
x, err := mv.XmlIndent(prefix, indent, rootTag...)
if err != nil {
return err
}
@ -477,11 +528,10 @@ func (mv Map) XmlSeqIndentWriter(xmlWriter io.Writer, prefix, indent string, roo
return err
}
// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Writes the Map as pretty XML on the Writer. []byte is the raw XML that was written.
// See XmlSeq() for encoding rules.
func (mv Map) XmlSeqIndentWriterRaw(xmlWriter io.Writer, prefix, indent string, rootTag ...string) ([]byte, error) {
// XmlIndentWriterRaw writes the Map as pretty XML on the Writer. []byte is the raw XML that was written.
// See Map.XmlSeq() for encoding rules.
/*
func (mv MapSeq) XmlIndentWriterRaw(xmlWriter io.Writer, prefix, indent string, rootTag ...string) ([]byte, error) {
x, err := mv.XmlSeqIndent(prefix, indent, rootTag...)
if err != nil {
return x, err
@ -490,16 +540,15 @@ func (mv Map) XmlSeqIndentWriterRaw(xmlWriter io.Writer, prefix, indent string,
_, err = xmlWriter.Write(x)
return x, err
}
*/
// -------------------- END: mv.Xml & mv.XmlWriter -------------------------------
// ---------------------- XmlSeqIndent ----------------------------
// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Encode a map[string]interface{} as a pretty XML string.
// See mv.XmlSeq() for encoding rules.
func (mv Map) XmlSeqIndent(prefix, indent string, rootTag ...string) ([]byte, error) {
// XmlIndent encodes a map[string]interface{} as a pretty XML string.
// See MapSeq.XmlSeq() for encoding rules.
func (mv MapSeq) XmlIndent(prefix, indent string, rootTag ...string) ([]byte, error) {
m := map[string]interface{}(mv)
var err error
@ -523,6 +572,21 @@ func (mv Map) XmlSeqIndent(prefix, indent string, rootTag ...string) ([]byte, er
} else {
err = mapToXmlSeqIndent(true, s, DefaultRootTag, m, p)
}
if xmlCheckIsValid {
if _, err = NewMapXml([]byte(*s)); err != nil {
return nil, err
}
d := xml.NewDecoder(bytes.NewReader([]byte(*s)))
for {
_, err = d.Token()
if err == io.EOF {
err = nil
break
} else if err != nil {
return nil, err
}
}
}
return []byte(*s), err
}
@ -541,7 +605,7 @@ func mapToXmlSeqIndent(doIndent bool, s *string, key string, value interface{},
if doIndent {
*s += p.padding
}
if key != "#comment" && key != "#directive" && key != "#procinst" {
if key != commentK && key != directiveK && key != procinstK {
*s += `<` + key
}
}
@ -549,27 +613,27 @@ func mapToXmlSeqIndent(doIndent bool, s *string, key string, value interface{},
case map[string]interface{}:
val := value.(map[string]interface{})
if key == "#comment" {
*s += `<!--` + val["#text"].(string) + `-->`
if key == commentK {
*s += `<!--` + val[textK].(string) + `-->`
noEndTag = true
break
}
if key == "#directive" {
*s += `<!` + val["#text"].(string) + `>`
if key == directiveK {
*s += `<!` + val[textK].(string) + `>`
noEndTag = true
break
}
if key == "#procinst" {
*s += `<?` + val["#target"].(string) + ` ` + val["#inst"].(string) + `?>`
if key == procinstK {
*s += `<?` + val[targetK].(string) + ` ` + val[instK].(string) + `?>`
noEndTag = true
break
}
haveAttrs := false
// process attributes first
if v, ok := val["#attr"].(map[string]interface{}); ok {
if v, ok := val[attrK].(map[string]interface{}); ok {
// First, unroll the map[string]interface{} into a []keyval array.
// Then sequence it.
kv := make([]keyval, len(v))
@ -582,21 +646,21 @@ func mapToXmlSeqIndent(doIndent bool, s *string, key string, value interface{},
// Now encode the attributes in original decoding sequence, using keyval array.
for _, a := range kv {
vv := a.v.(map[string]interface{})
switch vv["#text"].(type) {
switch vv[textK].(type) {
case string:
if xmlEscapeChars {
ss = escapeChars(vv["#text"].(string))
ss = escapeChars(vv[textK].(string))
} else {
ss = vv["#text"].(string)
ss = vv[textK].(string)
}
*s += ` ` + a.k + `="` + ss + `"`
case float64, bool, int, int32, int64, float32:
*s += ` ` + a.k + `="` + fmt.Sprintf("%v", vv["#text"]) + `"`
*s += ` ` + a.k + `="` + fmt.Sprintf("%v", vv[textK]) + `"`
case []byte:
if xmlEscapeChars {
ss = escapeChars(string(vv["#text"].([]byte)))
ss = escapeChars(string(vv[textK].([]byte)))
} else {
ss = string(vv["#text"].([]byte))
ss = string(vv[textK].([]byte))
}
*s += ` ` + a.k + `="` + ss + `"`
default:
@ -608,8 +672,8 @@ func mapToXmlSeqIndent(doIndent bool, s *string, key string, value interface{},
// simple element?
// every map value has, at least, "#seq" and, perhaps, "#text" and/or "#attr"
_, seqOK := val["#seq"] // have key
if v, ok := val["#text"]; ok && ((len(val) == 3 && haveAttrs) || (len(val) == 2 && !haveAttrs)) && seqOK {
_, seqOK := val[seqK] // have key
if v, ok := val[textK]; ok && ((len(val) == 3 && haveAttrs) || (len(val) == 2 && !haveAttrs)) && seqOK {
if stmp, ok := v.(string); ok && stmp != "" {
if xmlEscapeChars {
stmp = escapeChars(stmp)
@ -630,10 +694,10 @@ func mapToXmlSeqIndent(doIndent bool, s *string, key string, value interface{},
// 'kv' will hold everything that needs to be written
kv := make([]keyval, 0)
for k, v := range val {
if k == "#attr" { // already processed
if k == attrK { // already processed
continue
}
if k == "#seq" { // ignore - just for sorting
if k == seqK { // ignore - just for sorting
continue
}
switch v.(type) {
@ -804,13 +868,22 @@ func (e elemListSeq) Swap(i, j int) {
func (e elemListSeq) Less(i, j int) bool {
var iseq, jseq int
var fiseq, fjseq float64
var ok bool
if iseq, ok = e[i].v.(map[string]interface{})["#seq"].(int); !ok {
iseq = 9999999
if iseq, ok = e[i].v.(map[string]interface{})[seqK].(int); !ok {
if fiseq, ok = e[i].v.(map[string]interface{})[seqK].(float64); ok {
iseq = int(fiseq)
} else {
iseq = 9999999
}
}
if jseq, ok = e[j].v.(map[string]interface{})["#seq"].(int); !ok {
jseq = 9999999
if jseq, ok = e[j].v.(map[string]interface{})[seqK].(int); !ok {
if fjseq, ok = e[j].v.(map[string]interface{})[seqK].(float64); ok {
jseq = int(fjseq)
} else {
jseq = 9999999
}
}
return iseq <= jseq
@ -819,10 +892,11 @@ func (e elemListSeq) Less(i, j int) bool {
// =============== https://groups.google.com/forum/#!topic/golang-nuts/lHPOHD-8qio
// BeautifyXml (re)formats an XML doc similar to Map.XmlIndent().
// It preserves comments, directives and process instructions,
func BeautifyXml(b []byte, prefix, indent string) ([]byte, error) {
x, err := NewMapXmlSeq(b)
if err != nil {
return nil, err
}
return x.XmlSeqIndent(prefix, indent)
return x.XmlIndent(prefix, indent)
}

18
vendor/github.com/clbanning/mxj/v2/xmlseq2.go generated vendored Normal file
View file

@ -0,0 +1,18 @@
// Copyright 2012-2016, 2019 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
package mxj
// ---------------- expose Map methods to MapSeq type ---------------------------
// Pretty print a Map.
func (msv MapSeq) StringIndent(offset ...int) string {
return writeMap(map[string]interface{}(msv), true, true, offset...)
}
// Pretty print a Map without the value type information - just key:value entries.
func (msv MapSeq) StringIndentNoTypeInfo(offset ...int) string {
return writeMap(map[string]interface{}(msv), false, true, offset...)
}

6
vendor/modules.txt vendored
View file

@ -1,6 +1,6 @@
# github.com/clbanning/mxj v1.8.4
# github.com/clbanning/mxj/v2 v2.7.0
## explicit
github.com/clbanning/mxj
github.com/clbanning/mxj/v2
# github.com/dustin/go-humanize v1.0.1
## explicit
github.com/dustin/go-humanize
@ -16,6 +16,8 @@ github.com/go-chi/chi/v5/middleware
# github.com/gofrs/uuid/v5 v5.0.0
## explicit
github.com/gofrs/uuid/v5
# github.com/google/go-cmp v0.6.0
## explicit
# github.com/gorilla/mux v1.8.1
## explicit
github.com/gorilla/mux