diff --git a/vendor/github.com/clbanning/mxj/.version b/vendor/github.com/clbanning/mxj/.version index 713026061d..5d50668520 100644 --- a/vendor/github.com/clbanning/mxj/.version +++ b/vendor/github.com/clbanning/mxj/.version @@ -1,2 +1,2 @@ https://github.com/clbanning/mxj -87ef56c6f157ac80caf824a9779dddc6f69c6580 +49e0b4242cb3c9d0fcd8bef7b8d9ecfe13fadba7 diff --git a/vendor/github.com/clbanning/mxj/LICENSE b/vendor/github.com/clbanning/mxj/LICENSE index ccfc84e595..f27bccdf06 100644 --- a/vendor/github.com/clbanning/mxj/LICENSE +++ b/vendor/github.com/clbanning/mxj/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012-2015 Charles Banning . All rights reserved. +Copyright (c) 2012-2016 Charles Banning . All rights reserved. The MIT License (MIT) diff --git a/vendor/github.com/clbanning/mxj/atomFeedString.xml b/vendor/github.com/clbanning/mxj/atomFeedString.xml new file mode 100644 index 0000000000..474575a41c --- /dev/null +++ b/vendor/github.com/clbanning/mxj/atomFeedString.xml @@ -0,0 +1,54 @@ + +Code Review - My issueshttp://codereview.appspot.com/rietveld<>rietveld: an attempt at pubsubhubbub +2009-10-04T01:35:58+00:00email-address-removedurn:md5:134d9179c41f806be79b3a5f7877d19a + An attempt at adding pubsubhubbub support to Rietveld. +http://code.google.com/p/pubsubhubbub +http://code.google.com/p/rietveld/issues/detail?id=155 + +The server side of the protocol is trivial: + 1. add a &lt;link rel=&quot;hub&quot; href=&quot;hub-server&quot;&gt; tag to all + feeds that will be pubsubhubbubbed. + 2. every time one of those feeds changes, tell the hub + with a simple POST request. + +I have tested this by adding debug prints to a local hub +server and checking that the server got the right publish +requests. + +I can&#39;t quite get the server to work, but I think the bug +is not in my code. I think that the server expects to be +able to grab the feed and see the feed&#39;s actual URL in +the link rel=&quot;self&quot;, but the default value for that drops +the :port from the URL, and I cannot for the life of me +figure out how to get the Atom generator deep inside +django not to do that, or even where it is doing that, +or even what code is running to generate the Atom feed. +(I thought I knew but I added some assert False statements +and it kept running!) + +Ignoring that particular problem, I would appreciate +feedback on the right way to get the two values at +the top of feeds.py marked NOTE(rsc). + + +rietveld: correct tab handling +2009-10-03T23:02:17+00:00email-address-removedurn:md5:0a2a4f19bb815101f0ba2904aed7c35a + This fixes the buggy tab rendering that can be seen at +http://codereview.appspot.com/116075/diff/1/2 + +The fundamental problem was that the tab code was +not being told what column the text began in, so it +didn&#39;t know where to put the tab stops. Another problem +was that some of the code assumed that string byte +offsets were the same as column offsets, which is only +true if there are no tabs. + +In the process of fixing this, I cleaned up the arguments +to Fold and ExpandTabs and renamed them Break and +_ExpandTabs so that I could be sure that I found all the +call sites. I also wanted to verify that ExpandTabs was +not being used from outside intra_region_diff.py. + + + ` + diff --git a/vendor/github.com/clbanning/mxj/badxml_test.go b/vendor/github.com/clbanning/mxj/badxml_test.go new file mode 100644 index 0000000000..8cce47569a --- /dev/null +++ b/vendor/github.com/clbanning/mxj/badxml_test.go @@ -0,0 +1,68 @@ +// trying to recreate a panic + +package mxj + +import ( + "bytes" + "fmt" + "testing" +) + +var baddata = []byte(` + something strange + + + + + http://www.something.com + Some description goes here. + + +`) + +func TestBadXml(t *testing.T) { + fmt.Println("\n---------------- badxml_test.go\n") + fmt.Println("TestBadXml ...") + m, err := NewMapXml(baddata) + if err != nil { + t.Fatalf("err: didn't find xml.StartElement") + } + fmt.Printf("m: %v\n", m) + j, _ := m.Xml() + fmt.Println("m:", string(j)) +} + +func TestBadXmlSeq(t *testing.T) { + fmt.Println("TestBadXmlSeq ...") + m, err := NewMapXmlSeq(baddata) + if err != nil { + t.Fatalf("err: didn't find xmlStartElement") + } + fmt.Printf("m: %v\n", m) + j, _ := m.XmlSeq() + fmt.Println("m:", string(j)) +} + +func TestBadXmlReader(t *testing.T) { + fmt.Println("TestBadXmlReader ...") + r := bytes.NewReader(baddata) + m, err := NewMapXmlReader(r) + if err != nil { + t.Fatalf("err: didn't find xml.StartElement") + } + fmt.Printf("m: %v\n", m) + j, _ := m.Xml() + fmt.Println("m:", string(j)) +} + +func TestBadXmlSeqReader(t *testing.T) { + fmt.Println("TestBadXmlSeqReader ...") + r := bytes.NewReader(baddata) + m, err := NewMapXmlSeqReader(r) + if err != nil { + t.Fatalf("err: didn't find xmlStartElement") + } + fmt.Printf("m: %v\n", m) + j, _ := m.XmlSeq() + fmt.Println("m:", string(j)) +} diff --git a/vendor/github.com/clbanning/mxj/bom_test.go b/vendor/github.com/clbanning/mxj/bom_test.go new file mode 100644 index 0000000000..a7ab2f5dcb --- /dev/null +++ b/vendor/github.com/clbanning/mxj/bom_test.go @@ -0,0 +1,88 @@ +// bomxml.go - test handling Byte-Order-Mark headers + +package mxj + +import ( + "bytes" + "fmt" + "io" + "testing" +) + +// Check for Byte-Order-Mark header. +var boms = [][]byte{ + {'\xef', '\xbb', '\xbf'}, + {'\xfe', '\xff'}, + {'\xff', '\xfe'}, + {'\x00', '\x00', '\xfe', '\xff'}, + {'\xff', '\xfe', '\x00', '\x00'}, +} + +func TestBom(t *testing.T) { + fmt.Println("\n--------------- bom_test.go \n") + fmt.Println("TestBom ...") + + // use just UTF-8 BOM ... no alternative CharSetReader + if _, err := NewMapXml(boms[0]); err != io.EOF { + t.Fatalf("NewMapXml err; %v\n", err) + } + + if _, err := NewMapXmlSeq(boms[0]); err != io.EOF { + t.Fatalf("NewMapXmlSeq err: %v\n", err) + } +} + +var bomdata = append(boms[0], []byte(` + + + + http://www.something.com + Some description goes here. + +`)...) + +func TestBomData(t *testing.T) { + fmt.Println("TestBomData ...") + m, err := NewMapXml(bomdata) + if err != nil { + t.Fatalf("err: didn't find xml.StartElement") + } + fmt.Printf("m: %v\n", m) + j, _ := m.Xml() + fmt.Println("m:", string(j)) +} + +func TestBomDataSeq(t *testing.T) { + fmt.Println("TestBomDataSeq ...") + m, err := NewMapXmlSeq(bomdata) + if err != nil { + t.Fatalf("err: didn't find xml.StartElement") + } + fmt.Printf("m: %v\n", m) + j, _ := m.XmlSeq() + fmt.Println("m:", string(j)) +} + +func TestBomDataReader(t *testing.T) { + fmt.Println("TestBomDataReader ...") + r := bytes.NewReader(bomdata) + m, err := NewMapXmlReader(r) + if err != nil { + t.Fatalf("err: didn't find xml.StartElement") + } + fmt.Printf("m: %v\n", m) + j, _ := m.Xml() + fmt.Println("m:", string(j)) +} + +func TestBomDataSeqReader(t *testing.T) { + fmt.Println("TestBomDataSeqReader ...") + r := bytes.NewReader(bomdata) + m, err := NewMapXmlSeqReader(r) + if err != nil { + t.Fatalf("err: didn't find xml.StartElement") + } + fmt.Printf("m: %v\n", m) + j, _ := m.XmlSeq() + fmt.Println("m:", string(j)) +} diff --git a/vendor/github.com/clbanning/mxj/doc.go b/vendor/github.com/clbanning/mxj/doc.go index 4db312c6ec..ec6673f89f 100644 --- a/vendor/github.com/clbanning/mxj/doc.go +++ b/vendor/github.com/clbanning/mxj/doc.go @@ -1,5 +1,5 @@ // mxj - A collection of map[string]interface{} and associated XML and JSON utilities. -// Copyright 2012-2014 Charles Banning. All rights reserved. +// Copyright 2012-2015 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 @@ -11,6 +11,7 @@ mxj supplants the legacy x2j and j2x packages. If you want the old syntax, use m Note: this library was designed for processing ad hoc anonymous messages. Bulk processing large data sets may be much more efficiently performed using the encoding/xml or encoding/json packages from Go's standard library directly. Note: + 2015-12-02: NewMapXmlSeq() with mv.XmlSeq() & co. will try to preserve structure of XML doc when re-encoding. 2014-08-02: AnyXml() and AnyXmlIndent() will try to marshal arbitrary values to XML. SUMMARY @@ -63,22 +64,47 @@ SUMMARY A new Map with whatever keys are desired can be created from the current Map and then encoded in XML or JSON. (Note: keys can use dot-notation. 'oldKey' can also use wildcards and indexed arrays.) - newMap := m.NewMap("oldKey_1:newKey_1", "oldKey_2:newKey_2", ..., "oldKey_N:newKey_N") - newXml := newMap.Xml() // for example - newJson := newMap.Json() // ditto + newMap, err := m.NewMap("oldKey_1:newKey_1", "oldKey_2:newKey_2", ..., "oldKey_N:newKey_N") + newXml, err := newMap.Xml() // for example + newJson, err := newMap.Json() // ditto XML PARSING CONVENTIONS - - Attributes are parsed to map[string]interface{} values by prefixing a hyphen, '-', - to the attribute label. (PrependAttrWithHyphen(false) will override this.) + Using NewXml() + + - Attributes are parsed to `map[string]interface{}` values by prefixing a hyphen, `-`, + to the attribute label. (Unless overridden by `PrependAttrWithHyphen(false)`.) - If the element is a simple element and has attributes, the element value - is given the key '#text' for its map[string]interface{} representation. + is given the key `#text` for its `map[string]interface{}` representation. (See + the 'atomFeedString.xml' test data, below.) + - XML comments, directives, and process instructions are ignored. + - If CoerceKeysToLower() has been called, then the resultant keys will be lower case. + + Using NewXmlSeq() + + - Attributes are parsed to `map["#attr"]map[]map[string]interface{}`values + where the `` value has "#text" and "#seq" keys - the "#text" key holds the + value for ``. + - All elements, except for the root, have a "#seq" key. + - Comments, directives, and process instructions are unmarshalled into the Map using the + keys "#comment", "#directive", and "#procinst", respectively. (See documentation for more + specifics.) + + Both + + - By default, "Nan", "Inf", and "-Inf" values are not cast to float64. If you want them + to be cast, set a flag to cast them using CastNanInf(true). XML ENCODING CONVENTIONS - + - 'nil' Map values, which may represent 'null' JSON values, are encoded as "". - NOTE: the operation is not symmetric as "" elements are decoded as 'tag:""' Map values, - which, then, encode in JSON as '"tag":""' values.. + NOTE: the operation is not symmetric as "" elements are decoded as 'tag:""' Map values, + which, then, encode in JSON as '"tag":""' values.. + - ALSO: there is no guarantee that the encoded XML doc will be the same as the decoded one. (Go + randomizes the walk through map[string]interface{} values.) If you plan to re-encode the + Map value to XML and want the same sequencing of elements look at NewMapXmlSeq() and + m.XmlSeq() - these try to preserve the element sequencing but with added complexity when + working with the Map representation. */ package mxj diff --git a/vendor/github.com/clbanning/mxj/example_test.go b/vendor/github.com/clbanning/mxj/example_test.go index 81fb28612f..74b5a1ebd6 100644 --- a/vendor/github.com/clbanning/mxj/example_test.go +++ b/vendor/github.com/clbanning/mxj/example_test.go @@ -1,3 +1,5 @@ +// +test OMIT + // note - "// Output:" is a key for "go test" to match function ouput with the lines that follow. // It is also use by "godoc" to build the Output block of the function / method documentation. // To skip processing Example* functions, use: go test -run "Test*" diff --git a/vendor/github.com/clbanning/mxj/examples/getmetrics1.go b/vendor/github.com/clbanning/mxj/examples/getmetrics1.go index c4e680382c..b2b7a4a673 100644 --- a/vendor/github.com/clbanning/mxj/examples/getmetrics1.go +++ b/vendor/github.com/clbanning/mxj/examples/getmetrics1.go @@ -41,6 +41,7 @@ import ( "github.com/clbanning/mxj" "log" "os" + "sort" "time" ) @@ -123,14 +124,14 @@ func main() { // no guarantee that range on map will follow any sequence lv := len(valueEntry) - type ev [2]string - list := make([]ev, lv) + list := make([][2]string, lv) var i int for k, v := range valueEntry { list[i][0] = k list[i][1] = v.(string) i++ } + sort.Sort(mylist(list)) // extract keys as column header on first pass if !gotKeys { @@ -174,3 +175,20 @@ func main() { mf.Close() } } + +type mylist [][2]string + +func (m mylist) Len() int { + return len(m) +} + +func (m mylist) Less(i, j int) bool { + if m[i][0] > m[j][0] { + return false + } + return true +} + +func (m mylist) Swap(i, j int) { + m[i], m[j] = m[j], m[i] +} diff --git a/vendor/github.com/clbanning/mxj/examples/getmetrics2.go b/vendor/github.com/clbanning/mxj/examples/getmetrics2.go index 65d6c9513f..2840dba46c 100644 --- a/vendor/github.com/clbanning/mxj/examples/getmetrics2.go +++ b/vendor/github.com/clbanning/mxj/examples/getmetrics2.go @@ -42,6 +42,7 @@ import ( "github.com/clbanning/mxj" "log" "os" + "sort" "time" ) @@ -120,14 +121,14 @@ func main() { // no guarantee that range on map will follow any sequence lv := len(valueEntry) - type ev [2]string - list := make([]ev, lv) + list := make([][2]string, lv) var i int for k, v := range valueEntry { list[i][0] = k list[i][1] = v.(string) i++ } + sort.Sort(mylist(list)) // extract keys as column header on first pass if !gotKeys { @@ -171,3 +172,20 @@ func main() { mf.Close() } } + +type mylist [][2]string + +func (m mylist) Len() int { + return len(m) +} + +func (m mylist) Less(i, j int) bool { + if m[i][0] > m[j][0] { + return false + } + return true +} + +func (m mylist) Swap(i, j int) { + m[i], m[j] = m[j], m[i] +} diff --git a/vendor/github.com/clbanning/mxj/examples/getmetrics3.go b/vendor/github.com/clbanning/mxj/examples/getmetrics3.go index a914d5491b..31da6e8b02 100644 --- a/vendor/github.com/clbanning/mxj/examples/getmetrics3.go +++ b/vendor/github.com/clbanning/mxj/examples/getmetrics3.go @@ -43,6 +43,7 @@ import ( "github.com/clbanning/mxj" "log" "os" + "sort" "time" ) @@ -85,7 +86,7 @@ func main() { log.Fatal("merr:", merr.Error()) } fmt.Println(time.Now().String(), "... XML Unmarshaled - len:", len(m)) - fmt.Println("raw XML buffer size (should be same as File size):", len(*raw)) + fmt.Println("raw XML buffer size (should be same as File size):", len(raw)) // Get just the key values of interest. // Could also use m.ValuesForKey("Metric"), @@ -127,14 +128,14 @@ func main() { // no guarantee that range on map will follow any sequence lv := len(valueEntry) - type ev [2]string - list := make([]ev, lv) + list := make([][2]string, lv) var i int for k, v := range valueEntry { list[i][0] = k list[i][1] = v.(string) i++ } + sort.Sort(mylist(list)) // extract keys as column header on first pass if !gotKeys { @@ -178,3 +179,20 @@ func main() { mf.Close() } } + +type mylist [][2]string + +func (m mylist) Len() int { + return len(m) +} + +func (m mylist) Less(i, j int) bool { + if m[i][0] > m[j][0] { + return false + } + return true +} + +func (m mylist) Swap(i, j int) { + m[i], m[j] = m[j], m[i] +} diff --git a/vendor/github.com/clbanning/mxj/examples/getmetrics4.go b/vendor/github.com/clbanning/mxj/examples/getmetrics4.go index 2cf663817b..a6f2ed9287 100644 --- a/vendor/github.com/clbanning/mxj/examples/getmetrics4.go +++ b/vendor/github.com/clbanning/mxj/examples/getmetrics4.go @@ -44,6 +44,7 @@ import ( "github.com/clbanning/mxj" "log" "os" + "sort" "time" ) @@ -84,7 +85,7 @@ func main() { log.Fatal("merr:", merr.Error()) } fmt.Println(time.Now().String(), "... XML Unmarshaled - len:", len(m)) - fmt.Println("raw XML buffer size (should be same as File size):", len(*raw)) + fmt.Println("raw XML buffer size (should be same as File size):", len(raw)) // Get just the key values of interest. // Could also use m.ValuesForKey("Metric"), @@ -126,14 +127,14 @@ func main() { // no guarantee that range on map will follow any sequence lv := len(valueEntry) - type ev [2]string - list := make([]ev, lv) + list := make([][2]string, lv) var i int for k, v := range valueEntry { list[i][0] = k list[i][1] = v.(string) i++ } + sort.Sort(mylist(list)) // extract keys as column header on first pass if !gotKeys { @@ -177,3 +178,20 @@ func main() { mf.Close() } } + +type mylist [][2]string + +func (m mylist) Len() int { + return len(m) +} + +func (m mylist) Less(i, j int) bool { + if m[i][0] > m[j][0] { + return false + } + return true +} + +func (m mylist) Swap(i, j int) { + m[i], m[j] = m[j], m[i] +} diff --git a/vendor/github.com/clbanning/mxj/examples/gonuts1.go b/vendor/github.com/clbanning/mxj/examples/gonuts1.go index a0c5d6fce2..a6cf7ae3bf 100644 --- a/vendor/github.com/clbanning/mxj/examples/gonuts1.go +++ b/vendor/github.com/clbanning/mxj/examples/gonuts1.go @@ -55,7 +55,7 @@ func main() { } else if merr == io.EOF { break } - fmt.Println("\nMessage to parse:", string(*raw)) + fmt.Println("\nMessage to parse:", string(raw)) fmt.Println("Map value for XML message:", m.StringIndent()) // get the values for "netid" or "idnet" key using path == "data.*" diff --git a/vendor/github.com/clbanning/mxj/examples/gonuts10.go b/vendor/github.com/clbanning/mxj/examples/gonuts10.go new file mode 100644 index 0000000000..a8070a3fbe --- /dev/null +++ b/vendor/github.com/clbanning/mxj/examples/gonuts10.go @@ -0,0 +1,108 @@ +/* gonuts10.go - https://groups.google.com/forum/?fromgroups#!topic/golang-nuts/tf4aDQ1Hn_c +change: + + Sam + Kevin + Smith + + + +to: + + Sam + Kevin + Smith + Kevin Smith + + +NOTE: sequence of elements NOT guaranteed due to use of map[string]interface{}. + +Here we build the "full-name" element value from other values in the doc by selecting the +"first-name" value with the latest dates. +*/ + +package main + +import ( + "fmt" + "github.com/clbanning/mxj" +) + +var data = []byte(` + + Sam + Kevin + Smith + + +`) + +func main() { + m, err := mxj.NewMapXml(data) + if err != nil { + fmt.Println("NewMapXml err:", err) + return + } + // vals, err := m.ValuesForPath("author.first-name") // full-path option + vals, err := m.ValuesForKey("first-name") // key-only alternatively + if err != nil { + fmt.Println("ValuesForPath err:", err) + return + } else if len(vals) == 0 { + fmt.Println("no first-name vals") + return + } + var fname, date string + for _, v := range vals { + vm, ok := v.(map[string]interface{}) + if !ok { + fmt.Println("assertion failed") + return + } + fn, ok := vm["#text"].(string) + if !ok { + fmt.Println("no #text tag") + return + } + dt, ok := vm["-effect_range"].(string) + if !ok { + fmt.Println("no -effect_range attr") + return + } + if dt > date { + date = dt + fname = fn + } + } + /* + // alternatively: + //(however, this requires knowing what latest "effect_range" attribute value is) + vals, err := m.ValuesForKey("first-name", "-effect_range:2012-") + if len(vals) == 0 { + fmt.Println("no #text vals") + return + } + fname := vals[0].(map[string]interface{})["#text"].(string) + */ + + // vals, err = m.ValuesForKey("last-name") // key-only option + vals, err = m.ValuesForPath("author.last-name") // full-path option + if err != nil { + fmt.Println("ValuesForPath err:", err) + return + } else if len(vals) == 0 { + fmt.Println("no last-name vals") + return + } + lname := vals[0].(string) + if err = m.SetValueForPath(fname+" "+lname, "author.full-name"); err != nil { + fmt.Println("SetValueForPath err:", err) + return + } + b, err := m.XmlIndent("", " ") + if err != nil { + fmt.Println("XmlIndent err:", err) + return + } + fmt.Println(string(b)) +} diff --git a/vendor/github.com/clbanning/mxj/examples/gonuts10seq.go b/vendor/github.com/clbanning/mxj/examples/gonuts10seq.go new file mode 100644 index 0000000000..d7f844980f --- /dev/null +++ b/vendor/github.com/clbanning/mxj/examples/gonuts10seq.go @@ -0,0 +1,106 @@ +/* gonuts10.go - https://groups.google.com/forum/?fromgroups#!topic/golang-nuts/tf4aDQ1Hn_c +change: + + Sam + Kevin + Smith + + + +to: + + Sam + Kevin + Smith + Kevin Smith + + +NOTE: use NewMapXmlSeq() and mv.XmlSeqIndent() to preserve structure. + +Here we build the "full-name" element value from other values in the doc by selecting the +"first-name" value with the latest dates. +*/ + +package main + +import ( + "fmt" + "github.com/clbanning/mxj" + "strings" +) + +var data = []byte(` + + Sam + Kevin + Smith + + +`) + +func main() { + fmt.Println(string(data)) + m, err := mxj.NewMapXmlSeq(data) + if err != nil { + fmt.Println("NewMapXml err:", err) + return + } + vals, err := m.ValuesForPath("author.first-name") // full-path option + if err != nil { + fmt.Println("ValuesForPath err:", err) + return + } else if len(vals) == 0 { + fmt.Println("no first-name vals") + return + } + var fname, date string + var index int + for _, v := range vals { + vm, ok := v.(map[string]interface{}) + if !ok { + fmt.Println("assertion failed") + return + } + fn, ok := vm["#text"].(string) + if !ok { + fmt.Println("no #text tag") + return + } + // extract the associated date + dt, _ := mxj.Map(vm).ValueForPathString("#attr.effect_range.#text") + if dt == "" { + fmt.Println("no effect_range attr") + return + } + dts := strings.Split(dt, "-") + if len(dts) > 1 && dts[len(dts)-1] == "" { + index = len(dts) - 2 + } else if len(dts) > 0 { + index = len(dts) - 1 + } + if len(dts) > 0 && dts[index] > date { + date = dts[index] + fname = fn + } + } + + vals, err = m.ValuesForPath("author.last-name.#text") // full-path option + if err != nil { + fmt.Println("ValuesForPath err:", err) + return + } else if len(vals) == 0 { + fmt.Println("no last-name vals") + return + } + lname := vals[0].(string) + if err = m.SetValueForPath(fname+" "+lname, "author.full-name.#text"); err != nil { + fmt.Println("SetValueForPath err:", err) + return + } + b, err := m.XmlSeqIndent("", " ") + if err != nil { + fmt.Println("XmlIndent err:", err) + return + } + fmt.Println(string(b)) +} diff --git a/vendor/github.com/clbanning/mxj/examples/gonuts11seq.go b/vendor/github.com/clbanning/mxj/examples/gonuts11seq.go new file mode 100644 index 0000000000..2b85f58f10 --- /dev/null +++ b/vendor/github.com/clbanning/mxj/examples/gonuts11seq.go @@ -0,0 +1,510 @@ +/* gonuts10seqB.go - https://groups.google.com/forum/?fromgroups#!topic/golang-nuts/tf4aDQ1Hn_c + +Objective: assign Comment.CommentText attribute value to Request.ReportingName attribute that immediately follows Comment. + +NOTE: use NewMapXmlSeq() and mv.XmlSeqIndent() to preserve structure. + +See data value at EOF - from: https://gist.github.com/suntong/e4dcdc6c85dcf769eec4 +*/ + +package main + +import ( + "bytes" + "fmt" + "github.com/clbanning/mxj" + "io" +) + +func main() { + // fmt.Println(string(data)) + rdr := bytes.NewReader(data) + var m mxj.Map + var err error + // We read processing docs sequentially. + // Un-rooted ProcInst or Comments are processed AND just reencoded. (XmlSeqIndent() knows how, now.) + for m, err = mxj.NewMapXmlSeqReader(rdr); m != nil || err != io.EOF; m, err = mxj.NewMapXmlSeqReader(rdr) { + if err != nil { + if err != mxj.NO_ROOT { + fmt.Println("NewMapXmlSeq err:", err) + fmt.Println("m:", m) + } else if m != nil { + x, _ := m.XmlSeqIndent("", " ") + fmt.Println(string(x)) + } + continue + } + // fmt.Println(m.StringIndent()) + + // We have Two different arrays of Items in the XML doc, one nested in the other. + if err = copyCmts(m, "WebTest.Items"); err != nil { + fmt.Println("err:", err) + } + if err = copyCmts(m, "WebTest.Items.TransactionTimer.Items"); err != nil { + fmt.Println("err:", err) + } + // re-encode the map with the Items.Comment[#seq==n].#attr.CommentText + // values copied to the Items.Request[#seq==n+1].#attr.ReportingName elements. + b, err := m.XmlSeqIndent("", " ") + if err != nil { + fmt.Println("XmlIndent err:", err) + return + } + fmt.Println(string(b)) + } +} + +// Uncomment the print statements to details of the process here. +func copyCmts(m mxj.Map, path string) error { + // get the array of Items entries for the 'path' + vals, err := m.ValuesForPath(path) + if err != nil { + return fmt.Errorf("ValuesForPath err: %s", err.Error()) + } else if len(vals) == 0 { + return fmt.Errorf("no vals for path: %s", path) + } + // process each Items entry + for _, v := range vals { + vm, ok := v.(map[string]interface{}) + if !ok { + return fmt.Errorf("assertion failed") + } + // get the Comment list + c, ok := vm["Comment"] + if !ok { // --> no Items.Comment elements + continue + } + // Don't assume that Comment is an array. + // There may be just one value, in which case it will decode as map[string]interface{}. + switch c.(type) { + case map[string]interface{}: + c = []interface{}{c} + } + cmt := c.([]interface{}) + // get the Request list + r, ok := vm["Request"] + if !ok { // --> no Items.Request elements + continue + } + // Don't assume the Request is an array. + // There may be just one value, in which case it will decode as map[string]interface{}. + switch r.(type) { + case map[string]interface{}: + r = []interface{}{r} + } + req := r.([]interface{}) + + // fmt.Println("Comment:", cmt) + // fmt.Println("Request:", req) + + // Comment elements with #seq==n are followed by Request element with #seq==n+1. + // For each Comment.#seq==n extract the CommentText attribute value and use it to + // set the ReportingName attribute value in Request.#seq==n+1. + for _, v := range cmt { + vmap := v.(map[string]interface{}) + seq := vmap["#seq"].(int) // type is int + // extract CommentText attr from "#attr" + acmt, _ := mxj.Map(vmap).ValueForPath("#attr.CommentText.#text") + if acmt == "" { + fmt.Println("no CommentText value in Comment attributes") + } + // fmt.Println(seq, acmt) + // find the request with the #seq==seq+1 value + var r map[string]interface{} + for _, vv := range req { + rt := vv.(map[string]interface{}) + if rt["#seq"].(int) == seq+1 { + r = rt + break + } + } + if r == nil { // no Request with #seq==seq+1 + continue + } + if err := mxj.Map(r).SetValueForPath(acmt, "#attr.ReportingName.#text"); err != nil { + fmt.Println("SetValueForPath err:", err) + break + } + } + } + return nil +} + +var data = []byte(` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`) diff --git a/vendor/github.com/clbanning/mxj/examples/gonuts12seq.go b/vendor/github.com/clbanning/mxj/examples/gonuts12seq.go new file mode 100644 index 0000000000..ed874c26ec --- /dev/null +++ b/vendor/github.com/clbanning/mxj/examples/gonuts12seq.go @@ -0,0 +1,537 @@ +/* gonuts10seqB.go - https://groups.google.com/forum/?fromgroups#!topic/golang-nuts/tf4aDQ1Hn_c + +Objective: to quote from email + +================================ BEGIN QUOTE +I'm actually dealing with Microsoft webtest files. An example can be find at, + +https://gist.github.com/suntong/e4dcdc6c85dcf769eec4 + +It is the same as our case -- we have comments before each " + +Under it, the requests and comments are grouped into this transaction. + + +Now the challenge is, for *each* request with a comment immediately before it, change it attribute "ReportingName="""''s value with the content from its leading comments, and from the content of the grouping transaction as well. Let's say, first 10 chars or first three words or each. So for the first Request under "", which is " no Items.Comment elements + continue + } + // Don't assume that Comment is an array. + // There may be just one value, in which case it will decode as map[string]interface{}. + switch c.(type) { + case map[string]interface{}: + c = []interface{}{c} + } + cmt := c.([]interface{}) + // get the Request list + r, ok := vm["Request"] + if !ok { // --> no Items.Request elements + continue + } + // Don't assume the Request is an array. + // There may be just one value, in which case it will decode as map[string]interface{}. + switch r.(type) { + case map[string]interface{}: + r = []interface{}{r} + } + req := r.([]interface{}) + + // fmt.Println("Comment:", cmt) + // fmt.Println("Request:", req) + + // Comment elements with #seq==n are followed by Request element with #seq==n+1. + // For each Comment.#seq==n extract the CommentText attribute value and use it to + // set the ReportingName attribute value in Request.#seq==n+1. + for _, v := range cmt { + vmap := v.(map[string]interface{}) + seq := vmap["#seq"].(int) // type is int + // extract CommentText attr from array of "#attr" + acmt, _ := mxj.Map(vmap).ValueForPathString("#attr.CommentText.#text") + if acmt == "" { + fmt.Println("no CommentText value in Comment attributes") + } + // fmt.Println(seq, acmt) + // find the request with the #seq==seq+1 value + var r map[string]interface{} + for _, vv := range req { + rt := vv.(map[string]interface{}) + if rt["#seq"].(int) == seq+1 { + r = rt + break + } + } + if r == nil { // no Request with #seq==seq+1 + continue + } + if err := mxj.Map(r).SetValueForPath(tname+", "+acmt, "#attr.ReportingName.#text"); err != nil { + fmt.Println("SetValueForPath err:", err) + break + } + } + } + + // re-encode the map with the TransactionTimer.#attr.Name & Items.Comment[#seq==n].#attr.CommentText + // values copied to the Items.Request[#seq==n+1].#attr.ReportingName elements. + b, err := m.XmlSeqIndent("", " ") + if err != nil { + fmt.Println("XmlIndent err:", err) + return + } + fmt.Println(string(b)) + } +} + +var data = []byte(` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`) diff --git a/vendor/github.com/clbanning/mxj/examples/gonuts2.go b/vendor/github.com/clbanning/mxj/examples/gonuts2.go index 6bd0bab732..606f7ba377 100644 --- a/vendor/github.com/clbanning/mxj/examples/gonuts2.go +++ b/vendor/github.com/clbanning/mxj/examples/gonuts2.go @@ -5,7 +5,7 @@ // ... list of ClaimStatusCodeRecord ... // ... one instance of ClaimStatusCodeRecord ... // ... empty element ... -// ValueForPath +// ValuesForPath package main @@ -79,7 +79,7 @@ func fullPath(xmldata [][]byte) { // get the value for the key path of interest path := "Envelope.Body.GetClaimStatusCodesResponse.GetClaimStatusCodesResult.ClaimStatusCodeRecord" - values, err := m.ValueForPath(path) + values, err := m.ValuesForPath(path) if err != nil { fmt.Println("err:", err.Error()) return @@ -112,7 +112,7 @@ func partPath1(msg []byte) { m, _ := mxj.NewMapXml(msg) fmt.Println("m:", m.StringIndent()) path := "Envelope.Body.*.*.ClaimStatusCodeRecord" - values, err := m.ValueForPath(path) + values, err := m.ValuesForPath(path) if err != nil { fmt.Println("err:", err.Error()) return @@ -134,7 +134,7 @@ func partPath2(msg []byte) { m, _ := mxj.NewMapXml(msg) fmt.Println("m:", m.StringIndent()) path := "Envelope.Body.*.*.*" - values, err := m.ValueForPath(path) + values, err := m.ValuesForPath(path) if err != nil { fmt.Println("err:", err.Error()) return @@ -156,7 +156,7 @@ func partPath3(msg []byte) { m, _ := mxj.NewMapXml(msg) fmt.Println("m:", m.StringIndent()) path := "*.*.*.*.*" - values, err := m.ValueForPath(path) + values, err := m.ValuesForPath(path) if err != nil { fmt.Println("err:", err.Error()) return @@ -178,7 +178,7 @@ func partPath4(msg []byte) { m, _ := mxj.NewMapXml(msg) fmt.Println("m:", m.StringIndent()) path := "*.*.*.*.*.Description" - values, err := m.ValueForPath(path) + values, err := m.ValuesForPath(path) if err != nil { fmt.Println("err:", err.Error()) return @@ -200,7 +200,7 @@ func partPath5(msg []byte) { m, _ := mxj.NewMapXml(msg) fmt.Println("m:", m.StringIndent()) path := "*.*.*.*.*.*" - values, err := m.ValueForPath(path) + values, err := m.ValuesForPath(path) if err != nil { fmt.Println("err:", err.Error()) return @@ -222,7 +222,7 @@ func partPath6(msg []byte) { m, _ := mxj.NewMapXml(msg) fmt.Println("m:", m.StringIndent()) path := "*.*.*.*.*.*.*" - values, err := m.ValueForPath(path) + values, err := m.ValuesForPath(path) if err != nil { fmt.Println("err:", err.Error()) return diff --git a/vendor/github.com/clbanning/mxj/examples/gonuts9.go b/vendor/github.com/clbanning/mxj/examples/gonuts9.go index 7e4855b51d..46aa6816c4 100644 --- a/vendor/github.com/clbanning/mxj/examples/gonuts9.go +++ b/vendor/github.com/clbanning/mxj/examples/gonuts9.go @@ -4,8 +4,7 @@ package main import ( "fmt" - // "github.com/clbanning/mxj" - "tamgroup/mxj" + "github.com/clbanning/mxj" ) var data = []byte(` diff --git a/vendor/github.com/clbanning/mxj/keystolower_test.go b/vendor/github.com/clbanning/mxj/keystolower_test.go new file mode 100644 index 0000000000..2884c3ad5e --- /dev/null +++ b/vendor/github.com/clbanning/mxj/keystolower_test.go @@ -0,0 +1,59 @@ +// keystolower_test.go + +package mxj + +import ( + "fmt" + "testing" +) + +var tolowerdata1 = []byte(` + + value + +`) + +var tolowerdata2 = []byte(` + + value + +`) + +func TestToLower(t *testing.T) { + fmt.Println("\n-------------- keystolower_test.go") + fmt.Println("\nTestToLower ...") + + CoerceKeysToLower() + + m1, err := NewMapXml(tolowerdata1) + if err != nil { + t.Fatal(err) + } + m2, err := NewMapXml(tolowerdata2) + if err != nil { + t.Fatal(err) + } + + v1, err := m1.ValuesForPath("doc.element") + if err != nil { + t.Fatal(err) + } + v2, err := m2.ValuesForPath("doc.element") + if err != nil { + t.Fatal(err) + } + + if len(v1) != len(v2) { + t.Fatal(err, len(v1), len(v2)) + } + + m := v1[0].(map[string]interface{}) + mm := v2[0].(map[string]interface{}) + for k, v := range m { + if vv, ok := mm[k]; !ok { + t.Fatal("key:", k, "not in mm") + } else if v.(string) != vv.(string) { + t.Fatal(v.(string), "not in v2:", vv.(string)) + } + } +} diff --git a/vendor/github.com/clbanning/mxj/misc.go b/vendor/github.com/clbanning/mxj/misc.go new file mode 100644 index 0000000000..378612cb0f --- /dev/null +++ b/vendor/github.com/clbanning/mxj/misc.go @@ -0,0 +1,83 @@ +// 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 + +// misc.go - mimic functions (+others) called out in: +// https://groups.google.com/forum/#!topic/golang-nuts/jm_aGsJNbdQ +// Primarily these methods let you retrive XML structure information. + +package mxj + +import ( + "fmt" + "sort" +) + +// Return the root element of the Map. If there is not a single key in Map, +// then an error is returned. +func (m Map) Root() (string, error) { + mm := map[string]interface{}(m) + if len(mm) != 1 { + return "", fmt.Errorf("Map does not have singleton root. Len: %d.", len(mm)) + } + for k, _ := range mm { + return k, nil + } + return "", nil +} + +// If the path is an element with sub-elements, return a list of the sub-element +// keys. (The list is alphabeticly sorted.) NOTE: Map keys that are prefixed with +// '-', a hyphen, are considered attributes; see m.Attributes(path). +func (m Map) Elements(path string) ([]string, error) { + e, err := m.ValueForPath(path) + if err != nil { + return nil, err + } + switch e.(type) { + case map[string]interface{}: + ee := e.(map[string]interface{}) + elems := make([]string, len(ee)) + var i int + for k, _ := range ee { + if k[:1] == "-" { + continue // skip attributes + } + elems[i] = k + i++ + } + elems = elems[:i] + // alphabetic sort keeps things tidy + sort.Strings(elems) + return elems, nil + } + return nil, fmt.Errorf("no elements for path: %s", path) +} + +// If the path is an element with attributes, return a list of the attribute +// keys. (The list is alphabeticly sorted.) NOTE: Map keys that are not prefixed with +// '-', a hyphen, are not treated as attributes; see m.Elements(path). +func (m Map) Attributes(path string) ([]string, error) { + a, err := m.ValueForPath(path) + if err != nil { + return nil, err + } + switch a.(type) { + case map[string]interface{}: + aa := a.(map[string]interface{}) + attrs := make([]string, len(aa)) + var i int + for k, _ := range aa { + if k[:1] != "-" { + continue // skip non-attributes + } + attrs[i] = k[1:] + i++ + } + attrs = attrs[:i] + // alphabetic sort keeps things tidy + sort.Strings(attrs) + return attrs, nil + } + return nil, fmt.Errorf("no attributes for path: %s", path) +} diff --git a/vendor/github.com/clbanning/mxj/misc_test.go b/vendor/github.com/clbanning/mxj/misc_test.go new file mode 100644 index 0000000000..4547ea7b2c --- /dev/null +++ b/vendor/github.com/clbanning/mxj/misc_test.go @@ -0,0 +1,92 @@ +// misc_test.go + +package mxj + +import ( + "fmt" + "testing" +) + +var miscdata = []byte(` + + + sub_value_1 + sub_value_2 + + element_2 + +`) + +func TestMisc(t *testing.T) { + fmt.Println("\n------------------ misc_test.go ...") +} + +func TestRoot(t *testing.T) { + m, err := NewMapXml(miscdata) + if err != nil { + t.Fatal(err) + } + r, err := m.Root() + if err != nil { + t.Fatal(err) + } + if r != "doc" { + t.Fatal("Root not doc:", r) + } +} + +func TestElements(t *testing.T) { + m, err := NewMapXml(miscdata) + if err != nil { + t.Fatal(err) + } + e, err := m.Elements("doc") + if err != nil { + t.Fatal(err) + } + elist := []string{"elem1", "elem2"} + for i, ee := range e { + if ee != elist[i] { + t.Fatal("error in list, elem#:", i, "-", ee, ":", elist[i]) + } + } + + e, err = m.Elements("doc.elem1") + if err != nil { + t.Fatal(err) + } + elist = []string{"sub1", "sub2"} + for i, ee := range e { + if ee != elist[i] { + t.Fatal("error in list, elem#:", i, "-", ee, ":", elist[i]) + } + } +} + +func TestAttributes(t *testing.T) { + m, err := NewMapXml(miscdata) + if err != nil { + t.Fatal(err) + } + a, err := m.Attributes("doc.elem2") + if err != nil { + t.Fatal(err) + } + alist := []string{"name", "seq"} + for i, aa := range a { + if aa != alist[i] { + t.Fatal("error in list, elem#:", i, "-", aa, ":", alist[i]) + } + } + + a, err = m.Attributes("doc.elem1.sub2") + if err != nil { + t.Fatal(err) + } + alist = []string{"name", "seq"} + for i, aa := range a { + if aa != alist[i] { + t.Fatal("error in list, elem#:", i, "-", aa, ":", alist[i]) + } + } +} diff --git a/vendor/github.com/clbanning/mxj/mxj.go b/vendor/github.com/clbanning/mxj/mxj.go index 774c3dec31..724851d0ff 100644 --- a/vendor/github.com/clbanning/mxj/mxj.go +++ b/vendor/github.com/clbanning/mxj/mxj.go @@ -52,17 +52,17 @@ func (mv Map) Copy() (Map, error) { // Pretty print a Map. func (mv Map) StringIndent(offset ...int) string { - return writeMap(map[string]interface{}(mv), offset...) + return writeMap(map[string]interface{}(mv), true, offset...) } // Pretty print a Map without the value type information - just key:value entries. func (mv Map) StringIndentNoTypeInfo(offset ...int) string { - return writeMapNoTypes(map[string]interface{}(mv), offset...) + return writeMapNoTypes(map[string]interface{}(mv), true, offset...) } // writeMap - dumps the map[string]interface{} for examination. // 'offset' is initial indentation count; typically: Write(m). -func writeMap(m interface{}, offset ...int) string { +func writeMap(m interface{}, root bool, offset ...int) string { var indent int if len(offset) == 1 { indent = offset[0] @@ -74,7 +74,9 @@ func writeMap(m interface{}, offset ...int) string { return "[nil] nil" case string: return "[string] " + m.(string) - case float64: + case int, int32, int64: + return "[int] " + strconv.Itoa(m.(int)) + case float64, float32: return "[float64] " + strconv.FormatFloat(m.(float64), 'e', 2, 64) case bool: return "[bool] " + strconv.FormatBool(m.(bool)) @@ -95,19 +97,21 @@ func writeMap(m interface{}, offset ...int) string { for i := 0; i < indent; i++ { s += " " } - s += writeMap(v, indent+1) + s += writeMap(v, false, indent+1) } case map[string]interface{}: list := make([][2]string, len(m.(map[string]interface{}))) var n int for k, v := range m.(map[string]interface{}) { list[n][0] = k - list[n][1] = writeMap(v, indent+1) + list[n][1] = writeMap(v, false, indent+1) n++ } sort.Sort(mapList(list)) for _, v := range list { - s += "\n" + if !root { + s += "\n" + } for i := 0; i < indent; i++ { s += " " } @@ -122,7 +126,7 @@ func writeMap(m interface{}, offset ...int) string { // writeMapNoTypes - dumps the map[string]interface{} for examination. // 'offset' is initial indentation count; typically: Write(m). -func writeMapNoTypes(m interface{}, offset ...int) string { +func writeMapNoTypes(m interface{}, root bool, offset ...int) string { var indent int if len(offset) == 1 { indent = offset[0] @@ -155,19 +159,21 @@ func writeMapNoTypes(m interface{}, offset ...int) string { for i := 0; i < indent; i++ { s += " " } - s += writeMapNoTypes(v, indent+1) + s += writeMapNoTypes(v, false, indent+1) } case map[string]interface{}: list := make([][2]string, len(m.(map[string]interface{}))) var n int for k, v := range m.(map[string]interface{}) { list[n][0] = k - list[n][1] = writeMapNoTypes(v, indent+1) + list[n][1] = writeMapNoTypes(v, false, indent+1) n++ } sort.Sort(mapList(list)) for _, v := range list { - s += "\n" + if !root { + s += "\n" + } for i := 0; i < indent; i++ { s += " " } diff --git a/vendor/github.com/clbanning/mxj/nan_test.go b/vendor/github.com/clbanning/mxj/nan_test.go new file mode 100644 index 0000000000..be594e047b --- /dev/null +++ b/vendor/github.com/clbanning/mxj/nan_test.go @@ -0,0 +1,81 @@ +// nan_test.go + +package mxj + +import ( + "fmt" + "testing" +) + +func TestNan(t *testing.T) { + fmt.Println("\n------------ TestNan\n") + data := []byte("NAN") + + m, err := NewMapXml(data, true) + if err != nil { + t.Fatal("err:", err) + } + v, err := m.ValueForPath("foo.bar") + if err != nil { + t.Fatal("err:", err) + } + if _, ok := v.(string); !ok { + t.Fatal("v not string") + } + fmt.Println("foo.bar:", v) +} + +func TestInf(t *testing.T) { + data := []byte("INF") + + m, err := NewMapXml(data, true) + if err != nil { + t.Fatal("err:", err) + } + v, err := m.ValueForPath("foo.bar") + if err != nil { + t.Fatal("err:", err) + } + if _, ok := v.(string); !ok { + t.Fatal("v not string") + } + fmt.Println("foo.bar:", v) +} + +func TestMinusInf(t *testing.T) { + data := []byte("-INF") + + m, err := NewMapXml(data, true) + if err != nil { + t.Fatal("err:", err) + } + v, err := m.ValueForPath("foo.bar") + if err != nil { + t.Fatal("err:", err) + } + if _, ok := v.(string); !ok { + t.Fatal("v not string") + } + fmt.Println("foo.bar:", v) +} + +func TestCastNanInf(t *testing.T) { + data := []byte("NAN") + + CastNanInf(true) + + m, err := NewMapXml(data, true) + if err != nil { + t.Fatal("err:", err) + } + v, err := m.ValueForPath("foo.bar") + if err != nil { + t.Fatal("err:", err) + } + if _, ok := v.(float64); !ok { + fmt.Printf("%#v\n", v) + t.Fatal("v not float64") + } + fmt.Println("foo.bar:", v) +} + diff --git a/vendor/github.com/clbanning/mxj/readme.md b/vendor/github.com/clbanning/mxj/readme.md index de5a7d58ae..32983942fa 100644 --- a/vendor/github.com/clbanning/mxj/readme.md +++ b/vendor/github.com/clbanning/mxj/readme.md @@ -1,9 +1,28 @@

mxj - to/from maps, XML and JSON

-Marshal/Unmarshal XML to/from JSON and `map[string]interface{}` values, and extract/modify values from maps by key or key-path, including wildcards. +Decode/encode XML to/from map[string]interface{} (or JSON) values, and extract/modify values from maps by key or key-path, including wildcards. mxj supplants the legacy x2j and j2x packages. If you want the old syntax, use mxj/x2j and mxj/j2x packages. +

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: + + BenchmarkNewMapXml-4 100000 18043 ns/op + BenchmarkNewStructXml-4 100000 14892 ns/op + BenchmarkNewMapJson-4 300000 4633 ns/op + BenchmarkNewStructJson-4 300000 3427 ns/op + BenchmarkNewMapXmlBooks-4 20000 82850 ns/op + BenchmarkNewStructXmlBooks-4 20000 67822 ns/op + BenchmarkNewMapJsonBooks-4 100000 17222 ns/op + BenchmarkNewStructJsonBooks-4 100000 15309 ns/op +

Notices

+ 2016.03.02: By default decoding XML with float64 and bool value casting will not cast "NaN", "Inf", and "-Inf". + To cast them to float64, first set flag with CastNanInf(true). + 2016.02.22: New m.Root(), m.Elements(), m.Attributes methods let you examine XML document structure. + 2016.02.16: Add CoerceKeysToLower() option to handle tags with mixed capitalization. + 2016.02.12: Seek for first xml.StartElement token; only return error if io.EOF is reached first (handles BOM). + 2015.12.02: EXPERIMENTAL XML decoding/encoding that preserves original structure of document. See + NewMapXmlSeq() and mv.XmlSeq() / mv.XmlSeqIndent(). 2015-05-20: New: mv.StringIndentNoTypeInfo(). Also, alphabetically sort map[string]interface{} values by key to prettify output for mv.Xml(), mv.XmlIndent(), mv.StringIndent(), mv.StringIndentNoTypeInfo(). @@ -13,7 +32,7 @@ mxj supplants the legacy x2j and j2x packages. If you want the old syntax, use m 2014-08-02: AnyXml() and AnyXmlIndent() will try to marshal arbitrary values to XML. 2014-04-28: ValuesForPath() and NewMap() now accept path with indexed array references. -

Basic Unmarshal XML / JSON / struct

+

Basic Unmarshal XML to map[string]interface{}

type Map map[string]interface{}
Create a `Map` value, 'm', from any `map[string]interface{}` value, 'v': @@ -63,9 +82,9 @@ leafvalues := m.LeafValues() A new `Map` with whatever keys are desired can be created from the current `Map` and then encoded in XML or JSON. (Note: keys can use dot-notation.) -
newMap := m.NewMap("oldKey_1:newKey_1", "oldKey_2:newKey_2", ..., "oldKey_N:newKey_N")
-newXml := newMap.Xml()   // for example
-newJson := newMap.Json() // ditto
+
newMap, err := m.NewMap("oldKey_1:newKey_1", "oldKey_2:newKey_2", ..., "oldKey_N:newKey_N")
+newXml, err := newMap.Xml()   // for example
+newJson, err := newMap.Json() // ditto

Usage

@@ -75,17 +94,41 @@ Also, the subdirectory "examples" contains a wide range of examples, several tak

XML parsing conventions

+Using NewXml() + - Attributes are parsed to `map[string]interface{}` values by prefixing a hyphen, `-`, to the attribute label. (Unless overridden by `PrependAttrWithHyphen(false)`.) - If the element is a simple element and has attributes, the element value is given the key `#text` for its `map[string]interface{}` representation. (See the 'atomFeedString.xml' test data, below.) + - XML comments, directives, and process instructions are ignored. + - If CoerceKeysToLower() has been called, then the resultant keys will be lower case. + +Using NewXmlSeq() + + - Attributes are parsed to `map["#attr"]map[]map[string]interface{}`values + where the `` value has "#text" and "#seq" keys - the "#text" key holds the + value for ``. + - All elements, except for the root, have a "#seq" key. + - Comments, directives, and process instructions are unmarshalled into the Map using the + keys "#comment", "#directive", and "#procinst", respectively. (See documentation for more + specifics.) + +Both + + - By default, "Nan", "Inf", and "-Inf" values are not cast to float64. If you want them + to be cast, set a flag to cast them using CastNanInf(true).

XML encoding conventions

- 'nil' `Map` values, which may represent 'null' JSON values, are encoded as ``. - NOTE: the operation is not symmetric as `` elements are decoded as `tag:""` `Map` values, - which, then, encode in JSON as `"tag":""` values. + NOTE: the operation is not symmetric as `` elements are decoded as `tag:""` `Map` values, + which, then, encode in JSON as `"tag":""` values. + - ALSO: there is no guarantee that the encoded XML doc will be the same as the decoded one. (Go + randomizes the walk through map[string]interface{} values.) If you plan to re-encode the + Map value to XML and want the same sequencing of elements look at NewMapXmlSeq() and + m.XmlSeq() - these try to preserve the element sequencing but with added complexity when + working with the Map representation.

Running "go test"

@@ -96,15 +139,15 @@ output from running "go test" as examples of calling the various functions and m

Motivation

I make extensive use of JSON for messaging and typically unmarshal the messages into -`map[string]interface{}` variables. This is easily done using `json.Unmarshal` from the +`map[string]interface{}` values. This is easily done using `json.Unmarshal` from the standard Go libraries. Unfortunately, many legacy solutions use structured XML messages; in those environments the applications would have to be refactored to interoperate with my components. The better solution is to just provide an alternative HTTP handler that receives -XML messages and parses it into a `map[string]interface{}` variable and then reuse +XML messages and parses it into a `map[string]interface{}` value and then reuse all the JSON-based code. The Go `xml.Unmarshal()` function does not provide the same -option of unmarshaling XML messages into `map[string]interface{}` variables. So I wrote +option of unmarshaling XML messages into `map[string]interface{}` values. So I wrote a couple of small functions to fill this gap and released them as the x2j package. Over the next year and a half additional features were added, and the companion j2x diff --git a/vendor/github.com/clbanning/mxj/songtext.xml b/vendor/github.com/clbanning/mxj/songtext.xml new file mode 100644 index 0000000000..8c0f2becb1 --- /dev/null +++ b/vendor/github.com/clbanning/mxj/songtext.xml @@ -0,0 +1,29 @@ + + help me! + + + + Henry was a renegade + Didn't like to play it safe + One component at a time + There's got to be a better way + Oh, people came from miles around + Searching for a steady job + Welcome to the Motor Town + Booming like an atom bomb + + + Oh, Henry was the end of the story + Then everything went wrong + And we'll return it to its former glory + But it just takes so long + + + + It's going to take a long time + It's going to take it, but we'll make it one day + It's going to take a long time + It's going to take it, but we'll make it one day + + + diff --git a/vendor/github.com/clbanning/mxj/xml.go b/vendor/github.com/clbanning/mxj/xml.go index 8ac96d28bf..2ab686366d 100644 --- a/vendor/github.com/clbanning/mxj/xml.go +++ b/vendor/github.com/clbanning/mxj/xml.go @@ -1,4 +1,4 @@ -// Copyright 2012-2014 Charles Banning. All rights reserved. +// Copyright 2012-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 @@ -14,14 +14,13 @@ import ( "errors" "fmt" "io" - "regexp" "sort" "strconv" "strings" "time" ) -// ------------------- NewMapXml & NewMapXmlReader ... from x2j2 ------------------------- +// ------------------- NewMapXml & NewMapXmlReader ... ------------------------- // If XmlCharsetReader != nil, it will be used to decode the XML, if required. // import ( @@ -47,23 +46,24 @@ var XmlCharsetReader func(charset string, input io.Reader) (io.Reader, error) // if jerr != nil { // // handle error // } +// +// NOTES: +// 1. 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. func NewMapXml(xmlVal []byte, cast ...bool) (Map, error) { var r bool if len(cast) == 1 { r = cast[0] } - n, err := xmlToTree(xmlVal) - if err != nil { - return nil, err - } - - m := make(map[string]interface{}, 0) - m[n.key] = n.treeToMap(r) - - return m, nil + return xmlToMap(xmlVal, r) } // 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 +// 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. func NewMapXmlReader(xmlReader io.Reader, cast ...bool) (Map, error) { var r bool if len(cast) == 1 { @@ -71,29 +71,26 @@ func NewMapXmlReader(xmlReader io.Reader, cast ...bool) (Map, error) { } // build the node tree - n, err := xmlReaderToTree(xmlReader) - if err != nil { - return nil, err - } - - // create the Map value - m := make(map[string]interface{}) - m[n.key] = n.treeToMap(r) - - return m, nil + return xmlReaderToMap(xmlReader, r) } // XmlWriterBufSize - set the size of io.Writer for the TeeReader used by NewMapXmlReaderRaw() // and HandleXmlReaderRaw(). This reduces repeated memory allocations and copy() calls in most cases. +// NOTE: 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. var XmlWriterBufSize int = 256 // 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 -// 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. To log it, cast it to a string. +// 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. +// 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 +// 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. func NewMapXmlReaderRaw(xmlReader io.Reader, cast ...bool) (Map, []byte, error) { var r bool if len(cast) == 1 { @@ -105,7 +102,7 @@ func NewMapXmlReaderRaw(xmlReader io.Reader, cast ...bool) (Map, []byte, error) trdr := myTeeReader(xmlReader, wb) // see code at EOF // build the node tree - n, err := xmlReaderToTree(trdr) + m, err := xmlReaderToMap(trdr, r) // retrieve the raw XML that was decoded b := make([]byte, wb.Len()) @@ -115,49 +112,28 @@ func NewMapXmlReaderRaw(xmlReader io.Reader, cast ...bool) (Map, []byte, error) return nil, b, err } - // create the Map value - m := make(map[string]interface{}) - m[n.key] = n.treeToMap(r) - return m, b, nil } -// xmlReaderToTree() - parse a XML io.Reader to a tree of nodes -func xmlReaderToTree(rdr io.Reader) (*node, error) { +// xmlReaderToMap() - parse a XML io.Reader to a map[string]interface{} value +func xmlReaderToMap(rdr io.Reader, r bool) (map[string]interface{}, error) { // parse the Reader p := xml.NewDecoder(rdr) p.CharsetReader = XmlCharsetReader - return xmlToTreeParser("", nil, p) + return xmlToMapParser("", nil, p, r) } -// for building the parse tree -type node struct { - dup bool // is member of a list - attr bool // is an attribute - key string // XML tag - val string // element value - nodes []*node -} - -// xmlToTree - convert a XML doc into a tree of nodes. -func xmlToTree(doc []byte) (*node, error) { - // xml.Decoder doesn't properly handle whitespace in some doc - // see songTextString.xml test case ... - reg, _ := regexp.Compile("[ \t\n\r]*<") - doc = reg.ReplaceAll(doc, []byte("<")) - +// xmlToMap - convert a XML doc into map[string]interface{} value +func xmlToMap(doc []byte, r bool) (map[string]interface{}, error) { b := bytes.NewReader(doc) p := xml.NewDecoder(b) p.CharsetReader = XmlCharsetReader - n, berr := xmlToTreeParser("", nil, p) - if berr != nil { - return nil, berr - } - - return n, nil + return xmlToMapParser("", nil, p, r) } -// we allow people to drop hyphen when unmarshaling the XML doc. +// ===================================== where the work happens ============================= + +// Allow people to drop hyphen when unmarshaling the XML doc. var useHyphen bool = true // PrependAttrWithHyphen. Prepend attribute tags with a hyphen. @@ -173,7 +149,10 @@ func PrependAttrWithHyphen(v bool) { var includeTagSeqNum bool // IncludeTagSeqNum - include a "_seq":N key:value pair with each inner tag, denoting -// its position when parsed. E.g., +// its position when parsed. This is of limited usefulness, since list values cannot +// be tagged with "_seq" without changing their depth in the Map. +// So THIS SHOULD BE USED WITH CAUTION - see the test cases. Here's a sample of what +// you get. /* @@ -213,25 +192,58 @@ func IncludeTagSeqNum(b bool) { includeTagSeqNum = b } -// xmlToTreeParser - load a 'clean' XML doc into a tree of *node. -func xmlToTreeParser(skey string, a []xml.Attr, p *xml.Decoder) (*node, error) { - n := new(node) - n.nodes = make([]*node, 0) +// all keys will be "lower case" +var lowerCase bool + +// Coerce all tag values to keys in lower case. This is useful if you've got sources with variable +// tag capitalization, and you want to use m.ValuesForKeys(), etc., with the key or path spec +// in lower case. +// CoerceKeysToLower() will toggle the coercion flag true|false - on|off +// CoerceKeysToLower(true|false) will set the coercion flag on|off +// +// NOTE: only recognized by NewMapXml, NewMapXmlReader, and NewMapXmlReaderRaw functions as well as +// the associated HandleXmlReader and HandleXmlReaderRaw. +func CoerceKeysToLower(b ...bool) { + if len(b) == 1 { + lowerCase = b[0] + return + } + if !lowerCase { + lowerCase = true + } else { + lowerCase = false + } +} + +// xmlToMapParser (2015.11.12) - load a 'clean' XML doc into a map[string]interface{} directly. +// A refactoring of xmlToTreeParser(), markDuplicate() and treeToMap() - here, all-in-one. +// We've removed the intermediate *node tree with the allocation and subsequent rescanning. +func xmlToMapParser(skey string, a []xml.Attr, p *xml.Decoder, r bool) (map[string]interface{}, error) { + if lowerCase { + skey = strings.ToLower(skey) + } + + // NOTE: all attributes and sub-elements parsed into 'na', 'na' is returned as value for 'skey' + // Unless 'skey' is a simple element w/o attributes, in which case the xml.CharData value is the value. + var n, na map[string]interface{} var seq int // for includeTagSeqNum + // Allocate maps and load attributes, if any. if skey != "" { - n.key = skey + n = make(map[string]interface{}) // old n + na = make(map[string]interface{}) // old n.nodes if len(a) > 0 { for _, v := range a { - na := new(node) - na.attr = true + var key string if useHyphen { - na.key = `-` + v.Name.Local + key = `-` + v.Name.Local } else { - na.key = v.Name.Local + key = v.Name.Local } - na.val = v.Value - n.nodes = append(n.nodes, na) + if lowerCase { + key = strings.ToLower(key) + } + na[key] = cast(v.Value, r) } } } @@ -246,132 +258,128 @@ func xmlToTreeParser(skey string, a []xml.Attr, p *xml.Decoder) (*node, error) { switch t.(type) { case xml.StartElement: tt := t.(xml.StartElement) - // handle root - if n.key == "" { - n.key = tt.Name.Local - if len(tt.Attr) > 0 { - for _, v := range tt.Attr { - na := new(node) - na.attr = true - if useHyphen { - na.key = `-` + v.Name.Local - } else { - na.key = v.Name.Local - } - na.val = v.Value - n.nodes = append(n.nodes, na) - } - } - } else { - nn, nnerr := xmlToTreeParser(tt.Name.Local, tt.Attr, p) - if nnerr != nil { - return nil, nnerr - } - n.nodes = append(n.nodes, nn) - if includeTagSeqNum { // 2014.11.09 - sn := &node{false, false, "_seq", strconv.Itoa(seq), nil} - nn.nodes = append(nn.nodes, sn) + + // First call to xmlToMapParser() doesn't pass xml.StartElement - the map key. + // So when the loop is first entered, the first token is the root tag along + // with any attributes, which we process here. + // + // Subsequent calls to xmlToMapParser() will pass in tag+attributes for + // processing before getting the next token which is the element value, + // which is done above. + if skey == "" { + return xmlToMapParser(tt.Name.Local, tt.Attr, p, r) + } + + // If not initializing the map, parse the element. + // len(nn) == 1, necessarily - it is just an 'n'. + nn, err := xmlToMapParser(tt.Name.Local, tt.Attr, p, r) + if err != nil { + return nil, err + } + + // The nn map[string]interface{} value is a na[nn_key] value. + // We need to see if nn_key already exists - means we're parsing a list. + // This may require converting na[nn_key] value into []interface{} type. + // First, extract the key:val for the map - it's a singleton. + // Note: if CoerceKeysToLower() called, then key will be lower case. + var key string + var val interface{} + for key, val = range nn { + break + } + + // IncludeTagSeqNum requests that the element be augmented with a "_seq" sub-element. + // In theory, we don't need this if len(na) == 1. But, we don't know what might + // come next - we're only parsing forward. So if you ask for 'includeTagSeqNum' you + // get it on every element. (Personally, I never liked this, but I added it on request + // and did get a $50 Amazon gift card in return - now we support it for backwards compatibility!) + if includeTagSeqNum { + switch val.(type) { + case []interface{}: + // noop - There's no clean way to handle this w/o changing message structure. + case map[string]interface{}: + 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["_seq"] = seq + seq++ + val = v } } + + // 'na' holding sub-elements of n. + // See if 'key' already exists. + // If 'key' exists, then this is a list, if not just add key:val to na. + if v, ok := na[key]; ok { + var a []interface{} + switch v.(type) { + case []interface{}: + a = v.([]interface{}) + default: // anything else - note: v.(type) != nil + a = []interface{}{v} + } + a = append(a, val) + na[key] = a + } else { + na[key] = val // save it as a singleton + } case xml.EndElement: - // scan n.nodes for duplicate n.key values - n.markDuplicateKeys() + // len(n) > 0 if this is a simple element w/o xml.Attrs - see xml.CharData case. + if len(n) == 0 { + // If len(na)==0 we have an empty element == ""; + // it has no xml.Attr nor xml.CharData. + // Note: in original node-tree parser, val defaulted to ""; + // so we always had the default if len(node.nodes) == 0. + if len(na) > 0 { + n[skey] = na + } else { + n[skey] = "" // empty element + } + } return n, nil case xml.CharData: - tt := string(t.(xml.CharData)) // clean up possible noise - tt = strings.Trim(tt, "\t\r\b\n ") - if len(n.nodes) > 0 && len(tt) > 0 { - // if len(n.nodes) > 0 { - nn := new(node) - nn.key = "#text" - nn.val = tt - n.nodes = append(n.nodes, nn) - } else { - n.val = tt - } - if includeTagSeqNum { // 2014.11.09 - if len(n.nodes) == 0 { // treat like a simple element with attributes - nn := new(node) - nn.key = "#text" - nn.val = tt - n.nodes = append(n.nodes, nn) + tt := strings.Trim(string(t.(xml.CharData)), "\t\r\b\n ") + if len(tt) > 0 { + if len(na) > 0 { + na["#text"] = cast(tt, r) + } else if skey != "" { + n[skey] = cast(tt, r) + } else { + // per Adrian (http://www.adrianlungu.com/) catch stray text + // in decoder stream - + // https://github.com/clbanning/mxj/pull/14#issuecomment-182816374 + // NOTE: CharSetReader must be set to non-UTF-8 CharSet or you'll get + // a p.Token() decoding error when the BOM is UTF-16 or UTF-32. + continue } - sn := &node{false, false, "_seq", strconv.Itoa(seq), nil} - n.nodes = append(n.nodes, sn) - seq++ } default: // noop } } - // Logically we can't get here, but provide an error message anyway. - return nil, fmt.Errorf("Unknown parse error in xmlToTree() for: %s", n.key) } -// (*node)markDuplicateKeys - set node.dup flag for loading map[string]interface{}. -func (n *node) markDuplicateKeys() { - l := len(n.nodes) - for i := 0; i < l; i++ { - if n.nodes[i].dup { - continue - } - for j := i + 1; j < l; j++ { - if n.nodes[i].key == n.nodes[j].key { - n.nodes[i].dup = true - n.nodes[j].dup = true - } - } - } -} +var castNanInf bool -// (*node)treeToMap - convert a tree of nodes into a map[string]interface{}. -// (Parses to map that is structurally the same as from json.Unmarshal().) -// Note: root is not instantiated; call with: "m[n.key] = n.treeToMap(cast)". -func (n *node) treeToMap(r bool) interface{} { - if len(n.nodes) == 0 { - return cast(n.val, r) - } - - m := make(map[string]interface{}, 0) - for _, v := range n.nodes { - // 2014.11.9 - may have to back out - if includeTagSeqNum { - if len(v.nodes) == 1 { - m[v.key] = cast(v.val, r) - continue - } - } - - // just a value - if !v.dup && len(v.nodes) == 0 { - m[v.key] = cast(v.val, r) - continue - } - // a list of values - if v.dup { - var a []interface{} - if vv, ok := m[v.key]; ok { - a = vv.([]interface{}) - } else { - a = make([]interface{}, 0) - } - a = append(a, v.treeToMap(r)) - m[v.key] = interface{}(a) - continue - } - - // it's a unique key - m[v.key] = v.treeToMap(r) - } - - return interface{}(m) +// Cast "Nan", "Inf", "-Inf" XML values to 'float64'. +// By default, these values will be decoded as 'string'. +func CastNanInf(b bool) { + castNanInf = b } // cast - try to cast string values to bool or float64 func cast(s string, r bool) interface{} { if r { + // handle nan and inf + if !castNanInf { + switch strings.ToLower(s) { + case "nan", "inf", "-inf": + return interface{}(s) + } + } + // handle numeric strings ahead of boolean if f, err := strconv.ParseFloat(s, 64); err == nil { return interface{}(f) @@ -518,7 +526,7 @@ func (mv Map) XmlIndentWriterRaw(xmlWriter io.Writer, prefix, indent string, roo // Default poll delay to keep Handler from spinning on an open stream // like sitting on os.Stdin waiting for imput. -var xhandlerPollInterval = time.Duration(1e6) +var xhandlerPollInterval = time.Millisecond // Bulk process XML using handlers that process a Map value. // 'rdr' is an io.Reader for XML (stream) @@ -549,7 +557,7 @@ func HandleXmlReader(xmlReader io.Reader, mapHandler func(Map) bool, errHandler break } } else if merr != io.EOF { - <-time.After(xhandlerPollInterval) + time.Sleep(xhandlerPollInterval) } if merr == io.EOF { @@ -589,7 +597,7 @@ func HandleXmlReaderRaw(xmlReader io.Reader, mapHandler func(Map, []byte) bool, break } } else if merr != io.EOF { - <-time.After(xhandlerPollInterval) + time.Sleep(xhandlerPollInterval) } if merr == io.EOF { @@ -741,12 +749,11 @@ func mapToXmlIndent(doIndent bool, s *string, key string, value interface{}, pp break } // simple element? Note: '#text" is an invalid XML tag. - if v, ok := vv["#text"]; ok { - if cntAttr+1 < lenvv { - return errors.New("#text key occurs with other non-attribute keys") - } + if v, ok := vv["#text"]; ok && cntAttr+1 == lenvv { *s += ">" + fmt.Sprintf("%v", v) endTag = true + elen = 1 + isSimple = true break } // close tag with possible attributes @@ -771,9 +778,6 @@ func mapToXmlIndent(doIndent bool, s *string, key string, value interface{}, pp sort.Sort(elemList(elemlist)) var i int for _, v := range elemlist { - // if k[:1] == "-" { - // continue - // } switch v[1].(type) { case []interface{}: default: @@ -846,19 +850,15 @@ func mapToXmlIndent(doIndent bool, s *string, key string, value interface{}, pp isSimple = true endTag = true } - if endTag { if doIndent { if !isSimple { - // if p.mapDepth == 0 { - // p.Outdent() - // } *s += p.padding } } switch value.(type) { case map[string]interface{}, []byte, string, float64, bool, int, int32, int64, float32: - if elen > 0 || useGoXmlEmptyElemSyntax { + if elen > 0 || useGoXmlEmptyElemSyntax { if elen == 0 { *s += ">" } diff --git a/vendor/github.com/clbanning/mxj/xml2_test.go b/vendor/github.com/clbanning/mxj/xml2_test.go new file mode 100644 index 0000000000..0e9ff56eff --- /dev/null +++ b/vendor/github.com/clbanning/mxj/xml2_test.go @@ -0,0 +1,322 @@ +package mxj + +import ( + "encoding/json" + "encoding/xml" + "fmt" + "io" + "os" + "testing" +) + +func TestXml2Header(t *testing.T) { + fmt.Println("\n---------------- xml2_test.go ...\n") +} + +func TestNewMapXml4(t *testing.T) { + x := []byte(` + + + William T. Gaddis + The Recognitions + One of the great seminal American novels of the 20th century. + + + Austin Tappan Wright + Islandia + An example of earlier 20th century American utopian fiction. + + + John Hawkes + The Beetle Leg + A lyrical novel about the construction of Ft. Peck Dam in Montana. + + + + T.E. + Porter + + King's Day + A magical novella. + + +`) + + m, err := NewMapXml(x) + if err != nil && err != io.EOF { + t.Fatal("err:", err.Error()) + } + fmt.Println("NewMapXml4, x:\n", string(x)) + fmt.Println("NewMapXml4, m:\n", m) + fmt.Println("NewMapXml4, s:\n", m.StringIndent()) + b, err := m.XmlIndent("", " ") + if err != nil { + t.Fatal("err:", err) + } + fmt.Println("NewMapXml4, b:\n", string(b)) +} + +func TestNewMapXml5(t *testing.T) { + fh, err := os.Open("songtext.xml") + if err != nil { + t.Fatal("err:", err.Error()) + } + defer fh.Close() + + m, raw, err := NewMapXmlReaderRaw(fh) + if err != nil && err != io.EOF { + t.Fatal("err:", err.Error()) + } + fmt.Println("NewMapXml5, raw:\n", string(raw)) + fmt.Println("NewMapXml5, m:\n", m) + fmt.Println("NewMapXml5, s:\n", m.StringIndent()) + b, err := m.Xml() + if err != nil { + t.Fatal("err:", err) + } + fmt.Println("NewMapXml5, b:\n", string(b)) + b, err = m.XmlIndent("", " ") + if err != nil { + t.Fatal("err:", err) + } + fmt.Println("NewMapXml5, b:\n", string(b)) +} + +func TestNewMapXml6(t *testing.T) { + fh, err := os.Open("atomFeedString.xml") + if err != nil { + t.Fatal("err:", err.Error()) + } + defer fh.Close() + + m, raw, err := NewMapXmlReaderRaw(fh) + if err != nil && err != io.EOF { + t.Fatal("err:", err.Error()) + } + fmt.Println("NewMapXml6, raw:\n", string(raw)) + fmt.Println("NewMapXml6, m:\n", m) + fmt.Println("NewMapXml6, s:\n", m.StringIndent()) + b, err := m.Xml() + if err != nil { + t.Fatal("err:", err) + } + fmt.Println("NewMapXml6, b:\n", string(b)) + b, err = m.XmlIndent("", " ") + if err != nil { + t.Fatal("err:", err) + } + fmt.Println("NewMapXml6, b:\n", string(b)) +} + +// ===================================== benchmarks ============================ + +var smallxml = []byte(` + + + this + is + the + end + + +`) + +var smalljson = []byte(`{"doc":{"words":{"word1":"this","word2":"is","word3":"the","word4":"end"}}}`) + +type words struct { + Word1 string `xml:"word1"` + Word2 string `xml:"word2"` + Word3 string `xml:"word3"` + Word4 string `xml:"word4"` +} + +type xmldoc struct { + Words words `xml:"words"` +} + +type jsondoc struct { + Doc xmldoc +} + +func BenchmarkNewMapXml(b *testing.B) { + // var m Map + var err error + for i := 0; i < b.N; i++ { + if _, err = NewMapXml(smallxml); err != nil { + b.Fatal("err:", err) + } + } + // fmt.Println("m Map:", m) +} + +func BenchmarkNewStructXml(b *testing.B) { + var s *xmldoc + var err error + for i := 0; i < b.N; i++ { + s = new(xmldoc) + if err = xml.Unmarshal(smallxml, s); err != nil { + b.Fatal("err:", err) + } + } + // fmt.Println("s xmldoc:", *s) +} + +func BenchmarkNewMapJson(b *testing.B) { + var m map[string]interface{} + var err error + for i := 0; i < b.N; i++ { + m = make(map[string]interface{}) + if err = json.Unmarshal(smalljson, &m); err != nil { + b.Fatal("err:", err) + } + } + // fmt.Println("m map:", m) +} + +func BenchmarkNewStructJson(b *testing.B) { + var s *jsondoc + var err error + for i := 0; i < b.N; i++ { + s = new(jsondoc) + if err = json.Unmarshal(smalljson, s); err != nil { + b.Fatal("err:", err) + } + } + // fmt.Println("s jsondoc:", *s) +} + +// ================== something with a little more content ... =================== + +var xmlbooks = []byte(` + + + + William T. Gaddis + The Recognitions + One of the great seminal American novels of the 20th century. + + + Austin Tappan Wright + Islandia + An example of earlier 20th century American utopian fiction. + + + John Hawkes + The Beetle Leg + A lyrical novel set during the construction of Ft. Peck Dam in Montana. + + + + T.E. + Porter + + King's Day + A magical novella. + + + +`) + +var jsonbooks = []byte(` +{"doc": + {"books": + {"book":[ + { "author":"William T. Gaddis", + "title":"The Recognitions", + "review":"One of the great seminal American novels of the 20th century." + }, + { "author":"Austin Tappan Wright", + "title":"Islandia", + "review":"An example of earlier 20th century American utopian fiction." + }, + { "author":"John Hawkes", + "title":"The Beetle Leg", + "review":"A lyrical novel set during the construction of Ft. Peck Dam in Montana." + }, + { "author":{"first_name":"T.E.", "last_name":"Porter"}, + "title":"King's Day", + "review":"A magical novella." + }] + } + } +} +`) + +type book struct { + Author string `xml:"author"` + Title string `xml:"title"` + Review string `xml:"review"` +} + +type books struct { + Book []book `xml:"book"` +} + +type doc struct { + Books books `xml:"books"` +} + +type jsonbook struct { + Author json.RawMessage + Title string + Review string +} + +type jsonbooks2 struct { + Book []jsonbook +} + +type jsondoc1 struct { + Books jsonbooks2 +} + +type jsondoc2 struct { + Doc jsondoc1 +} + +func BenchmarkNewMapXmlBooks(b *testing.B) { + // var m Map + var err error + for i := 0; i < b.N; i++ { + if _, err = NewMapXml(xmlbooks); err != nil { + b.Fatal("err:", err) + } + } + // fmt.Println("m Map:", m) +} + +func BenchmarkNewStructXmlBooks(b *testing.B) { + var s *doc + var err error + for i := 0; i < b.N; i++ { + s = new(doc) + if err = xml.Unmarshal(xmlbooks, s); err != nil { + b.Fatal("err:", err) + } + } + // fmt.Println("s doc:", *s) +} + +func BenchmarkNewMapJsonBooks(b *testing.B) { + var m map[string]interface{} + var err error + for i := 0; i < b.N; i++ { + m = make(map[string]interface{}) + if err = json.Unmarshal(jsonbooks, &m); err != nil { + b.Fatal("err:", err) + } + } + // fmt.Println("m map:", m) +} + +func BenchmarkNewStructJsonBooks(b *testing.B) { + var s *jsondoc2 + var err error + for i := 0; i < b.N; i++ { + s = new(jsondoc2) + if err = json.Unmarshal(jsonbooks, s); err != nil { + b.Fatal("err:", err) + } + } + // fmt.Println("s jsondoc2:", *s) +} diff --git a/vendor/github.com/clbanning/mxj/xmlseq.go b/vendor/github.com/clbanning/mxj/xmlseq.go new file mode 100644 index 0000000000..9186fd5a2b --- /dev/null +++ b/vendor/github.com/clbanning/mxj/xmlseq.go @@ -0,0 +1,723 @@ +// Copyright 2012-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 + +// xmlseq.go - version of xml.go with sequence # injection on Decoding and sorting on Encoding. +// Also, handles comments, directives and process instructions. + +package mxj + +import ( + "bytes" + "encoding/xml" + "errors" + "fmt" + "io" + "sort" + "strings" +) + +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. +// +// NewMapXmlSeq - convert a XML doc into a Map with elements id'd with decoding sequence int - #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(). +// • 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 +// include a "#seq" k:v pair based on sequence they are decoded. Thus, XML like: +// +// value 1 +// value 2 +// value 3 +// +// is decoded as: +// doc : +// ltag :[[]interface{}] +// [item: 0] +// #seq :[int] 0 +// #text :[string] value 1 +// [item: 1] +// #seq :[int] 2 +// #text :[string] value 3 +// 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. +// • 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 +// is of map[string]interface{} type with the following keys: #target, #inst, and #seq. +// • comments, directives, and procinsts that are NOT part of a document with a root key will be returned as +// map[string]interface{} and the error value 'NoRoot'. +// • note: " 0 { + // xml.Attr is decoded into: map["#attr"]map[]interface{} + // where interface{} is map[string]interface{}{"#text":, "#seq":} + aa := make(map[string]interface{}, len(a)) + for i, v := range a { + aa[v.Name.Local] = map[string]interface{}{"#text": cast(v.Value, r), "#seq": i} + } + na["#attr"] = aa + } + } + for { + t, err := p.Token() + if err != nil { + if err != io.EOF { + return nil, errors.New("xml.Decoder.Token() - " + err.Error()) + } + return nil, err + } + switch t.(type) { + case xml.StartElement: + tt := t.(xml.StartElement) + + // First call to xmlSeqToMapParser() doesn't pass xml.StartElement - the map key. + // So when the loop is first entered, the first token is the root tag along + // with any attributes, which we process here. + // + // Subsequent calls to xmlSeqToMapParser() will pass in tag+attributes for + // processing before getting the next token which is the element value, + // which is done above. + if skey == "" { + return xmlSeqToMapParser(tt.Name.Local, tt.Attr, p, r) + } + + // If not initializing the map, parse the element. + // len(nn) == 1, necessarily - it is just an 'n'. + nn, err := xmlSeqToMapParser(tt.Name.Local, tt.Attr, p, r) + if err != nil { + return nil, err + } + + // The nn map[string]interface{} value is a na[nn_key] value. + // We need to see if nn_key already exists - means we're parsing a list. + // This may require converting na[nn_key] value into []interface{} type. + // First, extract the key:val for the map - it's a singleton. + var key string + var val interface{} + for key, val = range nn { + break + } + + // add "#seq" k:v pair - + // Sequence number included even in list elements - this should allow us + // to properly resequence even something goofy like: + // item 1 + // item 2 + // item 3 + // where all the "list" subelements are decoded into an array. + switch val.(type) { + case map[string]interface{}: + val.(map[string]interface{})["#seq"] = seq + seq++ + case interface{}: // a non-nil simple element: string, float64, bool + v := map[string]interface{}{"#text": val, "#seq": seq} + seq++ + val = v + } + + // 'na' holding sub-elements of n. + // See if 'key' already exists. + // If 'key' exists, then this is a list, if not just add key:val to na. + if v, ok := na[key]; ok { + var a []interface{} + switch v.(type) { + case []interface{}: + a = v.([]interface{}) + default: // anything else - note: v.(type) != nil + a = []interface{}{v} + } + a = append(a, val) + na[key] = a + } else { + na[key] = val // save it as a singleton + } + case xml.EndElement: + // len(n) > 0 if this is a simple element w/o xml.Attrs - see xml.CharData case. + if len(n) == 0 { + // If len(na)==0 we have an empty element == ""; + // it has no xml.Attr nor xml.CharData. + // Empty element content will be map["etag"]map["#text"]"" + // after #seq injection - map["etag"]map["#seq"]seq - after return. + if len(na) > 0 { + n[skey] = na + } else { + n[skey] = "" // empty element + } + } + return n, nil + case xml.CharData: + // clean up possible noise + tt := strings.Trim(string(t.(xml.CharData)), "\t\r\b\n ") + if skey == "" { + // per Adrian (http://www.adrianlungu.com/) catch stray text + // in decoder stream - + // https://github.com/clbanning/mxj/pull/14#issuecomment-182816374 + // NOTE: CharSetReader must be set to non-UTF-8 CharSet or you'll get + // a p.Token() decoding error when the BOM is UTF-16 or UTF-32. + continue + } + if len(tt) > 0 { + // every simple element is a #text and has #seq associated with it + na["#text"] = cast(tt, r) + na["#seq"] = seq + seq++ + } + case xml.Comment: + if n == nil { // no root 'key' + n = map[string]interface{}{"#comment": string(t.(xml.Comment))} + return n, NoRoot + } + cm := make(map[string]interface{}, 2) + cm["#text"] = string(t.(xml.Comment)) + cm["#seq"] = seq + seq++ + na["#comment"] = cm + case xml.Directive: + if n == nil { // no root 'key' + n = map[string]interface{}{"#directive": string(t.(xml.Directive))} + return n, NoRoot + } + dm := make(map[string]interface{}, 2) + dm["#text"] = string(t.(xml.Directive)) + dm["#seq"] = seq + seq++ + na["#directive"] = 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} + 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 + seq++ + na["#procinst"] = pm + default: + // noop - shouldn't ever get here, now, since we handle all token types + } + } +} + +// ------------------ END: NewMapXml & NewMapXmlReader ------------------------- + +// ------------------ mv.Xml & mv.XmlWriter - from j2x ------------------------ + +// 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(). +// 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 "#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 - . +// - The "#procinst" map key identifies a process instruction in the value "#target" and "#inst" +// map entries - . +// - Value type encoding: +// > string, bool, float64, int, int32, int64, float32: per "%v" formating +// > []bool, []uint8: by casting to string +// > structures, etc.: handed to xml.Marshal() - if there is an error, the element +// value is "UNKNOWN" +// - 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) { + m := map[string]interface{}(mv) + var err error + s := new(string) + p := new(pretty) // just a stub + + if len(m) == 1 && len(rootTag) == 0 { + for key, value := range m { + // if it an array, see if all values are map[string]interface{} + // we force a new root tag if we'll end up with no key:value in the list + // so: key:[string_val, bool:true] --> string_valtrue + switch value.(type) { + case []interface{}: + for _, v := range value.([]interface{}) { + switch v.(type) { + case map[string]interface{}: // noop + default: // anything else + err = mapToXmlSeqIndent(false, s, DefaultRootTag, m, p) + goto done + } + } + } + err = mapToXmlSeqIndent(false, s, key, value, p) + } + } else if len(rootTag) == 1 { + err = mapToXmlSeqIndent(false, s, rootTag[0], m, p) + } else { + err = mapToXmlSeqIndent(false, s, DefaultRootTag, m, p) + } +done: + 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 Xml() for encoding rules. +func (mv Map) XmlSeqWriter(xmlWriter io.Writer, rootTag ...string) error { + x, err := mv.XmlSeq(rootTag...) + if err != nil { + return err + } + + _, err = xmlWriter.Write(x) + 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 Xml() for encoding rules. +func (mv Map) XmlSeqWriterRaw(xmlWriter io.Writer, rootTag ...string) ([]byte, error) { + x, err := mv.XmlSeq(rootTag...) + if err != nil { + return x, err + } + + _, 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...) + if err != nil { + return err + } + + _, err = xmlWriter.Write(x) + 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 Xml() for encoding rules. +func (mv Map) XmlSeqIndentWriterRaw(xmlWriter io.Writer, prefix, indent string, rootTag ...string) ([]byte, error) { + x, err := mv.XmlSeqIndent(prefix, indent, rootTag...) + if err != nil { + return x, err + } + + _, 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) { + m := map[string]interface{}(mv) + + var err error + s := new(string) + p := new(pretty) + p.indent = indent + p.padding = prefix + + if len(m) == 1 && len(rootTag) == 0 { + // this can extract the key for the single map element + // use it if it isn't a key for a list + for key, value := range m { + if _, ok := value.([]interface{}); ok { + err = mapToXmlSeqIndent(true, s, DefaultRootTag, m, p) + } else { + err = mapToXmlSeqIndent(true, s, key, value, p) + } + } + } else if len(rootTag) == 1 { + err = mapToXmlSeqIndent(true, s, rootTag[0], m, p) + } else { + err = mapToXmlSeqIndent(true, s, DefaultRootTag, m, p) + } + return []byte(*s), err +} + +// where the work actually happens +// returns an error if an attribute is not atomic +func mapToXmlSeqIndent(doIndent bool, s *string, key string, value interface{}, pp *pretty) error { + var endTag bool + var isSimple bool + var noEndTag bool + var elen int + p := &pretty{pp.indent, pp.cnt, pp.padding, pp.mapDepth, pp.start} + + switch value.(type) { + case map[string]interface{}, []byte, string, float64, bool, int, int32, int64, float32: + if doIndent { + *s += p.padding + } + if key != "#comment" && key != "#directive" && key != "#procinst" { + *s += `<` + key + } + } + switch value.(type) { + case map[string]interface{}: + val := value.(map[string]interface{}) + + if key == "#comment" { + *s += `` + noEndTag = true + break + } + + if key == "#directive" { + *s += `` + noEndTag = true + break + } + + if key == "#procinst" { + *s += `` + noEndTag = true + break + } + + haveAttrs := false + // process attributes first + if v, ok := val["#attr"].(map[string]interface{}); ok { + // First, unroll the map[string]interface{} into a []keyval array. + // Then sequence it. + kv := make([]keyval, len(v)) + n := 0 + for ak, av := range v { + kv[n] = keyval{ak, av} + n++ + } + sort.Sort(elemListSeq(kv)) + // 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) { + case string, float64, bool, int, int32, int64, float32: + *s += ` ` + a.k + `="` + fmt.Sprintf("%v", vv["#text"]) + `"` + case []byte: + *s += ` ` + a.k + `="` + fmt.Sprintf("%v", string(vv["#text"].([]byte))) + `"` + default: + return fmt.Errorf("invalid attribute value for: %s", a.k) + } + } + haveAttrs = true + } + + // 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 { + if stmp, ok := v.(string); ok && stmp != "" { + *s += ">" + fmt.Sprintf("%v", v) + endTag = true + elen = 1 + } + isSimple = true + break + } else if !ok && ((len(val) == 2 && haveAttrs) || (len(val) == 1 && !haveAttrs)) && seqOK { + // here no #text but have #seq or #seq+#attr + endTag = false + break + } + + // we now need to sequence everything except attributes + // 'kv' will hold everything that needs to be written + kv := make([]keyval, 0) + for k, v := range val { + if k == "#attr" { // already processed + continue + } + if k == "#seq" { // ignore - just for sorting + continue + } + switch v.(type) { + case []interface{}: + // unwind the array as separate entries + for _, vv := range v.([]interface{}) { + kv = append(kv, keyval{k, vv}) + } + default: + kv = append(kv, keyval{k, v}) + } + } + + // close tag with possible attributes + *s += ">" + if doIndent { + *s += "\n" + } + // something more complex + p.mapDepth++ + // PrintElemListSeq(elemListSeq(kv)) + sort.Sort(elemListSeq(kv)) + // PrintElemListSeq(elemListSeq(kv)) + i := 0 + for _, v := range kv { + switch v.v.(type) { + case []interface{}: + default: + if i == 0 && doIndent { + p.Indent() + } + } + i++ + mapToXmlSeqIndent(doIndent, s, v.k, v.v, p) + switch v.v.(type) { + case []interface{}: // handled in []interface{} case + default: + if doIndent { + p.Outdent() + } + } + i-- + } + p.mapDepth-- + endTag = true + elen = 1 // we do have some content other than attrs + case []interface{}: + for _, v := range value.([]interface{}) { + if doIndent { + p.Indent() + } + mapToXmlSeqIndent(doIndent, s, key, v, p) + if doIndent { + p.Outdent() + } + } + return nil + case nil: + // terminate the tag + *s += "<" + key + break + default: // handle anything - even goofy stuff + elen = 0 + switch value.(type) { + case string, float64, bool, int, int32, int64, float32: + v := fmt.Sprintf("%v", value) + elen = len(v) + if elen > 0 { + *s += ">" + v + } + case []byte: // NOTE: byte is just an alias for uint8 + // similar to how xml.Marshal handles []byte structure members + v := string(value.([]byte)) + elen = len(v) + if elen > 0 { + *s += ">" + v + } + default: + var v []byte + var err error + if doIndent { + v, err = xml.MarshalIndent(value, p.padding, p.indent) + } else { + v, err = xml.Marshal(value) + } + if err != nil { + *s += ">UNKNOWN" + } else { + elen = len(v) + if elen > 0 { + *s += string(v) + } + } + } + isSimple = true + endTag = true + } + if endTag && !noEndTag { + if doIndent { + if !isSimple { + *s += p.padding + } + } + switch value.(type) { + case map[string]interface{}, []byte, string, float64, bool, int, int32, int64, float32: + if elen > 0 || useGoXmlEmptyElemSyntax { + if elen == 0 { + *s += ">" + } + *s += `" + } else { + *s += `/>` + } + } + } else if !noEndTag { + if useGoXmlEmptyElemSyntax { + *s += ">" + } else { + *s += "/>" + } + } + if doIndent { + if p.cnt > p.start { + *s += "\n" + } + p.Outdent() + } + + return nil +} + +// the element sort implementation + +type keyval struct { + k string + v interface{} +} +type elemListSeq []keyval + +func (e elemListSeq) Len() int { + return len(e) +} + +func (e elemListSeq) Swap(i, j int) { + e[i], e[j] = e[j], e[i] +} + +func (e elemListSeq) Less(i, j int) bool { + var iseq, jseq int + var ok bool + if iseq, ok = e[i].v.(map[string]interface{})["#seq"].(int); !ok { + iseq = 9999999 + } + + if jseq, ok = e[j].v.(map[string]interface{})["#seq"].(int); !ok { + jseq = 9999999 + } + + if iseq > jseq { + return false + } + return true +} + +func PrintElemListSeq(e elemListSeq) { + for n, v := range e { + fmt.Printf("%d: %v\n", n, v) + } +} diff --git a/vendor/github.com/clbanning/mxj/xmlseq_test.go b/vendor/github.com/clbanning/mxj/xmlseq_test.go new file mode 100644 index 0000000000..d9929da178 --- /dev/null +++ b/vendor/github.com/clbanning/mxj/xmlseq_test.go @@ -0,0 +1,67 @@ + +package mxj + +import ( + "fmt" + "io" + "testing" +) + +func TestXmlSeqHeader(t *testing.T) { + fmt.Println("\n---------------- xmlseq_test.go ...\n") +} + +func TestNewMapXmlSeq(t *testing.T) { + x := []byte(` + + + William T. Gaddis + Gaddis is one of the most influential but little know authors in America. + The Recognitions + + One of the great seminal American novels of the 20th century. + Without it Thomas Pynchon probably wouldn't have written Gravity's Rainbow. + + + Austin Tappan Wright + Islandia + An example of earlier 20th century American utopian fiction. + + + John Hawkes + The Beetle Leg + + A lyrical novel about the construction of Ft. Peck Dam in Montana. + + + + + T.E. + Porter + + King's Day + A magical novella. + + +`) + + m, err := NewMapXmlSeq(x) + if err != nil && err != io.EOF { + t.Fatal("err:", err.Error()) + } + fmt.Println("NewMapXmlSeq, x:\n", string(x)) + // fmt.Println("NewMapXmlSeq, m:\n", m) + fmt.Println("NewMapXmlSeq, s:\n", m.StringIndent()) + + b, err := m.XmlIndent("", " ") + if err != nil { + t.Fatal("err:", err) + } + fmt.Println("NewMapXmlSeq, mv.XmlIndent():\n", string(b)) + + b, err = m.XmlSeqIndent("", " ") + if err != nil { + t.Fatal("err:", err) + } + fmt.Println("NewMapXmlSeq, mv.XmlSeqIndent():\n", string(b)) +}