From 843ee65d8c8b2bf7835a586332fefb9a490b06d2 Mon Sep 17 00:00:00 2001 From: Cameron Moore Date: Fri, 29 Mar 2024 13:15:05 -0500 Subject: [PATCH] Update mxj dependency to v2.7.0 --- go.mod | 3 +- go.sum | 6 +- internal/hook/request.go | 2 +- vendor/github.com/clbanning/mxj/LICENSE | 55 --- .../github.com/clbanning/mxj/escapechars.go | 54 --- vendor/github.com/clbanning/mxj/exists.go | 7 - .../github.com/clbanning/mxj/v2/.travis.yml | 4 + vendor/github.com/clbanning/mxj/v2/LICENSE | 22 + .../clbanning/mxj/{ => v2}/anyxml.go | 54 ++- .../clbanning/mxj/{ => v2}/atomFeedString.xml | 0 .../github.com/clbanning/mxj/{ => v2}/doc.go | 13 +- .../clbanning/mxj/v2/escapechars.go | 93 ++++ vendor/github.com/clbanning/mxj/v2/exists.go | 9 + .../clbanning/mxj/{ => v2}/files.go | 0 .../clbanning/mxj/{ => v2}/files_test.badjson | 0 .../clbanning/mxj/{ => v2}/files_test.badxml | 0 .../clbanning/mxj/{ => v2}/files_test.json | 0 .../clbanning/mxj/{ => v2}/files_test.xml | 0 .../mxj/{ => v2}/files_test_dup.json | 0 .../clbanning/mxj/{ => v2}/files_test_dup.xml | 0 .../mxj/{ => v2}/files_test_indent.json | 0 .../mxj/{ => v2}/files_test_indent.xml | 0 vendor/github.com/clbanning/mxj/v2/go.mod | 3 + .../github.com/clbanning/mxj/{ => v2}/gob.go | 0 .../github.com/clbanning/mxj/{ => v2}/json.go | 0 .../clbanning/mxj/{ => v2}/keyvalues.go | 29 +- .../clbanning/mxj/{ => v2}/leafnode.go | 2 +- .../github.com/clbanning/mxj/{ => v2}/misc.go | 0 .../github.com/clbanning/mxj/{ => v2}/mxj.go | 0 .../clbanning/mxj/{ => v2}/newmap.go | 0 .../clbanning/mxj/{ => v2}/readme.md | 30 ++ .../clbanning/mxj/{ => v2}/remove.go | 0 .../clbanning/mxj/{ => v2}/rename.go | 13 +- .../github.com/clbanning/mxj/{ => v2}/set.go | 0 .../clbanning/mxj/{ => v2}/setfieldsep.go | 0 .../clbanning/mxj/{ => v2}/songtext.xml | 0 .../clbanning/mxj/{ => v2}/strict.go | 0 .../clbanning/mxj/{ => v2}/struct.go | 0 .../clbanning/mxj/{ => v2}/updatevalues.go | 8 +- .../github.com/clbanning/mxj/{ => v2}/xml.go | 455 +++++++++++++++--- .../clbanning/mxj/{ => v2}/xmlseq.go | 266 ++++++---- vendor/github.com/clbanning/mxj/v2/xmlseq2.go | 18 + vendor/modules.txt | 6 +- 43 files changed, 811 insertions(+), 341 deletions(-) delete mode 100644 vendor/github.com/clbanning/mxj/LICENSE delete mode 100644 vendor/github.com/clbanning/mxj/escapechars.go delete mode 100644 vendor/github.com/clbanning/mxj/exists.go create mode 100644 vendor/github.com/clbanning/mxj/v2/.travis.yml create mode 100644 vendor/github.com/clbanning/mxj/v2/LICENSE rename vendor/github.com/clbanning/mxj/{ => v2}/anyxml.go (76%) rename vendor/github.com/clbanning/mxj/{ => v2}/atomFeedString.xml (100%) rename vendor/github.com/clbanning/mxj/{ => v2}/doc.go (89%) create mode 100644 vendor/github.com/clbanning/mxj/v2/escapechars.go create mode 100644 vendor/github.com/clbanning/mxj/v2/exists.go rename vendor/github.com/clbanning/mxj/{ => v2}/files.go (100%) rename vendor/github.com/clbanning/mxj/{ => v2}/files_test.badjson (100%) rename vendor/github.com/clbanning/mxj/{ => v2}/files_test.badxml (100%) rename vendor/github.com/clbanning/mxj/{ => v2}/files_test.json (100%) rename vendor/github.com/clbanning/mxj/{ => v2}/files_test.xml (100%) rename vendor/github.com/clbanning/mxj/{ => v2}/files_test_dup.json (100%) rename vendor/github.com/clbanning/mxj/{ => v2}/files_test_dup.xml (100%) rename vendor/github.com/clbanning/mxj/{ => v2}/files_test_indent.json (100%) rename vendor/github.com/clbanning/mxj/{ => v2}/files_test_indent.xml (100%) create mode 100644 vendor/github.com/clbanning/mxj/v2/go.mod rename vendor/github.com/clbanning/mxj/{ => v2}/gob.go (100%) rename vendor/github.com/clbanning/mxj/{ => v2}/json.go (100%) rename vendor/github.com/clbanning/mxj/{ => v2}/keyvalues.go (93%) rename vendor/github.com/clbanning/mxj/{ => v2}/leafnode.go (99%) rename vendor/github.com/clbanning/mxj/{ => v2}/misc.go (100%) rename vendor/github.com/clbanning/mxj/{ => v2}/mxj.go (100%) rename vendor/github.com/clbanning/mxj/{ => v2}/newmap.go (100%) rename vendor/github.com/clbanning/mxj/{ => v2}/readme.md (88%) rename vendor/github.com/clbanning/mxj/{ => v2}/remove.go (100%) rename vendor/github.com/clbanning/mxj/{ => v2}/rename.go (79%) rename vendor/github.com/clbanning/mxj/{ => v2}/set.go (100%) rename vendor/github.com/clbanning/mxj/{ => v2}/setfieldsep.go (100%) rename vendor/github.com/clbanning/mxj/{ => v2}/songtext.xml (100%) rename vendor/github.com/clbanning/mxj/{ => v2}/strict.go (100%) rename vendor/github.com/clbanning/mxj/{ => v2}/struct.go (100%) rename vendor/github.com/clbanning/mxj/{ => v2}/updatevalues.go (94%) rename vendor/github.com/clbanning/mxj/{ => v2}/xml.go (73%) rename vendor/github.com/clbanning/mxj/{ => v2}/xmlseq.go (73%) create mode 100644 vendor/github.com/clbanning/mxj/v2/xmlseq2.go diff --git a/go.mod b/go.mod index 4fb1d13..3b1c66b 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 2cfbd39..96b8595 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/hook/request.go b/internal/hook/request.go index 643d5a8..6e0bd0c 100644 --- a/internal/hook/request.go +++ b/internal/hook/request.go @@ -8,7 +8,7 @@ import ( "net/url" "unicode" - "github.com/clbanning/mxj" + "github.com/clbanning/mxj/v2" ) // Request represents a webhook request. diff --git a/vendor/github.com/clbanning/mxj/LICENSE b/vendor/github.com/clbanning/mxj/LICENSE deleted file mode 100644 index f27bccd..0000000 --- a/vendor/github.com/clbanning/mxj/LICENSE +++ /dev/null @@ -1,55 +0,0 @@ -Copyright (c) 2012-2016 Charles Banning . 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. diff --git a/vendor/github.com/clbanning/mxj/escapechars.go b/vendor/github.com/clbanning/mxj/escapechars.go deleted file mode 100644 index bee0442..0000000 --- a/vendor/github.com/clbanning/mxj/escapechars.go +++ /dev/null @@ -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 '&' will be re-escaped as '&amp;'. -// -/* - The values are: - " " - ' ' - < < - > > - & & -*/ -func XMLEscapeChars(b bool) { - xmlEscapeChars = b -} - -// Scan for '&' first, since 's' may contain "&" that is parsed to "&amp;" -// - or "<" that is parsed to "&lt;". -var escapechars = [][2][]byte{ - {[]byte(`&`), []byte(`&`)}, - {[]byte(`<`), []byte(`<`)}, - {[]byte(`>`), []byte(`>`)}, - {[]byte(`"`), []byte(`"`)}, - {[]byte(`'`), []byte(`'`)}, -} - -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) -} - diff --git a/vendor/github.com/clbanning/mxj/exists.go b/vendor/github.com/clbanning/mxj/exists.go deleted file mode 100644 index 2fb3084..0000000 --- a/vendor/github.com/clbanning/mxj/exists.go +++ /dev/null @@ -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 -} diff --git a/vendor/github.com/clbanning/mxj/v2/.travis.yml b/vendor/github.com/clbanning/mxj/v2/.travis.yml new file mode 100644 index 0000000..9c86115 --- /dev/null +++ b/vendor/github.com/clbanning/mxj/v2/.travis.yml @@ -0,0 +1,4 @@ +language: go + +go: +- 1.x \ No newline at end of file diff --git a/vendor/github.com/clbanning/mxj/v2/LICENSE b/vendor/github.com/clbanning/mxj/v2/LICENSE new file mode 100644 index 0000000..1ada880 --- /dev/null +++ b/vendor/github.com/clbanning/mxj/v2/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012-2021 Charles Banning . 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. + diff --git a/vendor/github.com/clbanning/mxj/anyxml.go b/vendor/github.com/clbanning/mxj/v2/anyxml.go similarity index 76% rename from vendor/github.com/clbanning/mxj/anyxml.go rename to vendor/github.com/clbanning/mxj/v2/anyxml.go index ec2f3df..63970ee 100644 --- a/vendor/github.com/clbanning/mxj/anyxml.go +++ b/vendor/github.com/clbanning/mxj/v2/anyxml.go @@ -1,6 +1,7 @@ package mxj import ( + "bytes" "encoding/xml" "reflect" ) @@ -50,6 +51,8 @@ const ( 3.14159265 true + +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 + "" - b = []byte(ss) + if _, err = s.WriteString(""); 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 + "" - b = []byte(ss) + if _, err = s.WriteString(``); 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 diff --git a/vendor/github.com/clbanning/mxj/atomFeedString.xml b/vendor/github.com/clbanning/mxj/v2/atomFeedString.xml similarity index 100% rename from vendor/github.com/clbanning/mxj/atomFeedString.xml rename to vendor/github.com/clbanning/mxj/v2/atomFeedString.xml diff --git a/vendor/github.com/clbanning/mxj/doc.go b/vendor/github.com/clbanning/mxj/v2/doc.go similarity index 89% rename from vendor/github.com/clbanning/mxj/doc.go rename to vendor/github.com/clbanning/mxj/v2/doc.go index 8ed79a5..07ac098 100644 --- a/vendor/github.com/clbanning/mxj/doc.go +++ b/vendor/github.com/clbanning/mxj/v2/doc.go @@ -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[:map["#text":]] rather than map[:]. 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. diff --git a/vendor/github.com/clbanning/mxj/v2/escapechars.go b/vendor/github.com/clbanning/mxj/v2/escapechars.go new file mode 100644 index 0000000..eeb3d25 --- /dev/null +++ b/vendor/github.com/clbanning/mxj/v2/escapechars.go @@ -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 '&' will be re-escaped as '&amp;'. +// +/* + The values are: + " " + ' ' + < < + > > + & & +*/ +// +// 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 "&" that is parsed to "&amp;" +// - or "<" that is parsed to "&lt;". +var escapechars = [][2][]byte{ + {[]byte(`&`), []byte(`&`)}, + {[]byte(`<`), []byte(`<`)}, + {[]byte(`>`), []byte(`>`)}, + {[]byte(`"`), []byte(`"`)}, + {[]byte(`'`), []byte(`'`)}, +} + +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 + } +} diff --git a/vendor/github.com/clbanning/mxj/v2/exists.go b/vendor/github.com/clbanning/mxj/v2/exists.go new file mode 100644 index 0000000..07aeda4 --- /dev/null +++ b/vendor/github.com/clbanning/mxj/v2/exists.go @@ -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 +} diff --git a/vendor/github.com/clbanning/mxj/files.go b/vendor/github.com/clbanning/mxj/v2/files.go similarity index 100% rename from vendor/github.com/clbanning/mxj/files.go rename to vendor/github.com/clbanning/mxj/v2/files.go diff --git a/vendor/github.com/clbanning/mxj/files_test.badjson b/vendor/github.com/clbanning/mxj/v2/files_test.badjson similarity index 100% rename from vendor/github.com/clbanning/mxj/files_test.badjson rename to vendor/github.com/clbanning/mxj/v2/files_test.badjson diff --git a/vendor/github.com/clbanning/mxj/files_test.badxml b/vendor/github.com/clbanning/mxj/v2/files_test.badxml similarity index 100% rename from vendor/github.com/clbanning/mxj/files_test.badxml rename to vendor/github.com/clbanning/mxj/v2/files_test.badxml diff --git a/vendor/github.com/clbanning/mxj/files_test.json b/vendor/github.com/clbanning/mxj/v2/files_test.json similarity index 100% rename from vendor/github.com/clbanning/mxj/files_test.json rename to vendor/github.com/clbanning/mxj/v2/files_test.json diff --git a/vendor/github.com/clbanning/mxj/files_test.xml b/vendor/github.com/clbanning/mxj/v2/files_test.xml similarity index 100% rename from vendor/github.com/clbanning/mxj/files_test.xml rename to vendor/github.com/clbanning/mxj/v2/files_test.xml diff --git a/vendor/github.com/clbanning/mxj/files_test_dup.json b/vendor/github.com/clbanning/mxj/v2/files_test_dup.json similarity index 100% rename from vendor/github.com/clbanning/mxj/files_test_dup.json rename to vendor/github.com/clbanning/mxj/v2/files_test_dup.json diff --git a/vendor/github.com/clbanning/mxj/files_test_dup.xml b/vendor/github.com/clbanning/mxj/v2/files_test_dup.xml similarity index 100% rename from vendor/github.com/clbanning/mxj/files_test_dup.xml rename to vendor/github.com/clbanning/mxj/v2/files_test_dup.xml diff --git a/vendor/github.com/clbanning/mxj/files_test_indent.json b/vendor/github.com/clbanning/mxj/v2/files_test_indent.json similarity index 100% rename from vendor/github.com/clbanning/mxj/files_test_indent.json rename to vendor/github.com/clbanning/mxj/v2/files_test_indent.json diff --git a/vendor/github.com/clbanning/mxj/files_test_indent.xml b/vendor/github.com/clbanning/mxj/v2/files_test_indent.xml similarity index 100% rename from vendor/github.com/clbanning/mxj/files_test_indent.xml rename to vendor/github.com/clbanning/mxj/v2/files_test_indent.xml diff --git a/vendor/github.com/clbanning/mxj/v2/go.mod b/vendor/github.com/clbanning/mxj/v2/go.mod new file mode 100644 index 0000000..7bd84fe --- /dev/null +++ b/vendor/github.com/clbanning/mxj/v2/go.mod @@ -0,0 +1,3 @@ +module github.com/clbanning/mxj/v2 + +go 1.15 diff --git a/vendor/github.com/clbanning/mxj/gob.go b/vendor/github.com/clbanning/mxj/v2/gob.go similarity index 100% rename from vendor/github.com/clbanning/mxj/gob.go rename to vendor/github.com/clbanning/mxj/v2/gob.go diff --git a/vendor/github.com/clbanning/mxj/json.go b/vendor/github.com/clbanning/mxj/v2/json.go similarity index 100% rename from vendor/github.com/clbanning/mxj/json.go rename to vendor/github.com/clbanning/mxj/v2/json.go diff --git a/vendor/github.com/clbanning/mxj/keyvalues.go b/vendor/github.com/clbanning/mxj/v2/keyvalues.go similarity index 93% rename from vendor/github.com/clbanning/mxj/keyvalues.go rename to vendor/github.com/clbanning/mxj/v2/keyvalues.go index 0b244c8..55620ca 100644 --- a/vendor/github.com/clbanning/mxj/keyvalues.go +++ b/vendor/github.com/clbanning/mxj/v2/keyvalues.go @@ -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) diff --git a/vendor/github.com/clbanning/mxj/leafnode.go b/vendor/github.com/clbanning/mxj/v2/leafnode.go similarity index 99% rename from vendor/github.com/clbanning/mxj/leafnode.go rename to vendor/github.com/clbanning/mxj/v2/leafnode.go index cf413eb..1bc814f 100644 --- a/vendor/github.com/clbanning/mxj/leafnode.go +++ b/vendor/github.com/clbanning/mxj/v2/leafnode.go @@ -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 += "." } diff --git a/vendor/github.com/clbanning/mxj/misc.go b/vendor/github.com/clbanning/mxj/v2/misc.go similarity index 100% rename from vendor/github.com/clbanning/mxj/misc.go rename to vendor/github.com/clbanning/mxj/v2/misc.go diff --git a/vendor/github.com/clbanning/mxj/mxj.go b/vendor/github.com/clbanning/mxj/v2/mxj.go similarity index 100% rename from vendor/github.com/clbanning/mxj/mxj.go rename to vendor/github.com/clbanning/mxj/v2/mxj.go diff --git a/vendor/github.com/clbanning/mxj/newmap.go b/vendor/github.com/clbanning/mxj/v2/newmap.go similarity index 100% rename from vendor/github.com/clbanning/mxj/newmap.go rename to vendor/github.com/clbanning/mxj/v2/newmap.go diff --git a/vendor/github.com/clbanning/mxj/readme.md b/vendor/github.com/clbanning/mxj/v2/readme.md similarity index 88% rename from vendor/github.com/clbanning/mxj/readme.md rename to vendor/github.com/clbanning/mxj/v2/readme.md index 6bb21dc..0e0a09a 100644 --- a/vendor/github.com/clbanning/mxj/readme.md +++ b/vendor/github.com/clbanning/mxj/v2/readme.md @@ -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. +

Installation

+Using go.mod: +
+go get github.com/clbanning/mxj/v2@v2.7	
+
+ +
+import "github.com/clbanning/mxj/v2"
+
+ +... or just vendor the package. +

Related Packages

https://github.com/clbanning/checkxml provides functions for validating XML data. +

Refactor Encoder - 2020.05.01

+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 +

Refactor Decoder - 2015.11.15

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

Notices

+ 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[:map["#text":]] rather than map[:] 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. diff --git a/vendor/github.com/clbanning/mxj/remove.go b/vendor/github.com/clbanning/mxj/v2/remove.go similarity index 100% rename from vendor/github.com/clbanning/mxj/remove.go rename to vendor/github.com/clbanning/mxj/v2/remove.go diff --git a/vendor/github.com/clbanning/mxj/rename.go b/vendor/github.com/clbanning/mxj/v2/rename.go similarity index 79% rename from vendor/github.com/clbanning/mxj/rename.go rename to vendor/github.com/clbanning/mxj/v2/rename.go index e95a963..4c655ed 100644 --- a/vendor/github.com/clbanning/mxj/rename.go +++ b/vendor/github.com/clbanning/mxj/v2/rename.go @@ -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) diff --git a/vendor/github.com/clbanning/mxj/set.go b/vendor/github.com/clbanning/mxj/v2/set.go similarity index 100% rename from vendor/github.com/clbanning/mxj/set.go rename to vendor/github.com/clbanning/mxj/v2/set.go diff --git a/vendor/github.com/clbanning/mxj/setfieldsep.go b/vendor/github.com/clbanning/mxj/v2/setfieldsep.go similarity index 100% rename from vendor/github.com/clbanning/mxj/setfieldsep.go rename to vendor/github.com/clbanning/mxj/v2/setfieldsep.go diff --git a/vendor/github.com/clbanning/mxj/songtext.xml b/vendor/github.com/clbanning/mxj/v2/songtext.xml similarity index 100% rename from vendor/github.com/clbanning/mxj/songtext.xml rename to vendor/github.com/clbanning/mxj/v2/songtext.xml diff --git a/vendor/github.com/clbanning/mxj/strict.go b/vendor/github.com/clbanning/mxj/v2/strict.go similarity index 100% rename from vendor/github.com/clbanning/mxj/strict.go rename to vendor/github.com/clbanning/mxj/v2/strict.go diff --git a/vendor/github.com/clbanning/mxj/struct.go b/vendor/github.com/clbanning/mxj/v2/struct.go similarity index 100% rename from vendor/github.com/clbanning/mxj/struct.go rename to vendor/github.com/clbanning/mxj/v2/struct.go diff --git a/vendor/github.com/clbanning/mxj/updatevalues.go b/vendor/github.com/clbanning/mxj/v2/updatevalues.go similarity index 94% rename from vendor/github.com/clbanning/mxj/updatevalues.go rename to vendor/github.com/clbanning/mxj/v2/updatevalues.go index 46779f4..9e10d84 100644 --- a/vendor/github.com/clbanning/mxj/updatevalues.go +++ b/vendor/github.com/clbanning/mxj/v2/updatevalues.go @@ -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. diff --git a/vendor/github.com/clbanning/mxj/xml.go b/vendor/github.com/clbanning/mxj/v2/xml.go similarity index 73% rename from vendor/github.com/clbanning/mxj/xml.go rename to vendor/github.com/clbanning/mxj/v2/xml.go index fac0f1d..b72a146 100644 --- a/vendor/github.com/clbanning/mxj/xml.go +++ b/vendor/github.com/clbanning/mxj/v2/xml.go @@ -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[] 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 += `" + if _, err = b.WriteString(`"); 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(`"); err != nil { + return err } - *s += `" } 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() } diff --git a/vendor/github.com/clbanning/mxj/xmlseq.go b/vendor/github.com/clbanning/mxj/v2/xmlseq.go similarity index 73% rename from vendor/github.com/clbanning/mxj/xmlseq.go rename to vendor/github.com/clbanning/mxj/v2/xmlseq.go index 6be73ae..9732dec 100644 --- a/vendor/github.com/clbanning/mxj/xmlseq.go +++ b/vendor/github.com/clbanning/mxj/v2/xmlseq.go @@ -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[]map[string]interface{}{"#text":"", "#seq":} +// 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"]. // 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":, "#seq":} // • 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 - "" - are decoded as map["#comment"]map["#text"]"cmnt_text" with a "#seq" k:v pair. // • directives - "" - are decoded as map["#directive"]map[#text"]"directive_text" with a "#seq" k:v pair. // • process instructions - "" - 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 : tag preserve the +// 1. Keys in the MapSeq value that are parsed from a : tag preserve the // ":" notation rather than stripping it as with NewMapXml(). // 2. Attribute keys for name space prefix declarations preserve "xmlns:" 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 - . // - The "#directive" map key identifies a directive in the value "#text" map entry - . @@ -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 "value". -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 += `` + if key == commentK { + *s += `` noEndTag = true break } - if key == "#directive" { - *s += `` + if key == directiveK { + *s += `` noEndTag = true break } - if key == "#procinst" { - *s += `` + if key == procinstK { + *s += `` 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) } diff --git a/vendor/github.com/clbanning/mxj/v2/xmlseq2.go b/vendor/github.com/clbanning/mxj/v2/xmlseq2.go new file mode 100644 index 0000000..467fd07 --- /dev/null +++ b/vendor/github.com/clbanning/mxj/v2/xmlseq2.go @@ -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...) +} + diff --git a/vendor/modules.txt b/vendor/modules.txt index 2b2d4df..b228120 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -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