├── .travis.yml ├── LICENSE ├── _deprecate └── stuff.go ├── anyxml.go ├── anyxml_test.go ├── atomFeedString.xml ├── attrprefix_test.go ├── badxml_test.go ├── bom_test.go ├── bulk_test.go ├── bulkraw_test.go ├── cast_test.go ├── data_test.go ├── doc.go ├── escapechars.go ├── escapechars_test.go ├── example_test.go ├── examples ├── README ├── append.go ├── bom.go ├── books.go ├── getmetrics1.go ├── getmetrics2.go ├── getmetrics3.go ├── getmetrics4.go ├── gitissue1.go ├── gitissue2.dat ├── gitissue2.go ├── gitissue2_2.go ├── gonuts1.go ├── gonuts10.go ├── gonuts10seq.go ├── gonuts11seq.go ├── gonuts12seq.go ├── gonuts1a.go ├── gonuts2.go ├── gonuts3.go ├── gonuts4.go ├── gonuts5.go ├── gonuts5a.go ├── gonuts6.go ├── gonuts9.go ├── goofy_map.go ├── jpath.go ├── leafnodes.go ├── metrics_data.zip ├── order.go ├── partial.go ├── reddit01.go ├── reddit02.go ├── x2jcmd.go └── x2jcmd.xml ├── exists.go ├── exists_test.go ├── files.go ├── files_test.badjson ├── files_test.badxml ├── files_test.go ├── files_test.json ├── files_test.xml ├── files_test_dup.json ├── files_test_dup.xml ├── files_test_indent.json ├── files_test_indent.xml ├── global_map_prefix_test.go ├── go.mod ├── go.sum ├── gob.go ├── gob_test.go ├── isvalid_test.go ├── j2x ├── j2x.go └── j2x_test.go ├── j2x_test.go ├── json.go ├── json_test.go ├── keystolower_test.go ├── keyvalues.go ├── keyvalues2_test.go ├── keyvalues_test.go ├── largexml.xml ├── leafnode.go ├── leafnode_test.go ├── misc.go ├── misc_test.go ├── mxj.go ├── mxj_test.go ├── namespace_test.go ├── nan_test.go ├── newmap.go ├── newmap_test.go ├── readme.md ├── remove.go ├── remove_test.go ├── rename.go ├── rename_test.go ├── seqnum_test.go ├── set.go ├── set_test.go ├── setfieldsep.go ├── snakecase_test.go ├── songtext.xml ├── strict.go ├── strict_test.go ├── struct.go ├── struct_test.go ├── structvalue_test.go ├── updatevalues.go ├── updatevalues_test.go ├── x2j-wrapper ├── LICENSE ├── README ├── atomFeedString.xml ├── goofy_test.go ├── pathTestString.xml ├── reader2j.go ├── reader2j_test.go ├── songTextString.xml ├── x2j.go ├── x2j_bulk.go ├── x2j_findPath.go ├── x2j_test.go ├── x2j_test.xml ├── x2j_valuesAt.go ├── x2j_valuesFrom.go ├── x2jat_test.go ├── x2jfindPath_test.go ├── x2jpath_test.go ├── x2junmarshal_test.go ├── x2m_bulk.go ├── x2m_bulk.xml └── xml.go ├── x2j └── x2j.go ├── xml.go ├── xml2_test.go ├── xml3_test.go ├── xml_test.go ├── xmlseq.go ├── xmlseq2.go ├── xmlseq_test.go ├── xmlseq_whitespace_test.go └── xmppStream_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.x -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2021 Charles Banning . All rights reserved. 2 | 3 | The MIT License (MIT) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /_deprecate/stuff.go: -------------------------------------------------------------------------------- 1 | 2 | // ======================== newMapToXmlIndent 3 | 4 | func (mv Map) MarshalXml(rootTag ...string) ([]byte, error) { 5 | m := map[string]interface{}(mv) 6 | var err error 7 | // s := new(string) 8 | // b := new(strings.Builder) 9 | b := new(bytes.Buffer) 10 | p := new(pretty) // just a stub 11 | 12 | if len(m) == 1 && len(rootTag) == 0 { 13 | for key, value := range m { 14 | // if it an array, see if all values are map[string]interface{} 15 | // we force a new root tag if we'll end up with no key:value in the list 16 | // so: key:[string_val, bool:true] --> string_valtrue 17 | switch value.(type) { 18 | case []interface{}: 19 | for _, v := range value.([]interface{}) { 20 | switch v.(type) { 21 | case map[string]interface{}: // noop 22 | default: // anything else 23 | err = marshalMapToXmlIndent(false, b, DefaultRootTag, m, p) 24 | goto done 25 | } 26 | } 27 | } 28 | err = marshalMapToXmlIndent(false, b, key, value, p) 29 | } 30 | } else if len(rootTag) == 1 { 31 | err = marshalMapToXmlIndent(false, b, rootTag[0], m, p) 32 | } else { 33 | err = marshalMapToXmlIndent(false, b, DefaultRootTag, m, p) 34 | } 35 | done: 36 | return b.Bytes(), err 37 | } 38 | -------------------------------------------------------------------------------- /anyxml.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "bytes" 5 | "encoding/xml" 6 | "reflect" 7 | ) 8 | 9 | const ( 10 | DefaultElementTag = "element" 11 | ) 12 | 13 | // Encode arbitrary value as XML. 14 | // 15 | // Note: unmarshaling the resultant 16 | // XML may not return the original value, since tag labels may have been injected 17 | // to create the XML representation of the value. 18 | /* 19 | Encode an arbitrary JSON object. 20 | package main 21 | 22 | import ( 23 | "encoding/json" 24 | "fmt" 25 | "github.com/clbanning/mxj/v2" 26 | ) 27 | 28 | func main() { 29 | jsondata := []byte(`[ 30 | { "somekey":"somevalue" }, 31 | "string", 32 | 3.14159265, 33 | true 34 | ]`) 35 | var i interface{} 36 | err := json.Unmarshal(jsondata, &i) 37 | if err != nil { 38 | // do something 39 | } 40 | x, err := mxj.AnyXmlIndent(i, "", " ", "mydoc") 41 | if err != nil { 42 | // do something else 43 | } 44 | fmt.Println(string(x)) 45 | } 46 | 47 | output: 48 | 49 | somevalue 50 | string 51 | 3.14159265 52 | true 53 | 54 | 55 | An extreme example is available in examples/goofy_map.go. 56 | */ 57 | // Alternative values for DefaultRootTag and DefaultElementTag can be set as: 58 | // AnyXml( v, myRootTag, myElementTag). 59 | func AnyXml(v interface{}, tags ...string) ([]byte, error) { 60 | var rt, et string 61 | if len(tags) == 1 || len(tags) == 2 { 62 | rt = tags[0] 63 | } else { 64 | rt = DefaultRootTag 65 | } 66 | if len(tags) == 2 { 67 | et = tags[1] 68 | } else { 69 | et = DefaultElementTag 70 | } 71 | 72 | if v == nil { 73 | if useGoXmlEmptyElemSyntax { 74 | return []byte("<" + rt + ">"), nil 75 | } 76 | return []byte("<" + rt + "/>"), nil 77 | } 78 | if reflect.TypeOf(v).Kind() == reflect.Struct { 79 | return xml.Marshal(v) 80 | } 81 | 82 | var err error 83 | s := new(bytes.Buffer) 84 | p := new(pretty) 85 | 86 | var b []byte 87 | switch v.(type) { 88 | case []interface{}: 89 | if _, err = s.WriteString("<" + rt + ">"); err != nil { 90 | return nil, err 91 | } 92 | for _, vv := range v.([]interface{}) { 93 | switch vv.(type) { 94 | case map[string]interface{}: 95 | m := vv.(map[string]interface{}) 96 | if len(m) == 1 { 97 | for tag, val := range m { 98 | err = marshalMapToXmlIndent(false, s, tag, val, p) 99 | } 100 | } else { 101 | err = marshalMapToXmlIndent(false, s, et, vv, p) 102 | } 103 | default: 104 | err = marshalMapToXmlIndent(false, s, et, vv, p) 105 | } 106 | if err != nil { 107 | break 108 | } 109 | } 110 | if _, err = s.WriteString(""); err != nil { 111 | return nil, err 112 | } 113 | b = s.Bytes() 114 | case map[string]interface{}: 115 | m := Map(v.(map[string]interface{})) 116 | b, err = m.Xml(rt) 117 | default: 118 | err = marshalMapToXmlIndent(false, s, rt, v, p) 119 | b = s.Bytes() 120 | } 121 | 122 | return b, err 123 | } 124 | 125 | // Encode an arbitrary value as a pretty XML string. 126 | // Alternative values for DefaultRootTag and DefaultElementTag can be set as: 127 | // AnyXmlIndent( v, "", " ", myRootTag, myElementTag). 128 | func AnyXmlIndent(v interface{}, prefix, indent string, tags ...string) ([]byte, error) { 129 | var rt, et string 130 | if len(tags) == 1 || len(tags) == 2 { 131 | rt = tags[0] 132 | } else { 133 | rt = DefaultRootTag 134 | } 135 | if len(tags) == 2 { 136 | et = tags[1] 137 | } else { 138 | et = DefaultElementTag 139 | } 140 | 141 | if v == nil { 142 | if useGoXmlEmptyElemSyntax { 143 | return []byte(prefix + "<" + rt + ">"), nil 144 | } 145 | return []byte(prefix + "<" + rt + "/>"), nil 146 | } 147 | if reflect.TypeOf(v).Kind() == reflect.Struct { 148 | return xml.MarshalIndent(v, prefix, indent) 149 | } 150 | 151 | var err error 152 | s := new(bytes.Buffer) 153 | p := new(pretty) 154 | p.indent = indent 155 | p.padding = prefix 156 | 157 | var b []byte 158 | switch v.(type) { 159 | case []interface{}: 160 | if _, err = s.WriteString("<" + rt + ">\n"); err != nil { 161 | return nil, err 162 | } 163 | p.Indent() 164 | for _, vv := range v.([]interface{}) { 165 | switch vv.(type) { 166 | case map[string]interface{}: 167 | m := vv.(map[string]interface{}) 168 | if len(m) == 1 { 169 | for tag, val := range m { 170 | err = marshalMapToXmlIndent(true, s, tag, val, p) 171 | } 172 | } else { 173 | p.start = 1 // we 1 tag in 174 | err = marshalMapToXmlIndent(true, s, et, vv, p) 175 | // *s += "\n" 176 | if _, err = s.WriteString("\n"); err != nil { 177 | return nil, err 178 | } 179 | } 180 | default: 181 | p.start = 0 // in case trailing p.start = 1 182 | err = marshalMapToXmlIndent(true, s, et, vv, p) 183 | } 184 | if err != nil { 185 | break 186 | } 187 | } 188 | if _, err = s.WriteString(``); err != nil { 189 | return nil, err 190 | } 191 | b = s.Bytes() 192 | case map[string]interface{}: 193 | m := Map(v.(map[string]interface{})) 194 | b, err = m.XmlIndent(prefix, indent, rt) 195 | default: 196 | err = marshalMapToXmlIndent(true, s, rt, v, p) 197 | b = s.Bytes() 198 | } 199 | 200 | return b, err 201 | } 202 | -------------------------------------------------------------------------------- /anyxml_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestAnyXmlHeader(t *testing.T) { 10 | fmt.Println("\n---------------- anyxml_test.go ...") 11 | } 12 | 13 | var anydata = []byte(`[ 14 | { 15 | "somekey": "somevalue" 16 | }, 17 | { 18 | "somekey": "somevalue" 19 | }, 20 | { 21 | "somekey": "somevalue", 22 | "someotherkey": "someothervalue" 23 | }, 24 | "string", 25 | 3.14159265, 26 | true 27 | ]`) 28 | 29 | type MyStruct struct { 30 | Somekey string `xml:"somekey"` 31 | B float32 `xml:"floatval"` 32 | } 33 | 34 | func TestAnyXml(t *testing.T) { 35 | var i interface{} 36 | err := json.Unmarshal(anydata, &i) 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | x, err := AnyXml(i) 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | fmt.Println("[]->x:", string(x)) 45 | 46 | a := []interface{}{"try", "this", 3.14159265, true} 47 | x, err = AnyXml(a) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | fmt.Println("a->x:", string(x)) 52 | 53 | x, err = AnyXml(a, "myRootTag", "myElementTag") 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | fmt.Println("a->x:", string(x)) 58 | 59 | x, err = AnyXml(3.14159625) 60 | if err != nil { 61 | t.Fatal(err) 62 | } 63 | fmt.Println("f->x:", string(x)) 64 | 65 | s := MyStruct{"somevalue", 3.14159625} 66 | x, err = AnyXml(s) 67 | if err != nil { 68 | t.Fatal(err) 69 | } 70 | fmt.Println("s->x:", string(x)) 71 | } 72 | 73 | func TestAnyXmlIndent(t *testing.T) { 74 | var i interface{} 75 | err := json.Unmarshal(anydata, &i) 76 | if err != nil { 77 | t.Fatal(err) 78 | } 79 | x, err := AnyXmlIndent(i, "", " ") 80 | if err != nil { 81 | t.Fatal(err) 82 | } 83 | fmt.Println("[]->x:\n", string(x)) 84 | 85 | a := []interface{}{"try", "this", 3.14159265, true} 86 | x, err = AnyXmlIndent(a, "", " ") 87 | if err != nil { 88 | t.Fatal(err) 89 | } 90 | fmt.Println("a->x:\n", string(x)) 91 | 92 | x, err = AnyXmlIndent(3.14159625, "", " ") 93 | if err != nil { 94 | t.Fatal(err) 95 | } 96 | fmt.Println("f->x:\n", string(x)) 97 | 98 | x, err = AnyXmlIndent(3.14159625, "", " ", "myRootTag", "myElementTag") 99 | if err != nil { 100 | t.Fatal(err) 101 | } 102 | fmt.Println("f->x:\n", string(x)) 103 | 104 | s := MyStruct{"somevalue", 3.14159625} 105 | x, err = AnyXmlIndent(s, "", " ") 106 | if err != nil { 107 | t.Fatal(err) 108 | } 109 | fmt.Println("s->x:\n", string(x)) 110 | } 111 | 112 | 113 | func TestNilMap(t *testing.T) { 114 | XmlDefaultEmptyElemSyntax() 115 | checkval := "" 116 | xmlout, err := AnyXml(nil, "root") 117 | if err != nil { 118 | t.Fatal(err) 119 | } 120 | if string(xmlout) != checkval { 121 | fmt.Println(string(xmlout), "!=", checkval) 122 | t.Fatal() 123 | } 124 | 125 | checkval = " " 126 | xmlout, err = AnyXmlIndent(nil, " ", " ", "root") 127 | if err != nil { 128 | t.Fatal(err) 129 | } 130 | if string(xmlout) != checkval { 131 | fmt.Println(string(xmlout), "!=", checkval) 132 | t.Fatal() 133 | } 134 | 135 | // use Go XML marshal syntax for empty element" 136 | XmlGoEmptyElemSyntax() 137 | checkval = "" 138 | xmlout, err = AnyXml(nil, "root") 139 | if err != nil { 140 | t.Fatal(err) 141 | } 142 | if string(xmlout) != checkval { 143 | fmt.Println(string(xmlout), "!=", checkval) 144 | t.Fatal() 145 | } 146 | 147 | checkval = ` ` 148 | xmlout, err = AnyXmlIndent(nil, " ", " ", "root") 149 | if err != nil { 150 | t.Fatal(err) 151 | } 152 | if string(xmlout) != checkval { 153 | fmt.Println(string(xmlout), "!=", checkval) 154 | t.Fatal() 155 | } 156 | XmlDefaultEmptyElemSyntax() 157 | } 158 | 159 | func TestNilValue(t *testing.T) { 160 | val := map[string]interface{}{"toplevel": nil} 161 | checkval := "" 162 | 163 | XmlDefaultEmptyElemSyntax() 164 | xmlout, err := AnyXml(val, "root") 165 | if err != nil { 166 | t.Fatal(err) 167 | } 168 | if string(xmlout) != checkval { 169 | fmt.Println(string(xmlout), "!=", checkval) 170 | t.Fatal() 171 | } 172 | 173 | checkval = ` 174 | 175 | ` 176 | xmlout, err = AnyXmlIndent(val, " ", " ", "root") 177 | if err != nil { 178 | t.Fatal(err) 179 | } 180 | if string(xmlout) != checkval { 181 | fmt.Println(string(xmlout), "!=", checkval) 182 | t.Fatal() 183 | } 184 | 185 | XmlGoEmptyElemSyntax() 186 | checkval = "" 187 | xmlout, err = AnyXml(val, "root") 188 | if err != nil { 189 | t.Fatal(err) 190 | } 191 | if string(xmlout) != checkval { 192 | fmt.Println(string(xmlout), "!=", checkval) 193 | t.Fatal() 194 | } 195 | 196 | checkval = ` 197 | 198 | ` 199 | xmlout, err = AnyXmlIndent(val, " ", " ", "root") 200 | if err != nil { 201 | t.Fatal(err) 202 | } 203 | if string(xmlout) != checkval { 204 | fmt.Println(string(xmlout), "!=", checkval) 205 | t.Fatal() 206 | } 207 | XmlDefaultEmptyElemSyntax() 208 | } 209 | -------------------------------------------------------------------------------- /atomFeedString.xml: -------------------------------------------------------------------------------- 1 | 2 | Code Review - My issueshttp://codereview.appspot.com/rietveld<>rietveld: an attempt at pubsubhubbub 3 | 2009-10-04T01:35:58+00:00email-address-removedurn:md5:134d9179c41f806be79b3a5f7877d19a 4 | An attempt at adding pubsubhubbub support to Rietveld. 5 | http://code.google.com/p/pubsubhubbub 6 | http://code.google.com/p/rietveld/issues/detail?id=155 7 | 8 | The server side of the protocol is trivial: 9 | 1. add a &lt;link rel=&quot;hub&quot; href=&quot;hub-server&quot;&gt; tag to all 10 | feeds that will be pubsubhubbubbed. 11 | 2. every time one of those feeds changes, tell the hub 12 | with a simple POST request. 13 | 14 | I have tested this by adding debug prints to a local hub 15 | server and checking that the server got the right publish 16 | requests. 17 | 18 | I can&#39;t quite get the server to work, but I think the bug 19 | is not in my code. I think that the server expects to be 20 | able to grab the feed and see the feed&#39;s actual URL in 21 | the link rel=&quot;self&quot;, but the default value for that drops 22 | the :port from the URL, and I cannot for the life of me 23 | figure out how to get the Atom generator deep inside 24 | django not to do that, or even where it is doing that, 25 | or even what code is running to generate the Atom feed. 26 | (I thought I knew but I added some assert False statements 27 | and it kept running!) 28 | 29 | Ignoring that particular problem, I would appreciate 30 | feedback on the right way to get the two values at 31 | the top of feeds.py marked NOTE(rsc). 32 | 33 | 34 | rietveld: correct tab handling 35 | 2009-10-03T23:02:17+00:00email-address-removedurn:md5:0a2a4f19bb815101f0ba2904aed7c35a 36 | This fixes the buggy tab rendering that can be seen at 37 | http://codereview.appspot.com/116075/diff/1/2 38 | 39 | The fundamental problem was that the tab code was 40 | not being told what column the text began in, so it 41 | didn&#39;t know where to put the tab stops. Another problem 42 | was that some of the code assumed that string byte 43 | offsets were the same as column offsets, which is only 44 | true if there are no tabs. 45 | 46 | In the process of fixing this, I cleaned up the arguments 47 | to Fold and ExpandTabs and renamed them Break and 48 | _ExpandTabs so that I could be sure that I found all the 49 | call sites. I also wanted to verify that ExpandTabs was 50 | not being used from outside intra_region_diff.py. 51 | 52 | 53 | ` 54 | 55 | -------------------------------------------------------------------------------- /attrprefix_test.go: -------------------------------------------------------------------------------- 1 | // attrprefix_test.go - change attrPrefix var 2 | 3 | package mxj 4 | 5 | import ( 6 | "fmt" 7 | "testing" 8 | ) 9 | 10 | var data = []byte(` 11 | 12 | a test 13 | a test 14 | 15 | `) 16 | 17 | func TestPrefixDefault(t *testing.T) { 18 | fmt.Println("----------------- TestPrefixDefault ...") 19 | m, err := NewMapXml(data) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | vals, err := m.ValuesForKey("-attr1") 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | if len(vals) != 2 { 28 | t.Fatal("didn't get 2 -attr1 vals", len(vals)) 29 | } 30 | vals, err = m.ValuesForKey("-attr2") 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | if len(vals) != 2 { 35 | t.Fatal("didn't get 2 -attr2 vals", len(vals)) 36 | } 37 | } 38 | 39 | func TestPrefixNoHyphen(t *testing.T) { 40 | fmt.Println("----------------- TestPrefixNoHyphen ...") 41 | PrependAttrWithHyphen(false) 42 | m, err := NewMapXml(data) 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | vals, err := m.ValuesForKey("attr1") 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | if len(vals) != 2 { 51 | t.Fatal("didn't get 2 attr1 vals", len(vals)) 52 | } 53 | vals, err = m.ValuesForKey("attr2") 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | if len(vals) != 2 { 58 | t.Fatal("didn't get 2 attr2 vals", len(vals)) 59 | } 60 | } 61 | 62 | func TestPrefixUnderscore(t *testing.T) { 63 | fmt.Println("----------------- TestPrefixUnderscore ...") 64 | SetAttrPrefix("_") 65 | m, err := NewMapXml(data) 66 | if err != nil { 67 | t.Fatal(err) 68 | } 69 | vals, err := m.ValuesForKey("_attr1") 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | if len(vals) != 2 { 74 | t.Fatal("didn't get 2 _attr1 vals", len(vals)) 75 | } 76 | vals, err = m.ValuesForKey("_attr2") 77 | if err != nil { 78 | t.Fatal(err) 79 | } 80 | if len(vals) != 2 { 81 | t.Fatal("didn't get 2 _attr2 vals", len(vals)) 82 | } 83 | } 84 | 85 | func TestPrefixAt(t *testing.T) { 86 | fmt.Println("----------------- TestPrefixAt ...") 87 | SetAttrPrefix("@") 88 | m, err := NewMapXml(data) 89 | if err != nil { 90 | t.Fatal(err) 91 | } 92 | vals, err := m.ValuesForKey("@attr1") 93 | if err != nil { 94 | t.Fatal(err) 95 | } 96 | if len(vals) != 2 { 97 | t.Fatal("didn't get 2 @attr1 vals", len(vals)) 98 | } 99 | vals, err = m.ValuesForKey("@attr2") 100 | if err != nil { 101 | t.Fatal(err) 102 | } 103 | if len(vals) != 2 { 104 | t.Fatal("didn't get 2 @attr2 vals", len(vals)) 105 | } 106 | } 107 | 108 | func TestMarshalPrefixDefault(t *testing.T) { 109 | fmt.Println("----------------- TestMarshalPrefixDefault ...") 110 | m, err := NewMapXml(data) 111 | if err != nil { 112 | t.Fatal(err) 113 | } 114 | x, err := m.XmlIndent("", " ") 115 | if err != nil { 116 | t.Fatal(err) 117 | } 118 | fmt.Println(string(x)) 119 | } 120 | 121 | func TestMarshalPrefixNoHyphen(t *testing.T) { 122 | fmt.Println("----------------- TestMarshalPrefixNoHyphen ...") 123 | // 2021.03.09 - per issue #90, no produces a complex element 124 | PrependAttrWithHyphen(false) 125 | m, err := NewMapXml(data) 126 | if err != nil { 127 | t.Fatal(err) 128 | } 129 | x, err := m.XmlIndent("", " ") 130 | if err != nil { 131 | t.Fatal("error not reported for invalid key label") 132 | } 133 | fmt.Println("x:", string(x)) 134 | } 135 | 136 | func TestMarshalPrefixUnderscore(t *testing.T) { 137 | fmt.Println("----------------- TestMarshalPrefixUnderscore ...") 138 | SetAttrPrefix("_") 139 | m, err := NewMapXml(data) 140 | if err != nil { 141 | t.Fatal(err) 142 | } 143 | x, err := m.XmlIndent("", " ") 144 | if err != nil { 145 | t.Fatal(err) 146 | } 147 | fmt.Println(string(x)) 148 | } 149 | 150 | -------------------------------------------------------------------------------- /badxml_test.go: -------------------------------------------------------------------------------- 1 | // trying to recreate a panic 2 | 3 | package mxj 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "testing" 9 | ) 10 | 11 | var baddata = []byte(` 12 | something strange 13 | 14 | 15 | 16 | 17 | http://www.something.com 18 | Some description goes here. 19 | 20 | 21 | `) 22 | 23 | func TestBadXml(t *testing.T) { 24 | fmt.Println("\n---------------- badxml_test.go") 25 | fmt.Println("TestBadXml ...") 26 | m, err := NewMapXml(baddata) 27 | if err != nil { 28 | t.Fatalf("err: didn't find xml.StartElement") 29 | } 30 | fmt.Printf("m: %v\n", m) 31 | j, _ := m.Xml() 32 | fmt.Println("m:", string(j)) 33 | } 34 | 35 | func TestBadXmlSeq(t *testing.T) { 36 | fmt.Println("TestBadXmlSeq ...") 37 | m, err := NewMapXmlSeq(baddata) 38 | if err != nil { 39 | t.Fatalf("err: didn't find xmlStartElement") 40 | } 41 | fmt.Printf("m: %v\n", m) 42 | j, _ := m.Xml() 43 | fmt.Println("m:", string(j)) 44 | } 45 | 46 | func TestBadXmlReader(t *testing.T) { 47 | fmt.Println("TestBadXmlReader ...") 48 | r := bytes.NewReader(baddata) 49 | m, err := NewMapXmlReader(r) 50 | if err != nil { 51 | t.Fatalf("err: didn't find xml.StartElement") 52 | } 53 | fmt.Printf("m: %v\n", m) 54 | j, _ := m.Xml() 55 | fmt.Println("m:", string(j)) 56 | } 57 | 58 | func TestBadXmlSeqReader(t *testing.T) { 59 | fmt.Println("TestBadXmlSeqReader ...") 60 | r := bytes.NewReader(baddata) 61 | m, err := NewMapXmlSeqReader(r) 62 | if err != nil { 63 | t.Fatalf("err: didn't find xmlStartElement") 64 | } 65 | fmt.Printf("m: %v\n", m) 66 | j, _ := m.Xml() 67 | fmt.Println("m:", string(j)) 68 | } 69 | -------------------------------------------------------------------------------- /bom_test.go: -------------------------------------------------------------------------------- 1 | // bomxml.go - test handling Byte-Order-Mark headers 2 | 3 | package mxj 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "io" 9 | "testing" 10 | ) 11 | 12 | // Check for Byte-Order-Mark header. 13 | var boms = [][]byte{ 14 | {'\xef', '\xbb', '\xbf'}, 15 | {'\xfe', '\xff'}, 16 | {'\xff', '\xfe'}, 17 | {'\x00', '\x00', '\xfe', '\xff'}, 18 | {'\xff', '\xfe', '\x00', '\x00'}, 19 | } 20 | 21 | func TestBom(t *testing.T) { 22 | fmt.Println("\n--------------- bom_test.go") 23 | fmt.Println("TestBom ...") 24 | 25 | // use just UTF-8 BOM ... no alternative CharSetReader 26 | if _, err := NewMapXml(boms[0]); err != io.EOF { 27 | t.Fatalf("NewMapXml err; %v\n", err) 28 | } 29 | 30 | if _, err := NewMapXmlSeq(boms[0]); err != io.EOF { 31 | t.Fatalf("NewMapXmlSeq err: %v\n", err) 32 | } 33 | } 34 | 35 | var bomdata = append(boms[0], []byte(` 36 | 37 | 38 | 39 | http://www.something.com 40 | Some description goes here. 41 | 42 | `)...) 43 | 44 | func TestBomData(t *testing.T) { 45 | fmt.Println("TestBomData ...") 46 | m, err := NewMapXml(bomdata) 47 | if err != nil { 48 | t.Fatalf("err: didn't find xml.StartElement") 49 | } 50 | fmt.Printf("m: %v\n", m) 51 | j, _ := m.Xml() 52 | fmt.Println("m:", string(j)) 53 | } 54 | 55 | func TestBomDataSeq(t *testing.T) { 56 | fmt.Println("TestBomDataSeq ...") 57 | m, err := NewMapXmlSeq(bomdata) 58 | if err != nil { 59 | t.Fatalf("err: didn't find xml.StartElement") 60 | } 61 | fmt.Printf("m: %v\n", m) 62 | j, _ := m.Xml() 63 | fmt.Println("m:", string(j)) 64 | } 65 | 66 | func TestBomDataReader(t *testing.T) { 67 | fmt.Println("TestBomDataReader ...") 68 | r := bytes.NewReader(bomdata) 69 | m, err := NewMapXmlReader(r) 70 | if err != nil { 71 | t.Fatalf("err: didn't find xml.StartElement") 72 | } 73 | fmt.Printf("m: %v\n", m) 74 | j, _ := m.Xml() 75 | fmt.Println("m:", string(j)) 76 | } 77 | 78 | func TestBomDataSeqReader(t *testing.T) { 79 | fmt.Println("TestBomDataSeqReader ...") 80 | r := bytes.NewReader(bomdata) 81 | m, err := NewMapXmlSeqReader(r) 82 | if err != nil { 83 | t.Fatalf("err: didn't find xml.StartElement") 84 | } 85 | fmt.Printf("m: %v\n", m) 86 | j, _ := m.Xml() 87 | fmt.Println("m:", string(j)) 88 | } 89 | -------------------------------------------------------------------------------- /bulk_test.go: -------------------------------------------------------------------------------- 1 | // bulk_test.go - uses Handler and Writer functions to process some streams as a demo. 2 | 3 | package mxj 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "testing" 9 | ) 10 | 11 | func TestBulkHeader(t *testing.T) { 12 | fmt.Println("\n---------------- bulk_test.go ...") 13 | } 14 | 15 | var jsonWriter = new(bytes.Buffer) 16 | var xmlWriter = new(bytes.Buffer) 17 | 18 | var jsonErrLog = new(bytes.Buffer) 19 | var xmlErrLog = new(bytes.Buffer) 20 | 21 | func TestXmlReader(t *testing.T) { 22 | // create Reader for xmldata 23 | xmlReader := bytes.NewReader(xmldata) 24 | 25 | // read XML from Readerand pass Map value with the raw XML to handler 26 | err := HandleXmlReader(xmlReader, bxmaphandler, bxerrhandler) 27 | if err != nil { 28 | t.Fatal("err:", err.Error()) 29 | } 30 | 31 | // get the JSON 32 | j := make([]byte, jsonWriter.Len()) 33 | _, _ = jsonWriter.Read(j) 34 | 35 | // get the errors 36 | e := make([]byte, xmlErrLog.Len()) 37 | _, _ = xmlErrLog.Read(e) 38 | 39 | // print the input 40 | fmt.Println("XmlReader, xmldata:\n", string(xmldata)) 41 | // print the result 42 | fmt.Println("XmlReader, result :\n", string(j)) 43 | // print the errors 44 | fmt.Println("XmlReader, errors :\n", string(e)) 45 | } 46 | 47 | func bxmaphandler(m Map) bool { 48 | j, err := m.JsonIndent("", " ", true) 49 | if err != nil { 50 | return false 51 | } 52 | 53 | _, _ = jsonWriter.Write(j) 54 | // put in a NL to pretty up printing the Writer 55 | _, _ = jsonWriter.Write([]byte("\n")) 56 | return true 57 | } 58 | 59 | func bxerrhandler(err error) bool { 60 | // write errors to file 61 | _, _ = xmlErrLog.Write([]byte(err.Error())) 62 | _, _ = xmlErrLog.Write([]byte("\n")) // pretty up 63 | return true 64 | } 65 | 66 | func TestJsonReader(t *testing.T) { 67 | jsonReader := bytes.NewReader(jsondata) 68 | 69 | // read all the JSON 70 | err := HandleJsonReader(jsonReader, bjmaphandler, bjerrhandler) 71 | if err != nil { 72 | t.Fatal("err:", err.Error()) 73 | } 74 | 75 | // get the XML 76 | x := make([]byte, xmlWriter.Len()) 77 | _, _ = xmlWriter.Read(x) 78 | 79 | // get the errors 80 | e := make([]byte, jsonErrLog.Len()) 81 | _, _ = jsonErrLog.Read(e) 82 | 83 | // print the input 84 | fmt.Println("JsonReader, jsondata:\n", string(jsondata)) 85 | // print the result 86 | fmt.Println("JsonReader, result :\n", string(x)) 87 | // print the errors 88 | fmt.Println("JsonReader, errors :\n", string(e)) 89 | } 90 | 91 | func bjmaphandler(m Map) bool { 92 | x, err := m.XmlIndent(" ", " ") 93 | if err != nil { 94 | return false 95 | } 96 | _, _ = xmlWriter.Write(x) 97 | // put in a NL to pretty up printing the Writer 98 | _, _ = xmlWriter.Write([]byte("\n")) 99 | return true 100 | } 101 | 102 | func bjerrhandler(err error) bool { 103 | // write errors to file 104 | _, _ = jsonErrLog.Write([]byte(err.Error())) 105 | _, _ = jsonErrLog.Write([]byte("\n")) // pretty up 106 | return true 107 | } 108 | -------------------------------------------------------------------------------- /bulkraw_test.go: -------------------------------------------------------------------------------- 1 | // bulk_test.go - uses Handler and Writer functions to process some streams as a demo. 2 | 3 | package mxj 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "testing" 9 | ) 10 | 11 | func TestBulkRawHeader(t *testing.T) { 12 | fmt.Println("\n---------------- bulkraw_test.go ...") 13 | } 14 | 15 | // use data from bulk_test.go 16 | 17 | var jsonWriterRaw = new(bytes.Buffer) 18 | var xmlWriterRaw = new(bytes.Buffer) 19 | 20 | var jsonErrLogRaw = new(bytes.Buffer) 21 | var xmlErrLogRaw = new(bytes.Buffer) 22 | 23 | func TestXmlReaderRaw(t *testing.T) { 24 | // create Reader for xmldata 25 | xmlReader := bytes.NewReader(xmldata) 26 | 27 | // read XML from Reader and pass Map value with the raw XML to handler 28 | err := HandleXmlReaderRaw(xmlReader, bxmaphandlerRaw, bxerrhandlerRaw) 29 | if err != nil { 30 | t.Fatal("err:", err.Error()) 31 | } 32 | 33 | // get the JSON 34 | j := make([]byte, jsonWriterRaw.Len()) 35 | _, _ = jsonWriterRaw.Read(j) 36 | 37 | // get the errors 38 | e := make([]byte, xmlErrLogRaw.Len()) 39 | _, _ = xmlErrLogRaw.Read(e) 40 | 41 | // print the input 42 | fmt.Println("XmlReaderRaw, xmldata:\n", string(xmldata)) 43 | // print the result 44 | fmt.Println("XmlReaderRaw, result :\n", string(j)) 45 | // print the errors 46 | fmt.Println("XmlReaderRaw, errors :\n", string(e)) 47 | } 48 | 49 | func bxmaphandlerRaw(m Map, raw []byte) bool { 50 | j, err := m.JsonIndent("", " ", true) 51 | if err != nil { 52 | return false 53 | } 54 | 55 | _, _ = jsonWriterRaw.Write(j) 56 | // put in a NL to pretty up printing the Writer 57 | _, _ = jsonWriterRaw.Write([]byte("\n")) 58 | return true 59 | } 60 | 61 | func bxerrhandlerRaw(err error, raw []byte) bool { 62 | // write errors to file 63 | _, _ = xmlErrLogRaw.Write([]byte(err.Error())) 64 | _, _ = xmlErrLogRaw.Write([]byte("\n")) // pretty up 65 | _, _ = xmlErrLogRaw.Write(raw) 66 | _, _ = xmlErrLogRaw.Write([]byte("\n")) // pretty up 67 | return true 68 | } 69 | 70 | func TestJsonReaderRaw(t *testing.T) { 71 | jsonReader := bytes.NewReader(jsondata) 72 | 73 | // read all the JSON 74 | err := HandleJsonReaderRaw(jsonReader, bjmaphandlerRaw, bjerrhandlerRaw) 75 | if err != nil { 76 | t.Fatal("err:", err.Error()) 77 | } 78 | 79 | // get the XML 80 | x := make([]byte, xmlWriterRaw.Len()) 81 | _, _ = xmlWriterRaw.Read(x) 82 | 83 | // get the errors 84 | e := make([]byte, jsonErrLogRaw.Len()) 85 | _, _ = jsonErrLogRaw.Read(e) 86 | 87 | // print the input 88 | fmt.Println("JsonReaderRaw, jsondata:\n", string(jsondata)) 89 | // print the result 90 | fmt.Println("JsonReaderRaw, result :\n", string(x)) 91 | // print the errors 92 | fmt.Println("JsonReaderRaw, errors :\n", string(e)) 93 | } 94 | 95 | func bjmaphandlerRaw(m Map, raw []byte) bool { 96 | x, err := m.XmlIndent(" ", " ") 97 | if err != nil { 98 | return false 99 | } 100 | _, _ = xmlWriterRaw.Write(x) 101 | // put in a NL to pretty up printing the Writer 102 | _, _ = xmlWriterRaw.Write([]byte("\n")) 103 | return true 104 | } 105 | 106 | func bjerrhandlerRaw(err error, raw []byte) bool { 107 | // write errors to file 108 | _, _ = jsonErrLogRaw.Write([]byte(err.Error())) 109 | _, _ = jsonErrLogRaw.Write([]byte("\n")) // pretty up, Error() from json.Unmarshal !NL 110 | _, _ = jsonErrLogRaw.Write(raw) 111 | _, _ = jsonErrLogRaw.Write([]byte("\n")) // pretty up 112 | return true 113 | } 114 | -------------------------------------------------------------------------------- /cast_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | var castdata = []byte(` 9 | string 10 | 3.14159625 11 | 2019 12 | 13 | true 14 | FALSE 15 | T 16 | f 17 | `) 18 | 19 | func TestHeader(t *testing.T) { 20 | fmt.Println("\ncast_test.go ----------") 21 | } 22 | 23 | func TestCastDefault(t *testing.T) { 24 | fmt.Println("------------ TestCastDefault ...") 25 | m, err := NewMapXml(castdata) 26 | if err != nil { 27 | t.Fatal(err.Error()) 28 | } 29 | fmt.Printf("%#v\n", m) 30 | } 31 | 32 | func TestCastTrue(t *testing.T) { 33 | fmt.Println("------------ TestCastTrue ...") 34 | m, _ := NewMapXml(castdata, true) 35 | fmt.Printf("%#v\n", m) 36 | } 37 | 38 | func TestSetCheckTagToSkipFunc(t *testing.T) { 39 | fmt.Println("------------ TestSetCheckTagToSkipFunc ...") 40 | fn := func(tag string) bool { 41 | list := []string{"int","false"} 42 | for _, v := range list { 43 | if v == tag { 44 | return true 45 | } 46 | } 47 | return false 48 | } 49 | SetCheckTagToSkipFunc(fn) 50 | 51 | m, err := NewMapXml(castdata, true) 52 | if err != nil { 53 | t.Fatal(err.Error()) 54 | } 55 | fmt.Printf("%#v\n", m) 56 | } 57 | 58 | func TestCastValuesToFloat(t *testing.T) { 59 | fmt.Println("------------ TestCastValuesToFloat(false) ...") 60 | CastValuesToFloat(false) 61 | defer CastValuesToFloat(true) 62 | 63 | m, err := NewMapXml(castdata, true) 64 | if err != nil { 65 | t.Fatal(err.Error()) 66 | } 67 | fmt.Printf("%#v\n", m) 68 | } 69 | 70 | func TestCastValuesToBool(t *testing.T) { 71 | fmt.Println("------------ TestCastValuesToBool(false) ...") 72 | CastValuesToBool(false) 73 | defer CastValuesToBool(true) 74 | 75 | m, err := NewMapXml(castdata, true) 76 | if err != nil { 77 | t.Fatal(err.Error()) 78 | } 79 | fmt.Printf("%#v\n", m) 80 | } 81 | -------------------------------------------------------------------------------- /data_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | var xmldata = []byte(` 4 | 5 | William H. Gaddis 6 | The Recognitions 7 | One of the seminal American novels of the 20th century. 8 | 9 | 10 | William H. Gaddis 11 | JR 12 | Won the National Book Award. 13 | 14 | 15 | Austin Tappan Wright 16 | Islandia 17 | An example of earlier 20th century American utopian fiction. 18 | 19 | 20 | John Hawkes 21 | The Beetle Leg 22 | A lyrical novel about the construction of Ft. Peck Dam in Montana. 23 | 24 | 25 | 26 | T.E. 27 | Porter 28 | 29 | King's Day 30 | A magical novella. 31 | `) 32 | 33 | var jsondata = []byte(` 34 | {"book":{"author":"William H. Gaddis","review":"One of the great seminal American novels of the 20th century.","title":"The Recognitions"}} 35 | {"book":{"author":"Austin Tappan Wright","review":"An example of earlier 20th century American utopian fiction.","title":"Islandia"}} 36 | {"book":{"author":"John Hawkes","review":"A lyrical novel about the construction of Ft. Peck Dam in Montana.","title":"The Beetle Leg"}} 37 | {"book":{"author":{"first_name":"T.E.","last_name":"Porter"},"review":"A magical novella.","title":"King's Day"}} 38 | { "here":"we", "put":"in", "an":error }`) 39 | -------------------------------------------------------------------------------- /escapechars.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Charles Banning. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file 4 | 5 | package mxj 6 | 7 | import ( 8 | "bytes" 9 | ) 10 | 11 | var xmlEscapeChars bool 12 | 13 | // XMLEscapeChars(true) forces escaping invalid characters in attribute and element values. 14 | // NOTE: this is brute force with NO interrogation of '&' being escaped already; if it is 15 | // then '&' will be re-escaped as '&amp;'. 16 | // 17 | /* 18 | The values are: 19 | " " 20 | ' ' 21 | < < 22 | > > 23 | & & 24 | */ 25 | // 26 | // Note: if XMLEscapeCharsDecoder(true) has been called - or the default, 'false,' value 27 | // has been toggled to 'true' - then XMLEscapeChars(true) is ignored. If XMLEscapeChars(true) 28 | // has already been called before XMLEscapeCharsDecoder(true), XMLEscapeChars(false) is called 29 | // to turn escape encoding on mv.Xml, etc., to prevent double escaping ampersands, '&'. 30 | func XMLEscapeChars(b ...bool) { 31 | var bb bool 32 | if len(b) == 0 { 33 | bb = !xmlEscapeChars 34 | } else { 35 | bb = b[0] 36 | } 37 | if bb == true && xmlEscapeCharsDecoder == false { 38 | xmlEscapeChars = true 39 | } else { 40 | xmlEscapeChars = false 41 | } 42 | } 43 | 44 | // Scan for '&' first, since 's' may contain "&" that is parsed to "&amp;" 45 | // - or "<" that is parsed to "&lt;". 46 | var escapechars = [][2][]byte{ 47 | {[]byte(`&`), []byte(`&`)}, 48 | {[]byte(`<`), []byte(`<`)}, 49 | {[]byte(`>`), []byte(`>`)}, 50 | {[]byte(`"`), []byte(`"`)}, 51 | {[]byte(`'`), []byte(`'`)}, 52 | } 53 | 54 | func escapeChars(s string) string { 55 | if len(s) == 0 { 56 | return s 57 | } 58 | 59 | b := []byte(s) 60 | for _, v := range escapechars { 61 | n := bytes.Count(b, v[0]) 62 | if n == 0 { 63 | continue 64 | } 65 | b = bytes.Replace(b, v[0], v[1], n) 66 | } 67 | return string(b) 68 | } 69 | 70 | // per issue #84, escape CharData values from xml.Decoder 71 | 72 | var xmlEscapeCharsDecoder bool 73 | 74 | // XMLEscapeCharsDecoder(b ...bool) escapes XML characters in xml.CharData values 75 | // returned by Decoder.Token. Thus, the internal Map values will contain escaped 76 | // values, and you do not need to set XMLEscapeChars for proper encoding. 77 | // 78 | // By default, the Map values have the non-escaped values returned by Decoder.Token. 79 | // XMLEscapeCharsDecoder(true) - or, XMLEscapeCharsDecoder() - will toggle escape 80 | // encoding 'on.' 81 | // 82 | // Note: if XMLEscapeCharDecoder(true) is call then XMLEscapeChars(false) is 83 | // called to prevent re-escaping the values on encoding using mv.Xml, etc. 84 | func XMLEscapeCharsDecoder(b ...bool) { 85 | if len(b) == 0 { 86 | xmlEscapeCharsDecoder = !xmlEscapeCharsDecoder 87 | } else { 88 | xmlEscapeCharsDecoder = b[0] 89 | } 90 | if xmlEscapeCharsDecoder == true && xmlEscapeChars == true { 91 | xmlEscapeChars = false 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /escapechars_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | var s = `"'<>&` 9 | 10 | func TestEscapeChars(t *testing.T) { 11 | fmt.Println("\n================== TestEscapeChars") 12 | 13 | ss := escapeChars(s) 14 | 15 | if ss != `"'<>&` { 16 | t.Fatal(s, ":", ss) 17 | } 18 | 19 | fmt.Println(" s:", s) 20 | fmt.Println("ss:", ss) 21 | } 22 | 23 | func TestXMLEscapeChars(t *testing.T) { 24 | fmt.Println("================== TestXMLEscapeChars") 25 | 26 | XMLEscapeChars(true) 27 | defer XMLEscapeChars(false) 28 | 29 | m := map[string]interface{}{"mychars":s} 30 | 31 | x, err := AnyXmlIndent(s, "", " ") 32 | if err != nil { 33 | t.Fatal(err) 34 | } 35 | fmt.Println("s:", string(x)) 36 | 37 | x, err = AnyXmlIndent(m, "", " ") 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | fmt.Println("m:", string(x)) 42 | } 43 | 44 | func TestXMLEscapeChars2(t *testing.T) { 45 | fmt.Println("================== TestXMLEscapeChars2") 46 | 47 | XMLEscapeChars(true) 48 | defer XMLEscapeChars(false) 49 | 50 | ss := []byte(`"'<>&`) 51 | fmt.Println(string(ss)) 52 | 53 | mv, err := NewMapXml(ss) 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | fmt.Printf("%v\n", mv) 58 | 59 | x, err := mv.XmlIndent("", " ") 60 | if err != nil { 61 | t.Fatal(err) 62 | } 63 | fmt.Println("mv:", string(x)) 64 | } 65 | 66 | func TestXMLSeqEscapeChars(t *testing.T) { 67 | fmt.Println("================== TestXMLSeqEscapeChars") 68 | data := []byte(` 69 | 70 | >0-2y 71 | `) 72 | fmt.Println("data:", string(data)) 73 | 74 | m, err := NewMapXmlSeq(data) 75 | if err != nil { 76 | t.Fatal(err) 77 | } 78 | fmt.Printf("m: %v\n", m) 79 | 80 | XMLEscapeChars(true) 81 | defer XMLEscapeChars(false) 82 | 83 | x, err := m.XmlIndent("", " ") 84 | if err != nil { 85 | t.Fatal(err) 86 | } 87 | fmt.Println("m:", string(x)) 88 | } 89 | 90 | func TestXMLSeqEscapeChars2(t *testing.T) { 91 | fmt.Println("================== TestXMLSeqEscapeChars2") 92 | data := []byte(` 93 | 94 | >0-2y 95 | <10-15 96 | `) 97 | fmt.Println("data:", string(data)) 98 | 99 | m, err := NewMapXmlSeq(data) 100 | if err != nil { 101 | t.Fatal(err) 102 | } 103 | fmt.Printf("m: %v\n", m) 104 | 105 | XMLEscapeChars(true) 106 | defer XMLEscapeChars(false) 107 | 108 | x, err := m.XmlIndent("", " ") 109 | if err != nil { 110 | t.Fatal(err) 111 | } 112 | fmt.Println("m:", string(x)) 113 | } 114 | -------------------------------------------------------------------------------- /examples/README: -------------------------------------------------------------------------------- 1 | Recent additions from gonuts issues: 2 | 3 | - append.go illustrates turning a sub-element into a list (or appending to a list) 4 | - order.go illustrates working with a list of mixed tags and preserving their sequence 5 | 6 | 7 | Examples of using ValuesFromTagPath(). 8 | 9 | A number of interesting examples have shown up in the gonuts discussion group 10 | that could be handled - after a fashion - using the ValuesFromTagPath() function. 11 | 12 | gonuts1.go - 13 | 14 | Here we see that the message stream has a problem with multiple tag spellings, 15 | though the message structure remains constant. In this example we 'anonymize' 16 | the tag for the variant spellings. 17 | values := m.ValuesForPath("data.*) 18 | where '*' is any possible spelling - "netid" or "idnet" 19 | and the result is a list with 1 member of map[string]interface{} type. 20 | 21 | Once we've retrieved the Map, we can parse it using the known keys - "disable", 22 | "text1" and "word1". 23 | 24 | 25 | gonuts1a.go - (03-mar-14) 26 | 27 | Here we just permute the tag labels using m.NewMap() to make all the messages 28 | consistent. Then they can be decoded into a single structure definition. 29 | 30 | 31 | gonuts2.go - 32 | 33 | This is an interesting case where there was a need to handle messages with lists 34 | of "ClaimStatusCodeRecord" entries as well as messages with NONE. (Here we see 35 | some of the vagaries of dealing with mixed messages that are verging on becoming 36 | anonymous.) 37 | 38 | msg1 - the message with two ClaimStatusCodeRecord entries 39 | msg2 - the message with one ClaimStatusCodeRecord entry 40 | msg3 - the message with NO ClaimStatusCodeRecord entries 41 | 42 | ValuesForPath options: 43 | 44 | path == "Envelope.Body.GetClaimStatusCodesResponse.GetClaimStatusCodesResult.ClaimStatusCodeRecord" 45 | for msg == msg1: 46 | returns: a list - []interface{} - with two values of map[string]interface{} type 47 | for msg == msg2: 48 | returns: a list - []interface{} - with one map[string]interface{} type 49 | for msg == msg3: 50 | returns 'nil' - no values 51 | 52 | path == "*.*.*.*.*" 53 | for msg == msg1: 54 | returns: a list - []interface{} - with two values of map[string]interface{} type 55 | 56 | path == "*.*.*.*.*.Description 57 | for msg == msg1: 58 | returns: a list - []interface{} - with two values of string type, the individual 59 | values from parsing the two map[string]interface{} values where key=="Description" 60 | 61 | path == "*.*.*.*.*.*" 62 | for msg == msg1: 63 | returns: a list - []interface{} - with six values of string type, the individual 64 | values from parsing all keys in the two map[string]interface{} values 65 | 66 | Think of the wildcard character "*" as anonymizing the tag in the position of the path where 67 | it occurs. The books.go example has a range of use cases. 68 | 69 | 70 | gonuts3.go - 71 | 72 | Uses the ValuesForKey method to extract a list of image "src" file names that are encoded as 73 | attribute values. 74 | 75 | 76 | gonuts4.go - 77 | 78 | Here we use the ValuesForPath to extract attribute values for country names. The attribute 79 | is included in the 'path' argument by prepending it with a hyphen: ""doc.some_tag.geoInfo.country.-name". 80 | 81 | 82 | gonuts5.go (10-mar-14) - 83 | 84 | Extract a node of values using ValuesForPath based on name="list3-1-1-1". Then get the values 85 | for the 'int' entries based on attribute 'name' values - mv.ValuesForKey("int", "-name:"+n). 86 | 87 | gonuts5a.go (10-mar-14) - 88 | 89 | Extract a node of values using ValuesForPath based on name="list3-1-1-1". Then get the values 90 | for the 'int' entries based on attribute 'name' values - mv.ValuesForKey("*", "-name:"+n). 91 | (Same as gonuts5.go but with wildcarded key value, since we're matching elements on subkey.) 92 | 93 | 94 | EAT YOUR OWN DOG FOOD ... 95 | 96 | I needed to convert a large (14.9 MB) XML data set from an Eclipse metrics report on an 97 | application that had 355,100 lines of code in 211 packages into CSV data sets. The report 98 | included application-, package-, class- and method-level metrics reported in an element, 99 | "Value", with varying attributes. 100 | 101 | 102 | 103 | 104 | 105 | In addition, the metrics were reported with two different "Metric" compound elements: 106 | 107 | 108 | 109 | 110 | ... 111 | 112 | 113 | ... 114 | 115 | 116 | 117 | ... 118 | 119 | 120 | Using the mxj package seemed a more straightforward approach than using Go vernacular 121 | and the standard xml package. I wrote the program getmetrics.go to do this. Here are 122 | three version to illustrate using 123 | getmetrics1.go - pass os.File handle for metrics_data.xml to NewMapXmlReader. 124 | getmetrics2.go - load metrics_data.xml into an in-memory buffer, then pass it to NewMapXml. 125 | getmetrics3.go - demonstrates overhead of extracting the raw XML while decoding with NewMapXmlReaderRaw. 126 | 127 | To run example getmetrics1.go, extract a 120,000+ row data set from metrics_data.zip. Then: 128 | go run getmetrics1.go -file=metrics_data.xml 129 | 130 | 131 | -------------------------------------------------------------------------------- /examples/append.go: -------------------------------------------------------------------------------- 1 | // Per https://github.com/clbanning/mxj/issues/34 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "strconv" 8 | 9 | "github.com/clbanning/mxj/v2" 10 | ) 11 | 12 | var data = []byte(` 13 | 14 | 1 15 | `) 16 | 17 | func main() { 18 | m, err := mxj.NewMapXml(data) 19 | if err != nil { 20 | fmt.Println("new err:", err) 21 | return 22 | } 23 | b, err := m.ValueForPath("a.b") 24 | if err != nil { 25 | fmt.Println("value err:", err) 26 | return 27 | } 28 | 29 | b, err = appendElement(b, 2) 30 | if err != nil { 31 | fmt.Println("append err:", err) 32 | return 33 | } 34 | 35 | // Create the new value for 'b' as a map 36 | // and update 'm'. 37 | // We should probably have an UpdateValueForPath 38 | // method just as there is ValueForPath/ValuesForPath 39 | // methods. 40 | val := map[string]interface{}{"b": b} 41 | n, err := m.UpdateValuesForPath(val, "a.b") 42 | if err != nil { 43 | fmt.Println("update err:", err) 44 | return 45 | } 46 | if n == 0 { 47 | fmt.Println("err: a.b not updated, n =", n) 48 | return 49 | } 50 | 51 | x, err := m.XmlIndent("", " ") 52 | if err != nil { 53 | fmt.Println("XmlIndent err:", err) 54 | return 55 | } 56 | fmt.Println(string(x)) 57 | } 58 | 59 | func appendElement(v interface{}, n int) (interface{}, error) { 60 | switch v.(type) { 61 | case string: 62 | v = []interface{}{v.(string), strconv.Itoa(n)} 63 | case []interface{}: 64 | v = append(v.([]interface{}), interface{}(strconv.Itoa(n))) 65 | default: 66 | // capture map[string]interface{} value, simple element, etc. 67 | return v, fmt.Errorf("invalid type") 68 | } 69 | return v, nil 70 | } 71 | -------------------------------------------------------------------------------- /examples/bom.go: -------------------------------------------------------------------------------- 1 | // from: https://www.reddit.com/r/golang/comments/99lnxd/help_needed_encodingxml_parsing_malformed_xml/ 2 | // 3 | 4 | package main 5 | 6 | import ( 7 | "encoding/xml" 8 | "fmt" 9 | 10 | "github.com/clbanning/mxj/v2" 11 | ) 12 | 13 | func main() { 14 | type decodedXml struct { 15 | XMLName xml.Name `xml:"tag" binding:"required"` 16 | SomeAttr string `xml:"someattr,attr"` 17 | } 18 | 19 | data := []byte(` 20 | -==sddsfdqsdqsdsqd 21 | A BUNCH OF INVALID DATA IN THE XML FILE HAHAH 22 | --SEPARATERUFCIFHEIR 23 | Content-Type: text/plain 24 | 25 | 26 | --SEPARATERUFCIFHEIR-- 27 | {"hello": "world"} 28 | `) 29 | 30 | // don't prepend attributes with '-' 31 | mxj.SetAttrPrefix("") 32 | 33 | // parse the data as a map[string]interface{} value 34 | m, err := mxj.NewMapXml(data) 35 | if err != nil { 36 | fmt.Println("err:", err) 37 | return 38 | } 39 | // check that we got a 'tag' tagged doc 40 | if _, ok := m["tag"]; !ok { 41 | fmt.Println("no tag doc ...") 42 | return 43 | } 44 | fmt.Printf("%v\n", m) 45 | 46 | // extract the attribute value 47 | attrval, err := m.ValueForPath("tag.someattr") 48 | if err != nil { 49 | fmt.Println("err:", err) 50 | return 51 | } 52 | 53 | // create decodeXml value 54 | val := decodedXml{ 55 | XMLName: xml.Name{"", "tag"}, 56 | SomeAttr: attrval.(string)} 57 | fmt.Printf("%v\n", val) 58 | } 59 | -------------------------------------------------------------------------------- /examples/books.go: -------------------------------------------------------------------------------- 1 | // Note: this illustrates ValuesForKey() and ValuesForPath() methods 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "github.com/clbanning/mxj/v2" 8 | "log" 9 | ) 10 | 11 | var xmldata = []byte(` 12 | 13 | 14 | William H. Gaddis 15 | The Recognitions 16 | One of the great seminal American novels of the 20th century. 17 | 18 | 19 | Austin Tappan Wright 20 | Islandia 21 | An example of earlier 20th century American utopian fiction. 22 | 23 | 24 | John Hawkes 25 | The Beetle Leg 26 | A lyrical novel about the construction of Ft. Peck Dam in Montana. 27 | 28 | 29 | T.E. Porter 30 | King's Day 31 | A magical novella. 32 | 33 | 34 | `) 35 | 36 | func main() { 37 | fmt.Println(string(xmldata)) 38 | 39 | m, err := mxj.NewMapXml(xmldata) 40 | if err != nil { 41 | log.Fatal("err:", err.Error()) 42 | } 43 | 44 | v, _ := m.ValuesForKey("books") 45 | fmt.Println("path: books; len(v):", len(v)) 46 | fmt.Printf("\t%+v\n", v) 47 | 48 | v, _ = m.ValuesForPath("books.book") 49 | fmt.Println("path: books.book; len(v):", len(v)) 50 | for _, vv := range v { 51 | fmt.Printf("\t%+v\n", vv) 52 | } 53 | 54 | v, _ = m.ValuesForPath("books.*") 55 | fmt.Println("path: books.*; len(v):", len(v)) 56 | for _, vv := range v { 57 | fmt.Printf("\t%+v\n", vv) 58 | } 59 | 60 | v, _ = m.ValuesForPath("books.*.title") 61 | fmt.Println("path: books.*.title len(v):", len(v)) 62 | for _, vv := range v { 63 | fmt.Printf("\t%+v\n", vv) 64 | } 65 | 66 | v, _ = m.ValuesForPath("books.*.*") 67 | fmt.Println("path: books.*.*; len(v):", len(v)) 68 | for _, vv := range v { 69 | fmt.Printf("\t%+v\n", vv) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /examples/gitissue1.go: -------------------------------------------------------------------------------- 1 | // https://github.com/clbanning/mxj/issues/17 2 | 3 | package main 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "github.com/clbanning/mxj/v2" 9 | "io" 10 | ) 11 | 12 | var data = []byte(` 13 | 14 | just something to demo 15 | `) 16 | 17 | func main() { 18 | r := bytes.NewReader(data) 19 | m := make(map[string]interface{}) 20 | var v map[string]interface{} 21 | var err error 22 | for { 23 | v, err = mxj.NewMapXmlSeqReader(r) 24 | if err != nil { 25 | if err == io.EOF { 26 | break 27 | } 28 | if err != mxj.NoRoot { 29 | // handle error 30 | } 31 | } 32 | for key, val := range v { 33 | m[key] = val 34 | } 35 | } 36 | fmt.Printf("%v\n", m) 37 | } 38 | -------------------------------------------------------------------------------- /examples/gitissue2.dat: -------------------------------------------------------------------------------- 1 | 2 | 3 | QQ 4 | 5 | -------------------------------------------------------------------------------- /examples/gitissue2.go: -------------------------------------------------------------------------------- 1 | // https://github.com/clbanning/mxj/issues/17 2 | 3 | package main 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "github.com/clbanning/mxj/v2" 9 | "io" 10 | "io/ioutil" 11 | ) 12 | 13 | func main() { 14 | b, err := ioutil.ReadFile("gitissue2.dat") 15 | if err != nil { 16 | fmt.Println("err:", err) 17 | return 18 | } 19 | r := bytes.NewReader(b) 20 | m := make(map[string]interface{}) 21 | for { 22 | v, err := mxj.NewMapXmlSeqReader(r) 23 | // v, raw, err := mxj.NewMapXmlSeqReaderRaw(r) 24 | if err != nil { 25 | if err == io.EOF { 26 | break 27 | } 28 | if err != mxj.NoRoot { 29 | // handle error 30 | } 31 | } 32 | for key, val := range v { 33 | m[key] = val 34 | } 35 | // fmt.Println(string(raw)) 36 | } 37 | fmt.Printf("%v\n", m) 38 | } 39 | -------------------------------------------------------------------------------- /examples/gitissue2_2.go: -------------------------------------------------------------------------------- 1 | // https://github.com/clbanning/mxj/issues/17 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "github.com/clbanning/mxj/v2" 8 | "io" 9 | "os" 10 | ) 11 | 12 | func main() { 13 | fh, err := os.Open("gitissue2.dat") 14 | if err != nil { 15 | fmt.Println("err:", err) 16 | return 17 | } 18 | m := make(map[string]interface{}) 19 | for { 20 | v, err := mxj.NewMapXmlSeqReader(fh) 21 | if err != nil { 22 | if err == io.EOF { 23 | break 24 | } 25 | if err != mxj.NoRoot { 26 | // handle error 27 | } 28 | } 29 | for key, val := range v { 30 | m[key] = val 31 | } 32 | // fmt.Println(string(raw)) 33 | } 34 | fmt.Printf("%v\n", m) 35 | } 36 | -------------------------------------------------------------------------------- /examples/gonuts1.go: -------------------------------------------------------------------------------- 1 | // https://groups.google.com/forum/#!searchin/golang-nuts/idnet$20netid/golang-nuts/guM3ZHHqSF0/K1pBpMqQSSwJ 2 | // http://play.golang.org/p/BFFDxphKYK 3 | 4 | package main 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "github.com/clbanning/mxj/v2" 10 | "io" 11 | ) 12 | 13 | // Demo how to compensate for irregular tag labels in data. 14 | // Need to extract from an XML stream the values for "netid" and "idnet". 15 | // Solution: use a wildcard path "data.*" to anonymize the "netid" and "idnet" tags. 16 | 17 | var msg1 = []byte(` 18 | 19 | 20 | 21 | no 22 | default:text 23 | default:word 24 | 25 | 26 | `) 27 | 28 | var msg2 = []byte(` 29 | 30 | 31 | 32 | yes 33 | default:text 34 | default:word 35 | 36 | 37 | `) 38 | 39 | func main() { 40 | // let's create a message stream 41 | buf := new(bytes.Buffer) 42 | // load a couple of messages into it 43 | _, _ = buf.Write(msg1) 44 | _, _ = buf.Write(msg2) 45 | 46 | n := 0 47 | for { 48 | n++ 49 | // read the stream as Map values - quit on io.EOF 50 | m, raw, merr := mxj.NewMapXmlReaderRaw(buf) 51 | if merr != nil && merr != io.EOF { 52 | // handle error - for demo we just print it and continue 53 | fmt.Printf("msg: %d - merr: %s\n", n, merr.Error()) 54 | continue 55 | } else if merr == io.EOF { 56 | break 57 | } 58 | fmt.Println("\nMessage to parse:", string(raw)) 59 | fmt.Println("Map value for XML message:", m.StringIndent()) 60 | 61 | // get the values for "netid" or "idnet" key using path == "data.*" 62 | values, _ := m.ValuesForPath("data.*") 63 | fmt.Println("\nmsg:", n, "> path == data.* - got array of values, len:", len(values)) 64 | for i, val := range values { 65 | fmt.Println("ValuesForPath result array member -", i, ":", val) 66 | fmt.Println(" k:v pairs for array member:", i) 67 | for key, val := range val.(map[string]interface{}) { 68 | // You'd probably want to process the value, as appropriate. 69 | // Here we just print it out. 70 | fmt.Println("\t\t", key, ":", val) 71 | } 72 | } 73 | 74 | // This shows what happens if you wildcard the value keys for "idnet" and "netid" 75 | values, _ = m.ValuesForPath("data.*.*") 76 | fmt.Println("\npath == data.*.* - got an array of values, len(v):", len(values)) 77 | fmt.Println("(Note: values returned by ValuesForPath are at maximum depth of the tree. So just have values.)") 78 | for i, val := range values { 79 | fmt.Println("ValuesForPath array member -", i, ":", val) 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /examples/gonuts10.go: -------------------------------------------------------------------------------- 1 | /* gonuts10.go - https://groups.google.com/forum/?fromgroups#!topic/golang-nuts/tf4aDQ1Hn_c 2 | change: 3 | 4 | Sam 5 | Kevin 6 | Smith 7 | 8 | 9 | 10 | to: 11 | 12 | Sam 13 | Kevin 14 | Smith 15 | Kevin Smith 16 | 17 | 18 | NOTE: sequence of elements NOT guaranteed due to use of map[string]interface{}. 19 | 20 | Here we build the "full-name" element value from other values in the doc by selecting the 21 | "first-name" value with the latest dates. 22 | */ 23 | 24 | package main 25 | 26 | import ( 27 | "fmt" 28 | "github.com/clbanning/mxj/v2" 29 | ) 30 | 31 | var data = []byte(` 32 | 33 | Sam 34 | Kevin 35 | Smith 36 | 37 | 38 | `) 39 | 40 | func main() { 41 | m, err := mxj.NewMapXml(data) 42 | if err != nil { 43 | fmt.Println("NewMapXml err:", err) 44 | return 45 | } 46 | // vals, err := m.ValuesForPath("author.first-name") // full-path option 47 | vals, err := m.ValuesForKey("first-name") // key-only alternatively 48 | if err != nil { 49 | fmt.Println("ValuesForPath err:", err) 50 | return 51 | } else if len(vals) == 0 { 52 | fmt.Println("no first-name vals") 53 | return 54 | } 55 | var fname, date string 56 | for _, v := range vals { 57 | vm, ok := v.(map[string]interface{}) 58 | if !ok { 59 | fmt.Println("assertion failed") 60 | return 61 | } 62 | fn, ok := vm["#text"].(string) 63 | if !ok { 64 | fmt.Println("no #text tag") 65 | return 66 | } 67 | dt, ok := vm["-effect_range"].(string) 68 | if !ok { 69 | fmt.Println("no -effect_range attr") 70 | return 71 | } 72 | if dt > date { 73 | date = dt 74 | fname = fn 75 | } 76 | } 77 | /* 78 | // alternatively: 79 | //(however, this requires knowing what latest "effect_range" attribute value is) 80 | vals, err := m.ValuesForKey("first-name", "-effect_range:2012-") 81 | if len(vals) == 0 { 82 | fmt.Println("no #text vals") 83 | return 84 | } 85 | fname := vals[0].(map[string]interface{})["#text"].(string) 86 | */ 87 | 88 | // vals, err = m.ValuesForKey("last-name") // key-only option 89 | vals, err = m.ValuesForPath("author.last-name") // full-path option 90 | if err != nil { 91 | fmt.Println("ValuesForPath err:", err) 92 | return 93 | } else if len(vals) == 0 { 94 | fmt.Println("no last-name vals") 95 | return 96 | } 97 | lname := vals[0].(string) 98 | if err = m.SetValueForPath(fname+" "+lname, "author.full-name"); err != nil { 99 | fmt.Println("SetValueForPath err:", err) 100 | return 101 | } 102 | b, err := m.XmlIndent("", " ") 103 | if err != nil { 104 | fmt.Println("XmlIndent err:", err) 105 | return 106 | } 107 | fmt.Println(string(b)) 108 | } 109 | -------------------------------------------------------------------------------- /examples/gonuts10seq.go: -------------------------------------------------------------------------------- 1 | /* gonuts10.go - https://groups.google.com/forum/?fromgroups#!topic/golang-nuts/tf4aDQ1Hn_c 2 | change: 3 | 4 | Sam 5 | Kevin 6 | Smith 7 | 8 | 9 | 10 | to: 11 | 12 | Sam 13 | Kevin 14 | Smith 15 | Kevin Smith 16 | 17 | 18 | NOTE: use NewMapXmlSeq() and mv.XmlSeqIndent() to preserve structure. 19 | 20 | Here we build the "full-name" element value from other values in the doc by selecting the 21 | "first-name" value with the latest dates. 22 | */ 23 | 24 | package main 25 | 26 | import ( 27 | "fmt" 28 | "github.com/clbanning/mxj/v2" 29 | "strings" 30 | ) 31 | 32 | var data = []byte(` 33 | 34 | Sam 35 | Kevin 36 | Smith 37 | 38 | 39 | `) 40 | 41 | func main() { 42 | fmt.Println(string(data)) 43 | m, err := mxj.NewMapXmlSeq(data) 44 | if err != nil { 45 | fmt.Println("NewMapXml err:", err) 46 | return 47 | } 48 | vals, err := m.ValuesForPath("author.first-name") // full-path option 49 | if err != nil { 50 | fmt.Println("ValuesForPath err:", err) 51 | return 52 | } else if len(vals) == 0 { 53 | fmt.Println("no first-name vals") 54 | return 55 | } 56 | var fname, date string 57 | var index int 58 | for _, v := range vals { 59 | vm, ok := v.(map[string]interface{}) 60 | if !ok { 61 | fmt.Println("assertion failed") 62 | return 63 | } 64 | fn, ok := vm["#text"].(string) 65 | if !ok { 66 | fmt.Println("no #text tag") 67 | return 68 | } 69 | // extract the associated date 70 | dt, _ := mxj.Map(vm).ValueForPathString("#attr.effect_range.#text") 71 | if dt == "" { 72 | fmt.Println("no effect_range attr") 73 | return 74 | } 75 | dts := strings.Split(dt, "-") 76 | if len(dts) > 1 && dts[len(dts)-1] == "" { 77 | index = len(dts) - 2 78 | } else if len(dts) > 0 { 79 | index = len(dts) - 1 80 | } 81 | if len(dts) > 0 && dts[index] > date { 82 | date = dts[index] 83 | fname = fn 84 | } 85 | } 86 | 87 | vals, err = m.ValuesForPath("author.last-name.#text") // full-path option 88 | if err != nil { 89 | fmt.Println("ValuesForPath err:", err) 90 | return 91 | } else if len(vals) == 0 { 92 | fmt.Println("no last-name vals") 93 | return 94 | } 95 | lname := vals[0].(string) 96 | if err = m.SetValueForPath(fname+" "+lname, "author.full-name.#text"); err != nil { 97 | fmt.Println("SetValueForPath err:", err) 98 | return 99 | } 100 | b, err := m.XmlSeqIndent("", " ") 101 | if err != nil { 102 | fmt.Println("XmlIndent err:", err) 103 | return 104 | } 105 | fmt.Println(string(b)) 106 | } 107 | -------------------------------------------------------------------------------- /examples/gonuts1a.go: -------------------------------------------------------------------------------- 1 | // https://groups.google.com/forum/#!searchin/golang-nuts/idnet$20netid/golang-nuts/guM3ZHHqSF0/K1pBpMqQSSwJ 2 | // http://play.golang.org/p/BFFDxphKYK 3 | 4 | package main 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "github.com/clbanning/mxj/v2" 10 | "io" 11 | ) 12 | 13 | // Demo how to re-label a key using mv.NewMap(). 14 | // Need to normalize from an XML stream the tags "netid" and "idnet". 15 | // Solution: make everything "netid". 16 | 17 | var msg1 = []byte(` 18 | 19 | 20 | 21 | no 22 | default:text 23 | default:word 24 | 25 | 26 | `) 27 | 28 | var msg2 = []byte(` 29 | 30 | 31 | 32 | yes 33 | default:text 34 | default:word 35 | 36 | 37 | `) 38 | 39 | func main() { 40 | // let's create a message stream 41 | buf := new(bytes.Buffer) 42 | // load a couple of messages into it 43 | _, _ = buf.Write(msg1) 44 | _, _ = buf.Write(msg2) 45 | 46 | n := 0 47 | for { 48 | n++ 49 | // read the stream as Map values - quit on io.EOF 50 | m, raw, merr := mxj.NewMapXmlReaderRaw(buf) 51 | if merr != nil && merr != io.EOF { 52 | // handle error - for demo we just print it and continue 53 | fmt.Printf("msg: %d - merr: %s\n", n, merr.Error()) 54 | continue 55 | } else if merr == io.EOF { 56 | break 57 | } 58 | 59 | // the first keypair retains values if data correct 60 | // the second keypair relabels "idnet" to "netid" 61 | n, _ := m.NewMap("data.netid", "data.idnet:data.netid") 62 | x, _ := n.XmlIndent("", " ") 63 | 64 | fmt.Println("original value:", string(raw)) 65 | fmt.Println("new value:") 66 | fmt.Println(string(x)) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /examples/gonuts3.go: -------------------------------------------------------------------------------- 1 | // https://groups.google.com/forum/#!topic/golang-nuts/cok6xasvI3w 2 | // retrieve 'src' values from 'image' tags 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "github.com/clbanning/mxj/v2" 9 | ) 10 | 11 | var xmldata = []byte(` 12 | 13 | 14 | 15 | 16 | something else 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | `) 25 | 26 | func main() { 27 | fmt.Println("xmldata:", string(xmldata)) 28 | 29 | // get all image tag values - []interface{} 30 | m, merr := mxj.NewMapXml(xmldata) 31 | if merr != nil { 32 | fmt.Println("merr:", merr.Error()) 33 | return 34 | } 35 | 36 | // grab all values for attribute "src" 37 | // Note: attributes are prepended with a hyphen, '-'. 38 | sources, err := m.ValuesForKey("-src") 39 | if err != nil { 40 | fmt.Println("err:", err.Error()) 41 | return 42 | } 43 | 44 | for _, src := range sources { 45 | fmt.Println(src) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/gonuts4.go: -------------------------------------------------------------------------------- 1 | // https://groups.google.com/forum/#!topic/golang-nuts/-N9Toa6qlu8 2 | // shows that you can extract attribute values directly from tag/key path. 3 | // NOTE: attribute values are encoded by prepending a hyphen, '-'. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "github.com/clbanning/mxj/v2" 10 | ) 11 | 12 | var xmldata = []byte(` 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | `) 31 | 32 | func main() { 33 | fmt.Println("xmldata:", string(xmldata)) 34 | 35 | m, merr := mxj.NewMapXml(xmldata) 36 | if merr != nil { 37 | fmt.Println("merr:", merr) 38 | return 39 | } 40 | 41 | // Attributes are keys with prepended hyphen, '-'. 42 | values, err := m.ValuesForPath("doc.some_tag.geoInfo.country.-name") 43 | if err != nil { 44 | fmt.Println("err:", err.Error()) 45 | } 46 | 47 | for _, v := range values { 48 | fmt.Println("v:", v) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/gonuts5.go: -------------------------------------------------------------------------------- 1 | // gonuts5.go - from https://groups.google.com/forum/#!topic/golang-nuts/MWoYY19of3o 2 | // problem is to extract entries from by "int name=" 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "github.com/clbanning/mxj/v2" 9 | ) 10 | 11 | var xmlData = []byte(` 12 | 13 | 14 | 15 | 16 | 17 | 18 | 1 19 | 20 | 21 | 22 | 1 23 | 2 24 | 3 25 | 4 26 | 5 27 | 28 | 29 | 30 | 31 | 1 32 | 2 33 | 3 34 | 4 35 | 5 36 | 37 | 38 | 39 | 40 | `) 41 | 42 | func main() { 43 | // parse XML into a Map 44 | m, merr := mxj.NewMapXml(xmlData) 45 | if merr != nil { 46 | fmt.Println("merr:", merr.Error()) 47 | return 48 | } 49 | 50 | // extract the 'list3-1-1-1' node - there'll be just 1? 51 | // NOTE: attribute keys are prepended with '-' 52 | lstVal, lerr := m.ValuesForPath("*.*.*.*.*", "-name:list3-1-1-1") 53 | if lerr != nil { 54 | fmt.Println("ierr:", lerr.Error()) 55 | return 56 | } 57 | 58 | // assuming just one value returned - create a new Map 59 | mv := mxj.Map(lstVal[0].(map[string]interface{})) 60 | 61 | // extract the 'int' values by 'name' attribute: "-name" 62 | // interate over list of 'name' values of interest 63 | var names = []string{"field1", "field2", "field3", "field4", "field5"} 64 | for _, n := range names { 65 | vals, verr := mv.ValuesForKey("int", "-name:"+n) 66 | if verr != nil { 67 | fmt.Println("verr:", verr.Error(), len(vals)) 68 | return 69 | } 70 | 71 | // values for simple elements have key '#text' 72 | // NOTE: there can be only one value for key '#text' 73 | fmt.Println(n, ":", vals[0].(map[string]interface{})["#text"]) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /examples/gonuts5a.go: -------------------------------------------------------------------------------- 1 | // gonuts5.go - from https://groups.google.com/forum/#!topic/golang-nuts/MWoYY19of3o 2 | // problem is to extract entries from by "int name=" 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "github.com/clbanning/mxj/v2" 9 | ) 10 | 11 | var xmlData = []byte(` 12 | 13 | 14 | 15 | 16 | 17 | 18 | 1 19 | 20 | 21 | 22 | 1 23 | 2 24 | 3 25 | 4 26 | 5 27 | 28 | 29 | 30 | 31 | 1 32 | 2 33 | 3 34 | 4 35 | 5 36 | 37 | 38 | 39 | 40 | `) 41 | 42 | func main() { 43 | // parse XML into a Map 44 | m, merr := mxj.NewMapXml(xmlData) 45 | if merr != nil { 46 | fmt.Println("merr:", merr.Error()) 47 | return 48 | } 49 | 50 | // extract the 'list3-1-1-1' node - there'll be just 1? 51 | // NOTE: attribute keys are prepended with '-' 52 | lstVal, lerr := m.ValuesForPath("*.*.*.*.*", "-name:list3-1-1-1") 53 | if lerr != nil { 54 | fmt.Println("ierr:", lerr.Error()) 55 | return 56 | } 57 | 58 | // assuming just one value returned - create a new Map 59 | mv := mxj.Map(lstVal[0].(map[string]interface{})) 60 | 61 | // extract the 'int' values by 'name' attribute: "-name" 62 | // interate over list of 'name' values of interest 63 | var names = []string{"field1", "field2", "field3", "field4", "field5"} 64 | for _, n := range names { 65 | vals, verr := mv.ValuesForKey("*", "-name:"+n) 66 | if verr != nil { 67 | fmt.Println("verr:", verr.Error(), len(vals)) 68 | return 69 | } 70 | 71 | // values for simple elements have key '#text' 72 | // NOTE: there can be only one value for key '#text' 73 | fmt.Println(n, ":", vals[0].(map[string]interface{})["#text"]) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /examples/gonuts6.go: -------------------------------------------------------------------------------- 1 | /* https://groups.google.com/forum/#!topic/golang-nuts/EMXHB1nJoBA 2 | 3 | package main 4 | 5 | import "fmt" 6 | import "encoding/json" 7 | 8 | func main() { 9 | var v struct { 10 | DBInstances []struct { 11 | Endpoint struct { 12 | Address string 13 | } 14 | } 15 | } 16 | json.Unmarshal(s, &v) 17 | fmt.Println(v.DBInstances[0].Endpoint.Address) 18 | } 19 | */ 20 | 21 | package main 22 | 23 | import "fmt" 24 | import "github.com/clbanning/mxj/v2" 25 | 26 | func main() { 27 | m, err := mxj.NewMapJson(s) 28 | if err != nil { 29 | fmt.Println("err:", err.Error()) 30 | } 31 | v, err := m.ValuesForKey("Address") 32 | // v, err := m.ValuesForPath("DBInstances[0].Endpoint.Address") 33 | if err != nil { 34 | fmt.Println("err:", err.Error()) 35 | } 36 | if len(v) > 0 { 37 | fmt.Println(v[0].(string)) 38 | } else { 39 | fmt.Println("No value.") 40 | } 41 | } 42 | 43 | var s = []byte(`{ "DBInstances": [ { "PubliclyAccessible": true, "MasterUsername": "postgres", "LicenseModel": "postgresql-license", "VpcSecurityGroups": [ { "Status": "active", "VpcSecurityGroupId": "sg-e72a4282" } ], "InstanceCreateTime": "2014-06-29T03:52:59.268Z", "OptionGroupMemberships": [ { "Status": "in-sync", "OptionGroupName": "default:postgres-9-3" } ], "PendingModifiedValues": {}, "Engine": "postgres", "MultiAZ": true, "LatestRestorableTime": "2014-06-29T12:00:34Z", "DBSecurityGroups": [ { "Status": "active", "DBSecurityGroupName": "production-dbsecuritygroup-q4f0ugxpjck8" } ], "DBParameterGroups": [ { "DBParameterGroupName": "default.postgres9.3", "ParameterApplyStatus": "in-sync" } ], "AutoMinorVersionUpgrade": true, "PreferredBackupWindow": "06:59-07:29", "DBSubnetGroup": { "Subnets": [ { "SubnetStatus": "Active", "SubnetIdentifier": "subnet-34e5d01c", "SubnetAvailabilityZone": { "Name": "us-east-1b", "ProvisionedIopsCapable": false } }, { "SubnetStatus": "Active", "SubnetIdentifier": "subnet-50759d27", "SubnetAvailabilityZone": { "Name": "us-east-1c", "ProvisionedIopsCapable": false } }, { "SubnetStatus": "Active", "SubnetIdentifier": "subnet-450a1f03", "SubnetAvailabilityZone": { "Name": "us-east-1d", "ProvisionedIopsCapable": false } } ], "DBSubnetGroupName": "default", "VpcId": "vpc-acb86cc9", "DBSubnetGroupDescription": "default", "SubnetGroupStatus": "Complete" }, "SecondaryAvailabilityZone": "us-east-1b", "ReadReplicaDBInstanceIdentifiers": [], "AllocatedStorage": 15, "BackupRetentionPeriod": 1, "DBName": "deis", "PreferredMaintenanceWindow": "fri:05:52-fri:06:22", "Endpoint": { "Port": 5432, "Address": "production.cfk8mskkbkeu.us-east-1.rds.amazonaws.com" }, "DBInstanceStatus": "available", "EngineVersion": "9.3.3", "AvailabilityZone": "us-east-1c", "DBInstanceClass": "db.m1.small", "DBInstanceIdentifier": "production" } ] }`) 44 | -------------------------------------------------------------------------------- /examples/gonuts9.go: -------------------------------------------------------------------------------- 1 | // https://groups.google.com/forum/?fromgroups#!topic/golang-nuts/2A6_YRYXCjA 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "github.com/clbanning/mxj/v2" 8 | ) 9 | 10 | var data = []byte(` 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | `) 42 | 43 | func main() { 44 | m, err := mxj.NewMapXml(data) 45 | if err != nil { 46 | fmt.Println("err:", err) 47 | } 48 | fmt.Println(m.StringIndentNoTypeInfo()) 49 | 50 | doc, err := m.XmlIndent("", " ") 51 | if err != nil { 52 | fmt.Println("err:", err) 53 | } 54 | fmt.Println(string(doc)) 55 | 56 | val, err := m.ValuesForKey("child1") 57 | if err != nil { 58 | fmt.Println("err:", err) 59 | } 60 | fmt.Println("val:", val) 61 | 62 | mxj.XmlGoEmptyElemSyntax() 63 | doc, err = mxj.AnyXmlIndent(val, "", " ", "child1") 64 | if err != nil { 65 | fmt.Println("err:", err) 66 | } 67 | fmt.Println(string(doc)) 68 | } 69 | -------------------------------------------------------------------------------- /examples/goofy_map.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/clbanning/mxj/v2" 7 | ) 8 | 9 | func main() { 10 | data := map[interface{}]interface{}{ 11 | "hello": "out there", 12 | 1: "number one", 13 | 3.12: "pi", 14 | "five": 5, 15 | } 16 | 17 | m, err := mxj.AnyXmlIndent(data,"", " ") 18 | if err != nil { 19 | fmt.Println(err) 20 | return 21 | } 22 | fmt.Println(string(m)) 23 | } 24 | -------------------------------------------------------------------------------- /examples/leafnodes.go: -------------------------------------------------------------------------------- 1 | // https://groups.google.com/forum/#!topic/golang-nuts/pj0C5IrZk4I 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | 8 | "github.com/clbanning/mxj/v2" 9 | ) 10 | 11 | func main() { 12 | j := `{"jsonData":{ 13 | "DataReference":[ 14 | { 15 | "ParameterType":"test", 16 | "Applicationtype":[ 17 | { 18 | "Application1":{ 19 | "ApplicationName":"app1", 20 | "Param1":{ 21 | "Name":"app1.param1" 22 | }, 23 | "Param2":{ 24 | "Name":"app1.param2" 25 | } 26 | }, 27 | "Application2":{ 28 | "ApplicationName":"app2", 29 | "Param1":{ 30 | "Name":"app2.param1" 31 | }, 32 | "Param2":{ 33 | "Name":"app2.param2" 34 | } 35 | } 36 | } 37 | ] 38 | } 39 | ] 40 | }}` 41 | 42 | // unmarshal into a map 43 | m, err := mxj.NewMapJson([]byte(j)) 44 | if err != nil { 45 | fmt.Println("err:", err) 46 | return 47 | } 48 | mxj.LeafUseDotNotation() 49 | l := m.LeafNodes() 50 | for _, v := range l { 51 | fmt.Println("path:", v.Path, "value:", v.Value) 52 | } 53 | /* 54 | Output (sequence not guaranteed): 55 | path: jsonData.DataReference.0.ParameterType value: test 56 | path: jsonData.DataReference.0.Applicationtype.0.Application1.ApplicationName value: app1 57 | path: jsonData.DataReference.0.Applicationtype.0.Application1.Param1.Name value: app1.param1 58 | path: jsonData.DataReference.0.Applicationtype.0.Application1.Param2.Name value: app1.param2 59 | path: jsonData.DataReference.0.Applicationtype.0.Application2.ApplicationName value: app2 60 | path: jsonData.DataReference.0.Applicationtype.0.Application2.Param1.Name value: app2.param1 61 | path: jsonData.DataReference.0.Applicationtype.0.Application2.Param2.Name value: app2.param2 62 | */ 63 | } 64 | -------------------------------------------------------------------------------- /examples/metrics_data.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clbanning/mxj/4b4fff5e8fe91f2d6a48e621bfa455fd8397ac6d/examples/metrics_data.zip -------------------------------------------------------------------------------- /examples/order.go: -------------------------------------------------------------------------------- 1 | // Preserve list order with intermixed sub-elements. 2 | // from: https://groups.google.com/forum/#!topic/golang-nuts/8KvlKsdh84k 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "sort" 9 | 10 | "github.com/clbanning/mxj/v2" 11 | ) 12 | 13 | var data = ` 14 | sadasd 15 | gfdfg 16 | bcbcbc 17 | hihihi 18 | jkjkjk 19 | lmlmlm 20 | ` 21 | 22 | func main() { 23 | m, err := mxj.NewMapXmlSeq([]byte(data)) 24 | if err != nil { 25 | fmt.Println("err:", err) 26 | return 27 | } 28 | 29 | // Merge a b, and c members into a single list. 30 | // The value has "#text" key; "#seq" has the list sequence index. 31 | // Preserve both and the list key if you want to reencode. 32 | list := make([]*listval, 0) 33 | for k, v := range m["node"].(map[string]interface{}) { 34 | switch v.(type) { 35 | case map[string]interface{}: 36 | // This handles the lone 'c' element in the list. 37 | mem := v.(map[string]interface{}) 38 | lval := &listval{k, mem["#text"].(string), mem["#seq"].(int)} 39 | list = append(list, lval) 40 | case []interface{}: 41 | // 'a' and 'b' were decoded as slices. 42 | for _, vv := range v.([]interface{}) { 43 | mem := vv.(map[string]interface{}) 44 | lval := &listval{k, mem["#text"].(string), mem["#seq"].(int)} 45 | list = append(list, lval) 46 | } 47 | } 48 | } 49 | 50 | // Sort the list into orignal DOC sequence. 51 | sort.Sort(Lval(list)) 52 | 53 | // Do some work with the list members - let's swap values. 54 | for i := 0; i < 3; i++ { 55 | list[i].val, list[5-i].val = list[5-i].val, list[i].val 56 | } 57 | 58 | // Rebuild map[string]interface{} value for "node". 59 | // Everything can be slice values - []interface{} - for encoding. 60 | a := make([]interface{}, 0) 61 | b := make([]interface{}, 0) 62 | c := make([]interface{}, 0) 63 | for _, v := range list { 64 | val := map[string]interface{}{"#text": v.val, "#seq": v.seq} 65 | switch v.list { 66 | case "a": 67 | a = append(a, interface{}(val)) 68 | case "b": 69 | b = append(b, interface{}(val)) 70 | case "c": 71 | c = append(c, interface{}(val)) 72 | } 73 | } 74 | val := map[string]interface{}{"a": a, "b": b, "c": c} 75 | m["node"] = interface{}(val) 76 | 77 | x, err := m.XmlSeqIndent("", " ") 78 | if err != nil { 79 | fmt.Println("err:", err) 80 | return 81 | } 82 | fmt.Println(data) // original 83 | fmt.Println(string(x)) // modified 84 | } 85 | 86 | // ======== sort interface implementation ======== 87 | type listval struct { 88 | list string 89 | val string 90 | seq int 91 | } 92 | 93 | type Lval []*listval 94 | 95 | func (l Lval) Len() int { 96 | return len(l) 97 | } 98 | 99 | func (l Lval) Less(i, j int) bool { 100 | if l[i].seq < l[j].seq { 101 | return true 102 | } 103 | return false 104 | } 105 | 106 | func (l Lval) Swap(i, j int) { 107 | l[i], l[j] = l[j], l[i] 108 | } 109 | -------------------------------------------------------------------------------- /examples/partial.go: -------------------------------------------------------------------------------- 1 | // https://github.com/clbanning/mxj/issues/53 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | 8 | "github.com/clbanning/mxj/v2" 9 | ) 10 | 11 | var source = []byte(` 12 | 13 | c 14 | d 15 | 16 | 17 | x 18 | y 19 | z 20 | 21 | f 22 | g 23 | `) 24 | 25 | var wants = map[string][]string{ 26 | "one": { 27 | "a.b.c", 28 | "a.e.x", 29 | }, 30 | "two": { 31 | "a.b.d", 32 | "a.e.y", 33 | "a.e.z", 34 | }, 35 | "three": { 36 | "a.f", 37 | "a.g", 38 | }, 39 | } 40 | 41 | func main() { 42 | m, err := mxj.NewMapXml(source) 43 | if err != nil { 44 | fmt.Println("err:", err) 45 | return 46 | } 47 | 48 | n, err := m.NewMap(wants["one"]...) 49 | if err != nil { 50 | fmt.Println("err:", err) 51 | return 52 | } 53 | 54 | target, err := n.XmlIndent("", " ") 55 | if err != nil { 56 | fmt.Println("err:", err) 57 | return 58 | } 59 | 60 | fmt.Println(string(target)) 61 | 62 | // the rest of the wants 63 | n, _ = m.NewMap(wants["two"]...) 64 | target, _ = n.XmlIndent("", " ") 65 | fmt.Println(string(target)) 66 | 67 | n, _ = m.NewMap(wants["three"]...) 68 | target, _ = n.XmlIndent("", " ") 69 | fmt.Println(string(target)) 70 | } 71 | -------------------------------------------------------------------------------- /examples/reddit01.go: -------------------------------------------------------------------------------- 1 | // https://www.reddit.com/r/golang/comments/9eclgy/xml_unmarshaling_internal_references/ 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | 8 | "github.com/clbanning/mxj/v2" 9 | ) 10 | 11 | var data = []byte(` 12 | 13 | 14 | 15 | 16 | 17 | Hello!! 18 | 19 | `) 20 | 21 | func main() { 22 | m, err := mxj.NewMapXml(data) 23 | if err != nil { 24 | fmt.Println("err:", err) 25 | return 26 | } 27 | fmt.Printf("%v\n", m) 28 | 29 | type mystruct struct { 30 | FromUser string 31 | ToUser string 32 | Message string 33 | } 34 | myStruct := mystruct{} 35 | vals, err := m.ValuesForPath("app.users.user", "-id:1") 36 | if err != nil { 37 | fmt.Println("err:", err) 38 | return 39 | } 40 | if len(vals) == 1 { 41 | myStruct.FromUser = vals[0].(map[string]interface{})["-name"].(string) 42 | } 43 | vals, err = m.ValuesForPath("app.users.user", "-id:2") 44 | if err != nil { 45 | fmt.Println("err:", err) 46 | return 47 | } 48 | if len(vals) == 1 { 49 | myStruct.ToUser = vals[0].(map[string]interface{})["-name"].(string) 50 | } 51 | vals, err = m.ValuesForPath("app.messages.message.#text") 52 | if err != nil { 53 | fmt.Println("err:", err) 54 | return 55 | } 56 | if len(vals) == 1 { 57 | myStruct.Message = vals[0].(string) 58 | } 59 | 60 | fmt.Printf("%#v\n", myStruct) 61 | } 62 | -------------------------------------------------------------------------------- /examples/reddit02.go: -------------------------------------------------------------------------------- 1 | // https://www.reddit.com/r/golang/comments/9eclgy/xml_unmarshaling_internal_references/ 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | 8 | "github.com/clbanning/mxj/v2" 9 | ) 10 | 11 | var data = []byte(` 12 | 13 | 14 | 15 | 16 | 17 | Hello!! 18 | 19 | `) 20 | 21 | func main() { 22 | m, err := mxj.NewMapXml(data) 23 | if err != nil { 24 | fmt.Println("err:", err) 25 | return 26 | } 27 | fmt.Printf("%v\n", m) 28 | 29 | type mystruct struct { 30 | FromUser string 31 | ToUser string 32 | Message string 33 | } 34 | myStruct := mystruct{} 35 | val, err := m.ValueForKey("user", "-id:1") 36 | if val != nil { 37 | myStruct.FromUser = val.(map[string]interface{})["-name"].(string) 38 | } else { 39 | // if there no val, then err is at least KeyNotExistError 40 | fmt.Println("err:", err) 41 | return 42 | } 43 | val, err = m.ValueForKey("user", "-id:2") 44 | if val != nil { 45 | myStruct.ToUser = val.(map[string]interface{})["-name"].(string) 46 | } else { 47 | // if there no val, then err is at least KeyNotExistError 48 | fmt.Println("err:", err) 49 | return 50 | } 51 | val, err = m.ValueForKey("#text") 52 | if val != nil { 53 | myStruct.Message = val.(string) 54 | } else { 55 | // if there no val, then err is at least KeyNotExistError 56 | fmt.Println("err:", err) 57 | return 58 | } 59 | 60 | fmt.Printf("%#v\n", myStruct) 61 | } 62 | -------------------------------------------------------------------------------- /examples/x2jcmd.go: -------------------------------------------------------------------------------- 1 | // Per: https://github.com/clbanning/mxj/issues/24 2 | // Per: https://github.com/clbanning/mxj/issues/25 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "io" 9 | "os" 10 | "github.com/clbanning/mxj/v2/x2j" 11 | ) 12 | 13 | func main() { 14 | for { 15 | _, _, err := x2j.XmlReaderToJsonWriter(os.Stdin, os.Stdout) 16 | if err == io.EOF { 17 | break 18 | } 19 | if err != nil { 20 | fmt.Println(err) 21 | break 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/x2jcmd.xml: -------------------------------------------------------------------------------- 1 | is a test 2 | is another 3 | -------------------------------------------------------------------------------- /exists.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | // Checks whether the path exists. If err != nil then 'false' is returned 4 | // along with the error encountered parsing either the "path" or "subkeys" 5 | // argument. 6 | func (mv Map) Exists(path string, subkeys ...string) (bool, error) { 7 | v, err := mv.ValuesForPath(path, subkeys...) 8 | return (err == nil && len(v) > 0), err 9 | } 10 | -------------------------------------------------------------------------------- /exists_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestExists(t *testing.T) { 9 | fmt.Println("------------ exists_test.go") 10 | m := map[string]interface{}{ 11 | "Div": map[string]interface{}{ 12 | "Colour": "blue", 13 | }, 14 | } 15 | mv := Map(m) 16 | 17 | if v, _ := mv.Exists("Div.Colour"); !v { 18 | t.Fatal("Haven't found an existing element") 19 | } 20 | 21 | if v, _ := mv.Exists("Div.Color"); v { 22 | t.Fatal("Have found a non existing element") 23 | } 24 | } 25 | 26 | /* 27 | var existsDoc = []byte(` 28 | 29 | 30 | 31 | William T. Gaddis 32 | The Recognitions 33 | One of the great seminal American novels of the 20th century. 34 | 35 | 36 | Something else. 37 | 38 | `) 39 | 40 | func TestExistsWithSubKeys(t *testing.T) { 41 | mv, err := NewMapXml(existsDoc) 42 | if err != nil { 43 | t.Fatal("err:", err.Error()) 44 | } 45 | 46 | if !mv.Exists("doc.books.book", "-seq:1") { 47 | t.Fatal("Did't find an existing element") 48 | } 49 | 50 | if mv.Exists("doc.books.book", "-seq:2") { 51 | t.Fatal("Found a non-existing element") 52 | } 53 | } 54 | */ 55 | -------------------------------------------------------------------------------- /files_test.badjson: -------------------------------------------------------------------------------- 1 | { "this":"is", "a":"test", "file":"for", "files_test.go":"case" } 2 | { "with":"some", "bad":JSON, "in":"it" } 3 | -------------------------------------------------------------------------------- /files_test.badxml: -------------------------------------------------------------------------------- 1 | 2 | test 3 | for files.go 4 | 5 | 6 | some 7 | doc 8 | test case 9 | 10 | -------------------------------------------------------------------------------- /files_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestFilesHeader(t *testing.T) { 9 | fmt.Println("\n---------------- files_test.go ...") 10 | } 11 | 12 | func TestNewJsonFile(t *testing.T) { 13 | fmt.Println("NewMapsFromJsonFile()") 14 | am, err := NewMapsFromJsonFile("files_test.json") 15 | if err != nil { 16 | t.Fatal(err.Error()) 17 | } 18 | for _, v := range am { 19 | fmt.Printf("%v\n", v) 20 | } 21 | 22 | am, err = NewMapsFromJsonFile("nil") 23 | if err == nil { 24 | t.Fatal("no error returned for read of nil file") 25 | } 26 | fmt.Println("caught error: ", err.Error()) 27 | 28 | am, err = NewMapsFromJsonFile("files_test.badjson") 29 | if err == nil { 30 | t.Fatal("no error returned for read of badjson file") 31 | } 32 | fmt.Println("caught error: ", err.Error()) 33 | } 34 | 35 | func TestNewJsonFileRaw(t *testing.T) { 36 | fmt.Println("NewMapsFromJsonFileRaw()") 37 | mr, err := NewMapsFromJsonFileRaw("files_test.json") 38 | if err != nil { 39 | t.Fatal(err.Error()) 40 | } 41 | for _, v := range mr { 42 | fmt.Printf("%v\n", v) 43 | } 44 | 45 | mr, err = NewMapsFromJsonFileRaw("nil") 46 | if err == nil { 47 | t.Fatal("no error returned for read of nil file") 48 | } 49 | fmt.Println("caught error: ", err.Error()) 50 | 51 | mr, err = NewMapsFromJsonFileRaw("files_test.badjson") 52 | if err == nil { 53 | t.Fatal("no error returned for read of badjson file") 54 | } 55 | fmt.Println("caught error: ", err.Error()) 56 | } 57 | 58 | func TestNewXmFile(t *testing.T) { 59 | fmt.Println("NewMapsFromXmlFile()") 60 | am, err := NewMapsFromXmlFile("files_test.xml") 61 | if err != nil { 62 | t.Fatal(err.Error()) 63 | } 64 | for _, v := range am { 65 | fmt.Printf("%v\n", v) 66 | } 67 | 68 | am, err = NewMapsFromXmlFile("nil") 69 | if err == nil { 70 | t.Fatal("no error returned for read of nil file") 71 | } 72 | fmt.Println("caught error: ", err.Error()) 73 | 74 | am, err = NewMapsFromXmlFile("files_test.badxml") 75 | if err == nil { 76 | t.Fatal("no error returned for read of badjson file") 77 | } 78 | fmt.Println("caught error: ", err.Error()) 79 | } 80 | 81 | func TestNewXmFileRaw(t *testing.T) { 82 | fmt.Println("NewMapsFromXmlFileRaw()") 83 | mr, err := NewMapsFromXmlFileRaw("files_test.xml") 84 | if err != nil { 85 | t.Fatal(err.Error()) 86 | } 87 | for _, v := range mr { 88 | fmt.Printf("%v\n", v) 89 | } 90 | 91 | mr, err = NewMapsFromXmlFileRaw("nil") 92 | if err == nil { 93 | t.Fatal("no error returned for read of nil file") 94 | } 95 | fmt.Println("caught error: ", err.Error()) 96 | 97 | mr, err = NewMapsFromXmlFileRaw("files_test.badxml") 98 | if err == nil { 99 | t.Fatal("no error returned for read of badjson file") 100 | } 101 | fmt.Println("caught error: ", err.Error()) 102 | } 103 | 104 | func TestMaps(t *testing.T) { 105 | fmt.Println("TestMaps()") 106 | mvs := NewMaps() 107 | for i := 0; i < 2; i++ { 108 | m, _ := NewMapJson([]byte(`{ "this":"is", "a":"test" }`)) 109 | mvs = append(mvs, m) 110 | } 111 | fmt.Println("mvs:", mvs) 112 | 113 | s, _ := mvs.JsonString() 114 | fmt.Println("JsonString():", s) 115 | 116 | s, _ = mvs.JsonStringIndent("", " ") 117 | fmt.Println("JsonStringIndent():", s) 118 | 119 | s, _ = mvs.XmlString() 120 | fmt.Println("XmlString():", s) 121 | 122 | s, _ = mvs.XmlStringIndent("", " ") 123 | fmt.Println("XmlStringIndent():", s) 124 | } 125 | 126 | func TestJsonFile(t *testing.T) { 127 | am, err := NewMapsFromJsonFile("files_test.json") 128 | if err != nil { 129 | t.Fatal(err.Error()) 130 | } 131 | for _, v := range am { 132 | fmt.Printf("%v\n", v) 133 | } 134 | 135 | err = am.JsonFile("files_test_dup.json") 136 | if err != nil { 137 | t.Fatal(err.Error()) 138 | } 139 | fmt.Println("files_test_dup.json written") 140 | 141 | err = am.JsonFileIndent("files_test_indent.json", "", " ") 142 | if err != nil { 143 | t.Fatal(err.Error()) 144 | } 145 | fmt.Println("files_test_indent.json written") 146 | } 147 | 148 | func TestXmlFile(t *testing.T) { 149 | am, err := NewMapsFromXmlFile("files_test.xml") 150 | if err != nil { 151 | t.Fatal(err.Error()) 152 | } 153 | for _, v := range am { 154 | fmt.Printf("%v\n", v) 155 | } 156 | 157 | err = am.XmlFile("files_test_dup.xml") 158 | if err != nil { 159 | t.Fatal(err.Error()) 160 | } 161 | fmt.Println("files_test_dup.xml written") 162 | 163 | err = am.XmlFileIndent("files_test_indent.xml", "", " ") 164 | if err != nil { 165 | t.Fatal(err.Error()) 166 | } 167 | fmt.Println("files_test_indent.xml written") 168 | } 169 | -------------------------------------------------------------------------------- /files_test.json: -------------------------------------------------------------------------------- 1 | { "this":"is", "a":"test", "file":"for", "files_test.go":"case" } 2 | { "with":"just", "two":2, "JSON":"values", "true":true } 3 | -------------------------------------------------------------------------------- /files_test.xml: -------------------------------------------------------------------------------- 1 | 2 | test 3 | for files.go 4 | 5 | 6 | some 7 | doc 8 | test case 9 | 10 | -------------------------------------------------------------------------------- /files_test_dup.json: -------------------------------------------------------------------------------- 1 | {"a":"test","file":"for","files_test.go":"case","this":"is"}{"JSON":"values","true":true,"two":2,"with":"just"} -------------------------------------------------------------------------------- /files_test_dup.xml: -------------------------------------------------------------------------------- 1 | for files.gotestdoctest casesome -------------------------------------------------------------------------------- /files_test_indent.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": "test", 3 | "file": "for", 4 | "files_test.go": "case", 5 | "this": "is" 6 | } 7 | { 8 | "JSON": "values", 9 | "true": true, 10 | "two": 2, 11 | "with": "just" 12 | } -------------------------------------------------------------------------------- /files_test_indent.xml: -------------------------------------------------------------------------------- 1 | 2 | for files.go 3 | test 4 | 5 | doc 6 | test case 7 | some 8 | -------------------------------------------------------------------------------- /global_map_prefix_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/go-cmp/cmp" 7 | ) 8 | 9 | var whiteSpaceDataSeqTest2 = []byte(` 10 | 11 | William T. Gaddis 12 | The Recognitions 13 | One of the great seminal American novels of the 20th century. 14 | 15 | 16 | Austin Tappan Wright 17 | Islandia 18 | An example of earlier 20th century American utopian fiction. 19 | 20 | 21 | John Hawkes 22 | The Beetle Leg 23 | A lyrical novel about the construction of Ft. Peck Dam in Montana. 24 | 25 | `) 26 | 27 | func TestSetGlobalKeyMapPrefix(t *testing.T) { 28 | prefixList := []struct { 29 | name string 30 | value string 31 | }{ 32 | { 33 | name: "Testing with % as Map Key Prefix", 34 | value: "%", 35 | }, 36 | { 37 | name: "Testing with _ as Map Key Prefix", 38 | value: "_", 39 | }, 40 | { 41 | name: "Testing with - as Map Key Prefix", 42 | value: "-", 43 | }, 44 | { 45 | name: "Testing with & as Map Key Prefix", 46 | value: "&", 47 | }, 48 | } 49 | 50 | for _, prefix := range prefixList { 51 | t.Run(prefix.name, func(t *testing.T) { 52 | 53 | // Testing MapSeq(Ordering) with whitespace and byte equivalence 54 | DisableTrimWhiteSpace(true) 55 | SetGlobalKeyMapPrefix(prefix.value) 56 | 57 | m, err := NewMapFormattedXmlSeq(whiteSpaceDataSeqTest2) 58 | if err != nil { 59 | t.Fatal(err) 60 | } 61 | 62 | m1 := MapSeq(m) 63 | x, err := m1.XmlIndent("", " ") 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | 68 | if string(x) != string(whiteSpaceDataSeqTest2) { 69 | t.Fatalf("expected\n'%s' \ngot \n'%s'", whiteSpaceDataSeqTest2, x) 70 | } 71 | DisableTrimWhiteSpace(false) 72 | 73 | // Testing Map with whitespace and deep equivalence 74 | DisableTrimWhiteSpace(true) 75 | m3, err := NewMapXml(whiteSpaceDataSeqTest2) 76 | if err != nil { 77 | t.Fatal(err) 78 | } 79 | 80 | m4 := Map(m3) 81 | 82 | if !cmp.Equal(m3, m4) { 83 | t.Errorf("Maps unmatched using %s", prefix.value) 84 | } 85 | DisableTrimWhiteSpace(false) 86 | 87 | // Testing MapSeq(Ordering) without whitespace and byte equivalence 88 | m5, err := NewMapFormattedXmlSeq(whiteSpaceDataSeqTest2) 89 | if err != nil { 90 | t.Fatal(err) 91 | } 92 | 93 | m6 := MapSeq(m5) 94 | 95 | if !cmp.Equal(m5, m6) { 96 | t.Errorf("Maps unmatched using %s", prefix.value) 97 | } 98 | 99 | // Testing Map without whitespace and deep equivalence 100 | m7, err := NewMapXml(whiteSpaceDataSeqTest2) 101 | if err != nil { 102 | t.Fatal(err) 103 | } 104 | 105 | m8 := Map(m7) 106 | 107 | if !cmp.Equal(m7, m8) { 108 | t.Errorf("Maps unmatched using %s", prefix.value) 109 | } 110 | }) 111 | 112 | } 113 | SetGlobalKeyMapPrefix("#") 114 | 115 | } 116 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/clbanning/mxj/v2 2 | 3 | go 1.15 4 | 5 | require github.com/google/go-cmp v0.5.9 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 2 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 3 | -------------------------------------------------------------------------------- /gob.go: -------------------------------------------------------------------------------- 1 | // gob.go - Encode/Decode a Map into a gob object. 2 | 3 | package mxj 4 | 5 | import ( 6 | "bytes" 7 | "encoding/gob" 8 | ) 9 | 10 | // NewMapGob returns a Map value for a gob object that has been 11 | // encoded from a map[string]interface{} (or compatible type) value. 12 | // It is intended to provide symmetric handling of Maps that have 13 | // been encoded using mv.Gob. 14 | func NewMapGob(gobj []byte) (Map, error) { 15 | m := make(map[string]interface{}, 0) 16 | if len(gobj) == 0 { 17 | return m, nil 18 | } 19 | r := bytes.NewReader(gobj) 20 | dec := gob.NewDecoder(r) 21 | if err := dec.Decode(&m); err != nil { 22 | return m, err 23 | } 24 | return m, nil 25 | } 26 | 27 | // Gob returns a gob-encoded value for the Map 'mv'. 28 | func (mv Map) Gob() ([]byte, error) { 29 | var buf bytes.Buffer 30 | enc := gob.NewEncoder(&buf) 31 | if err := enc.Encode(map[string]interface{}(mv)); err != nil { 32 | return nil, err 33 | } 34 | return buf.Bytes(), nil 35 | } 36 | -------------------------------------------------------------------------------- /gob_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "fmt" 7 | "testing" 8 | ) 9 | 10 | var gobData = map[string]interface{}{ 11 | "one": 1, 12 | "two": 2.0001, 13 | "three": "tres", 14 | "four": []int{1, 2, 3, 4}, 15 | "five": map[string]interface{}{"hi": "there"}} 16 | 17 | func TestGobHeader(t *testing.T) { 18 | fmt.Println("\n---------------- gob_test.go ...") 19 | } 20 | 21 | func TestNewMapGob(t *testing.T) { 22 | var buf bytes.Buffer 23 | gob.Register(gobData) 24 | enc := gob.NewEncoder(&buf) 25 | if err := enc.Encode(gobData); err != nil { 26 | t.Fatal("enc.Encode err:", err.Error()) 27 | } 28 | // decode 'buf' into a Map - map[string]interface{} 29 | m, err := NewMapGob(buf.Bytes()) 30 | if err != nil { 31 | t.Fatal("NewMapGob err:", err.Error()) 32 | } 33 | fmt.Printf("m: %v\n", m) 34 | } 35 | 36 | func TestMapGob(t *testing.T) { 37 | mv := Map(gobData) 38 | g, err := mv.Gob() 39 | if err != nil { 40 | t.Fatal("m.Gob err:", err.Error()) 41 | } 42 | // decode 'g' into a map[string]interface{} 43 | m := make(map[string]interface{}) 44 | r := bytes.NewReader(g) 45 | dec := gob.NewDecoder(r) 46 | if err := dec.Decode(&m); err != nil { 47 | t.Fatal("dec.Decode err:", err.Error()) 48 | } 49 | fmt.Printf("m: %v\n", m) 50 | } 51 | 52 | func TestGobSymmetric(t *testing.T) { 53 | mv := Map(gobData) 54 | fmt.Printf("mv: %v\n", mv) 55 | g, err := mv.Gob() 56 | if err != nil { 57 | t.Fatal("m.Gob err:", err.Error()) 58 | } 59 | m, err := NewMapGob(g) 60 | if err != nil { 61 | t.Fatal("NewMapGob err:", err.Error()) 62 | } 63 | fmt.Printf("m : %v\n", m) 64 | } 65 | -------------------------------------------------------------------------------- /isvalid_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestXmlCheckIsValid(t *testing.T) { 9 | fmt.Println("================== TestXmlCheckIsValid") 10 | XmlCheckIsValid() 11 | defer XmlCheckIsValid() 12 | 13 | data := []byte(`{"":"empty", "$invalid":"hex$", "entities":"<>&", "nil": null}`) 14 | m, err := NewMapJson(data) 15 | if err != nil { 16 | t.Fatal("NewMapJson err;", err) 17 | } 18 | fmt.Printf("%v\n", m) 19 | if _, err = m.Xml(); err == nil { 20 | t.Fatal("Xml err: nil") 21 | } 22 | if _, err = m.XmlIndent("", " "); err == nil { 23 | t.Fatal("XmlIndent err: nil") 24 | } 25 | 26 | data = []byte(`{"$invalid":"hex$", "entities":"<>&", "nil": null}`) 27 | m, err = NewMapJson(data) 28 | if err != nil { 29 | t.Fatal("NewMapJson err;", err) 30 | } 31 | fmt.Printf("%v\n", m) 32 | if _, err = m.Xml(); err == nil { 33 | t.Fatal("Xml err: nil") 34 | } 35 | if _, err = m.XmlIndent("", " "); err == nil { 36 | t.Fatal("XmlIndent err: nil") 37 | } 38 | 39 | data = []byte(`{"entities":"<>&", "nil": null}`) 40 | m, err = NewMapJson(data) 41 | if err != nil { 42 | t.Fatal("NewMapJson err;", err) 43 | } 44 | fmt.Printf("%v\n", m) 45 | if _, err = m.Xml(); err == nil { 46 | t.Fatal("Xml err: nil") 47 | } 48 | if _, err = m.XmlIndent("", " "); err == nil { 49 | t.Fatal("XmlIndent err: nil") 50 | } 51 | 52 | data = []byte(`{"nil": null}`) 53 | m, err = NewMapJson(data) 54 | if err != nil { 55 | t.Fatal("NewMapJson err;", err) 56 | } 57 | fmt.Printf("%v\n", m) 58 | if b, err := m.Xml(); err != nil { 59 | fmt.Println("m:", string(b)) 60 | t.Fatal("Xml err:", err) 61 | } 62 | if _, err = m.XmlIndent("", " "); err != nil { 63 | t.Fatal("XmlIndent err:", nil) 64 | } 65 | 66 | ms, err := NewMapXmlSeq([]byte(`element`)) 67 | fmt.Printf("%v\n", ms) 68 | if _, err = ms.Xml(); err != nil { 69 | t.Fatal("Xml err:", err) 70 | } 71 | 72 | if _, err = ms.XmlIndent("", " "); err != nil { 73 | t.Fatal("XmlIndent err:", err) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /j2x/j2x.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2014 Charles Banning. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file 4 | 5 | // j2x.go - For (mostly) backwards compatibility with legacy j2x package. 6 | // Wrappers for end-to-end JSON to XML transformation and value manipulation. 7 | package j2x 8 | 9 | import ( 10 | . "github.com/clbanning/mxj/v2" 11 | "io" 12 | ) 13 | 14 | // FromJson() --> map[string]interface{} 15 | func JsonToMap(jsonVal []byte) (map[string]interface{}, error) { 16 | return NewMapJson(jsonVal) 17 | } 18 | 19 | // interface{} --> ToJson (w/o safe encoding, default) { 20 | func MapToJson(m map[string]interface{}, safeEncoding ...bool) ([]byte, error) { 21 | return Map(m).Json() 22 | } 23 | 24 | // FromJson() --> ToXml(). 25 | func JsonToXml(jsonVal []byte) ([]byte, error) { 26 | m, err := NewMapJson(jsonVal) 27 | if err != nil { 28 | return nil, err 29 | } 30 | return m.Xml() 31 | } 32 | 33 | // FromJson() --> ToXmlWriter(). 34 | func JsonToXmlWriter(jsonVal []byte, xmlWriter io.Writer) error { 35 | m, err := NewMapJson(jsonVal) 36 | if err != nil { 37 | return err 38 | } 39 | return m.XmlWriter(xmlWriter) 40 | } 41 | 42 | // FromJsonReader() --> ToXml(). 43 | func JsonReaderToXml(jsonReader io.Reader) ([]byte, []byte, error) { 44 | m, jraw, err := NewMapJsonReaderRaw(jsonReader) 45 | if err != nil { 46 | return jraw, nil, err 47 | } 48 | x, xerr := m.Xml() 49 | return jraw, x, xerr 50 | } 51 | 52 | // FromJsonReader() --> ToXmlWriter(). Handy for transforming bulk message sets. 53 | func JsonReaderToXmlWriter(jsonReader io.Reader, xmlWriter io.Writer) error { 54 | m, err := NewMapJsonReader(jsonReader) 55 | if err != nil { 56 | return err 57 | } 58 | err = m.XmlWriter(xmlWriter) 59 | return err 60 | } 61 | 62 | // JSON wrappers for Map methods implementing key path and value functions. 63 | 64 | // Wrap PathsForKey for JSON. 65 | func JsonPathsForKey(jsonVal []byte, key string) ([]string, error) { 66 | m, err := NewMapJson(jsonVal) 67 | if err != nil { 68 | return nil, err 69 | } 70 | paths := m.PathsForKey(key) 71 | return paths, nil 72 | } 73 | 74 | // Wrap PathForKeyShortest for JSON. 75 | func JsonPathForKeyShortest(jsonVal []byte, key string) (string, error) { 76 | m, err := NewMapJson(jsonVal) 77 | if err != nil { 78 | return "", err 79 | } 80 | path := m.PathForKeyShortest(key) 81 | return path, nil 82 | } 83 | 84 | // Wrap ValuesForKey for JSON. 85 | func JsonValuesForKey(jsonVal []byte, key string, subkeys ...string) ([]interface{}, error) { 86 | m, err := NewMapJson(jsonVal) 87 | if err != nil { 88 | return nil, err 89 | } 90 | return m.ValuesForKey(key, subkeys...) 91 | } 92 | 93 | // Wrap ValuesForKeyPath for JSON. 94 | func JsonValuesForKeyPath(jsonVal []byte, path string, subkeys ...string) ([]interface{}, error) { 95 | m, err := NewMapJson(jsonVal) 96 | if err != nil { 97 | return nil, err 98 | } 99 | return m.ValuesForPath(path, subkeys...) 100 | } 101 | 102 | // Wrap UpdateValuesForPath for JSON 103 | // 'jsonVal' is XML value 104 | // 'newKeyValue' is the value to replace an existing value at the end of 'path' 105 | // 'path' is the dot-notation path with the key whose value is to be replaced at the end 106 | // (can include wildcard character, '*') 107 | // 'subkeys' are key:value pairs of key:values that must match for the key 108 | func JsonUpdateValsForPath(jsonVal []byte, newKeyValue interface{}, path string, subkeys ...string) ([]byte, error) { 109 | m, err := NewMapJson(jsonVal) 110 | if err != nil { 111 | return nil, err 112 | } 113 | _, err = m.UpdateValuesForPath(newKeyValue, path, subkeys...) 114 | if err != nil { 115 | return nil, err 116 | } 117 | return m.Json() 118 | } 119 | 120 | // Wrap NewMap for JSON and return as JSON 121 | // 'jsonVal' is an JSON value 122 | // 'keypairs' are "oldKey:newKey" values that conform to 'keypairs' in (Map)NewMap. 123 | func JsonNewJson(jsonVal []byte, keypairs ...string) ([]byte, error) { 124 | m, err := NewMapJson(jsonVal) 125 | if err != nil { 126 | return nil, err 127 | } 128 | n, err := m.NewMap(keypairs...) 129 | if err != nil { 130 | return nil, err 131 | } 132 | return n.Json() 133 | } 134 | 135 | // Wrap NewMap for JSON and return as XML 136 | // 'jsonVal' is an JSON value 137 | // 'keypairs' are "oldKey:newKey" values that conform to 'keypairs' in (Map)NewMap. 138 | func JsonNewXml(jsonVal []byte, keypairs ...string) ([]byte, error) { 139 | m, err := NewMapJson(jsonVal) 140 | if err != nil { 141 | return nil, err 142 | } 143 | n, err := m.NewMap(keypairs...) 144 | if err != nil { 145 | return nil, err 146 | } 147 | return n.Xml() 148 | } 149 | 150 | // Wrap LeafNodes for JSON. 151 | // 'jsonVal' is an JSON value 152 | func JsonLeafNodes(jsonVal []byte) ([]LeafNode, error) { 153 | m, err := NewMapJson(jsonVal) 154 | if err != nil { 155 | return nil, err 156 | } 157 | return m.LeafNodes(), nil 158 | } 159 | 160 | // Wrap LeafValues for JSON. 161 | // 'jsonVal' is an JSON value 162 | func JsonLeafValues(jsonVal []byte) ([]interface{}, error) { 163 | m, err := NewMapJson(jsonVal) 164 | if err != nil { 165 | return nil, err 166 | } 167 | return m.LeafValues(), nil 168 | } 169 | 170 | // Wrap LeafPath for JSON. 171 | // 'xmlVal' is an JSON value 172 | func JsonLeafPath(jsonVal []byte) ([]string, error) { 173 | m, err := NewMapJson(jsonVal) 174 | if err != nil { 175 | return nil, err 176 | } 177 | return m.LeafPaths(), nil 178 | } 179 | -------------------------------------------------------------------------------- /j2x/j2x_test.go: -------------------------------------------------------------------------------- 1 | // thanks to Chris Malek (chris.r.malek@gmail.com) for suggestion to handle JSON list docs. 2 | 3 | package j2x 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "io/ioutil" 9 | "testing" 10 | ) 11 | 12 | func TestJsonToXml_1(t *testing.T) { 13 | // mimic a io.Reader 14 | // Body := bytes.NewReader([]byte(`{"some-null-value":"", "a-non-null-value":"bar"}`)) 15 | Body := bytes.NewReader([]byte(`[{"some-null-value":"", "a-non-null-value":"bar"}]`)) 16 | 17 | //body, err := ioutil.ReadAll(req.Body) 18 | body, err := ioutil.ReadAll(Body) 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | fmt.Println(string(body)) 23 | //if err != nil { 24 | // t.Fatal(err) 25 | //} 26 | 27 | var xmloutput []byte 28 | //xmloutput, err = j2x.JsonToXml(body) 29 | xmloutput, err = JsonToXml(body) 30 | 31 | //log.Println(string(xmloutput)) 32 | 33 | if err != nil { 34 | t.Fatal(err) 35 | // log.Println(err) 36 | // http.Error(rw, "Could not convert to xml", 400) 37 | } 38 | fmt.Println("xmloutput:", string(xmloutput)) 39 | } 40 | 41 | func TestJsonToXml_2(t *testing.T) { 42 | // mimic a io.Reader 43 | Body := bytes.NewReader([]byte(`{"somekey":[{"value":"1st"},{"value":"2nd"}]}`)) 44 | 45 | //body, err := ioutil.ReadAll(req.Body) 46 | body, err := ioutil.ReadAll(Body) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | fmt.Println(string(body)) 51 | //if err != nil { 52 | // t.Fatal(err) 53 | //} 54 | 55 | var xmloutput []byte 56 | //xmloutput, err = j2x.JsonToXml(body) 57 | xmloutput, err = JsonToXml(body) 58 | 59 | //log.Println(string(xmloutput)) 60 | 61 | if err != nil { 62 | t.Fatal(err) 63 | // log.Println(err) 64 | // http.Error(rw, "Could not convert to xml", 400) 65 | } 66 | fmt.Println("xmloutput:", string(xmloutput)) 67 | } 68 | -------------------------------------------------------------------------------- /j2x_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | var jjdata = []byte(`{ "key1":"string", "key2":34, "key3":true, "key4":"unsafe: <>&", "key5":null }`) 9 | 10 | func TestJ2XHeader(t *testing.T) { 11 | fmt.Println("\n---------------- j2x_test .go ...") 12 | } 13 | 14 | func TestJ2X(t *testing.T) { 15 | 16 | m, err := NewMapJson(jjdata) 17 | if err != nil { 18 | t.Fatal("NewMapJson, err:", err) 19 | } 20 | 21 | x, err := m.Xml() 22 | if err != nil { 23 | t.Fatal("m.Xml(), err:", err) 24 | } 25 | 26 | fmt.Println("j2x, jdata:", string(jjdata)) 27 | fmt.Println("j2x, m :", m) 28 | fmt.Println("j2x, xml :", string(x)) 29 | } 30 | -------------------------------------------------------------------------------- /json_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "testing" 8 | ) 9 | 10 | var jdata = []byte(`{ "key1":"string", "key2":34, "key3":true, "key4":"unsafe: <>&", "key5":null }`) 11 | var jdata2 = []byte(`{ "key1":"string", "key2":34, "key3":true, "key4":"unsafe: <>&" }, 12 | { "key":"value in new JSON string" }`) 13 | 14 | func TestJsonHeader(t *testing.T) { 15 | fmt.Println("\n---------------- json_test.go ...") 16 | } 17 | 18 | func TestNewMapJson(t *testing.T) { 19 | 20 | m, merr := NewMapJson(jdata) 21 | if merr != nil { 22 | t.Fatal("NewMapJson, merr:", merr.Error()) 23 | } 24 | 25 | fmt.Println("NewMapJson, jdata:", string(jdata)) 26 | fmt.Printf("NewMapJson, m : %#v\n", m) 27 | } 28 | 29 | func TestNewMapJsonNumber(t *testing.T) { 30 | 31 | JsonUseNumber = true 32 | 33 | m, merr := NewMapJson(jdata) 34 | if merr != nil { 35 | t.Fatal("NewMapJson, merr:", merr.Error()) 36 | } 37 | 38 | fmt.Println("NewMapJson, jdata:", string(jdata)) 39 | fmt.Printf("NewMapJson, m : %#v\n", m) 40 | 41 | JsonUseNumber = false 42 | } 43 | 44 | func TestNewMapJsonError(t *testing.T) { 45 | 46 | m, merr := NewMapJson(jdata[:len(jdata)-2]) 47 | if merr == nil { 48 | t.Fatal("NewMapJsonError, m:", m) 49 | } 50 | 51 | fmt.Println("NewMapJsonError, jdata :", string(jdata[:len(jdata)-2])) 52 | fmt.Println("NewMapJsonError, merror:", merr.Error()) 53 | 54 | newData := []byte(`{ "this":"is", "in":error }`) 55 | m, merr = NewMapJson(newData) 56 | if merr == nil { 57 | t.Fatal("NewMapJsonError, m:", m) 58 | } 59 | 60 | fmt.Println("NewMapJsonError, newData :", string(newData)) 61 | fmt.Println("NewMapJsonError, merror :", merr.Error()) 62 | } 63 | 64 | func TestNewMapJsonReader(t *testing.T) { 65 | 66 | rdr := bytes.NewBuffer(jdata2) 67 | 68 | for { 69 | m, jb, merr := NewMapJsonReaderRaw(rdr) 70 | if merr != nil && merr != io.EOF { 71 | t.Fatal("NewMapJsonReader, merr:", merr.Error()) 72 | } 73 | if merr == io.EOF { 74 | break 75 | } 76 | 77 | fmt.Println("NewMapJsonReader, jb:", string(jb)) 78 | fmt.Printf("NewMapJsonReader, m : %#v\n", m) 79 | } 80 | } 81 | 82 | func TestNewMapJsonReaderNumber(t *testing.T) { 83 | 84 | JsonUseNumber = true 85 | 86 | rdr := bytes.NewBuffer(jdata2) 87 | 88 | for { 89 | m, jb, merr := NewMapJsonReaderRaw(rdr) 90 | if merr != nil && merr != io.EOF { 91 | t.Fatal("NewMapJsonReader, merr:", merr.Error()) 92 | } 93 | if merr == io.EOF { 94 | break 95 | } 96 | 97 | fmt.Println("NewMapJsonReader, jb:", string(jb)) 98 | fmt.Printf("NewMapJsonReader, m : %#v\n", m) 99 | } 100 | 101 | JsonUseNumber = false 102 | } 103 | 104 | func TestJson(t *testing.T) { 105 | 106 | m, _ := NewMapJson(jdata) 107 | 108 | j, jerr := m.Json() 109 | if jerr != nil { 110 | t.Fatal("Json, jerr:", jerr.Error()) 111 | } 112 | 113 | fmt.Println("Json, jdata:", string(jdata)) 114 | fmt.Println("Json, j :", string(j)) 115 | 116 | j, _ = m.Json(true) 117 | fmt.Println("Json, j safe:", string(j)) 118 | } 119 | 120 | func TestJsonWriter(t *testing.T) { 121 | mv := Map(map[string]interface{}{"this": "is a", "float": 3.14159, "and": "a", "bool": true}) 122 | 123 | w := new(bytes.Buffer) 124 | raw, err := mv.JsonWriterRaw(w) 125 | if err != nil { 126 | t.Fatal("err:", err.Error()) 127 | } 128 | 129 | b := make([]byte, w.Len()) 130 | _, err = w.Read(b) 131 | if err != nil { 132 | t.Fatal("err:", err.Error()) 133 | } 134 | 135 | fmt.Println("JsonWriter, raw:", string(raw)) 136 | fmt.Println("JsonWriter, b :", string(b)) 137 | } 138 | -------------------------------------------------------------------------------- /keystolower_test.go: -------------------------------------------------------------------------------- 1 | // keystolower_test.go 2 | 3 | package mxj 4 | 5 | import ( 6 | "fmt" 7 | "testing" 8 | ) 9 | 10 | var tolowerdata1 = []byte(` 11 | 12 | value 13 | 14 | `) 15 | 16 | var tolowerdata2 = []byte(` 17 | 18 | value 19 | 20 | `) 21 | 22 | func TestToLower(t *testing.T) { 23 | fmt.Println("\n-------------- keystolower_test.go") 24 | fmt.Println("\nTestToLower ...") 25 | 26 | CoerceKeysToLower() 27 | defer CoerceKeysToLower() 28 | 29 | m1, err := NewMapXml(tolowerdata1) 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | m2, err := NewMapXml(tolowerdata2) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | 38 | v1, err := m1.ValuesForPath("doc.element") 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | v2, err := m2.ValuesForPath("doc.element") 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | 47 | if len(v1) != len(v2) { 48 | t.Fatal(err, len(v1), len(v2)) 49 | } 50 | 51 | m := v1[0].(map[string]interface{}) 52 | mm := v2[0].(map[string]interface{}) 53 | for k, v := range m { 54 | if vv, ok := mm[k]; !ok { 55 | t.Fatal("key:", k, "not in mm") 56 | } else if v.(string) != vv.(string) { 57 | t.Fatal(v.(string), "not in v2:", vv.(string)) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /keyvalues2_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestSetSubkeyFieldSeparator(t *testing.T) { 9 | PrependAttrWithHyphen(true) 10 | 11 | fmt.Println("----------- TestSetSubkeyFieldSeparator") 12 | data := ` 13 | 14 | value 1 15 | value 2 16 | value 3 17 | ` 18 | 19 | m, err := NewMapXml([]byte(data)) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | vals, err := m.ValuesForKey("elem", "-attr:2:text") 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | if len(vals) != 1 { 28 | t.Fatal(":len(vals);", len(vals), vals) 29 | } 30 | if vals[0].(map[string]interface{})[textK].(string) != "value 2" { 31 | t.Fatal(":expecting: value 2; got:", vals[0].(map[string]interface{})[textK]) 32 | } 33 | 34 | SetFieldSeparator("|") 35 | defer SetFieldSeparator() 36 | vals, err = m.ValuesForKey("elem", "-attr|2|text") 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | if len(vals) != 1 { 41 | t.Fatal("|len(vals);", len(vals), vals) 42 | } 43 | if vals[0].(map[string]interface{})[textK].(string) != "value 2" { 44 | t.Fatal("|expecting: value 2; got:", vals[0].(map[string]interface{})[textK]) 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /leafnode.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | // leafnode.go - return leaf nodes with paths and values for the Map 4 | // inspired by: https://groups.google.com/forum/#!topic/golang-nuts/3JhuVKRuBbw 5 | 6 | import ( 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | const ( 12 | NoAttributes = true // suppress LeafNode values that are attributes 13 | ) 14 | 15 | // LeafNode - a terminal path value in a Map. 16 | // For XML Map values it represents an attribute or simple element value - of type 17 | // string unless Map was created using Cast flag. For JSON Map values it represents 18 | // a string, numeric, boolean, or null value. 19 | type LeafNode struct { 20 | Path string // a dot-notation representation of the path with array subscripting 21 | Value interface{} // the value at the path termination 22 | } 23 | 24 | // LeafNodes - returns an array of all LeafNode values for the Map. 25 | // The option no_attr argument suppresses attribute values (keys with prepended hyphen, '-') 26 | // as well as the "#text" key for the associated simple element value. 27 | // 28 | // PrependAttrWithHypen(false) will result in attributes having .attr-name as 29 | // terminal node in 'path' while the path for the element value, itself, will be 30 | // the base path w/o "#text". 31 | // 32 | // LeafUseDotNotation(true) causes list members to be identified using ".N" syntax 33 | // rather than "[N]" syntax. 34 | func (mv Map) LeafNodes(no_attr ...bool) []LeafNode { 35 | var a bool 36 | if len(no_attr) == 1 { 37 | a = no_attr[0] 38 | } 39 | 40 | l := make([]LeafNode, 0) 41 | getLeafNodes("", "", map[string]interface{}(mv), &l, a) 42 | return l 43 | } 44 | 45 | func getLeafNodes(path, node string, mv interface{}, l *[]LeafNode, noattr bool) { 46 | // if stripping attributes, then also strip "#text" key 47 | if !noattr || node != textK { 48 | if path != "" && node[:1] != "[" { 49 | path += "." 50 | } 51 | path += node 52 | } 53 | switch mv.(type) { 54 | case map[string]interface{}: 55 | for k, v := range mv.(map[string]interface{}) { 56 | // if noattr && k[:1] == "-" { 57 | if noattr && len(attrPrefix) > 0 && strings.Index(k, attrPrefix) == 0 { 58 | continue 59 | } 60 | getLeafNodes(path, k, v, l, noattr) 61 | } 62 | case []interface{}: 63 | for i, v := range mv.([]interface{}) { 64 | if useDotNotation { 65 | getLeafNodes(path, strconv.Itoa(i), v, l, noattr) 66 | } else { 67 | getLeafNodes(path, "["+strconv.Itoa(i)+"]", v, l, noattr) 68 | } 69 | } 70 | default: 71 | // can't walk any further, so create leaf 72 | n := LeafNode{path, mv} 73 | *l = append(*l, n) 74 | } 75 | } 76 | 77 | // LeafPaths - all paths that terminate in LeafNode values. 78 | func (mv Map) LeafPaths(no_attr ...bool) []string { 79 | ln := mv.LeafNodes() 80 | ss := make([]string, len(ln)) 81 | for i := 0; i < len(ln); i++ { 82 | ss[i] = ln[i].Path 83 | } 84 | return ss 85 | } 86 | 87 | // LeafValues - all terminal values in the Map. 88 | func (mv Map) LeafValues(no_attr ...bool) []interface{} { 89 | ln := mv.LeafNodes() 90 | vv := make([]interface{}, len(ln)) 91 | for i := 0; i < len(ln); i++ { 92 | vv[i] = ln[i].Value 93 | } 94 | return vv 95 | } 96 | 97 | // ====================== utilities ====================== 98 | 99 | // https://groups.google.com/forum/#!topic/golang-nuts/pj0C5IrZk4I 100 | var useDotNotation bool 101 | 102 | // LeafUseDotNotation sets a flag that list members in LeafNode paths 103 | // should be identified using ".N" syntax rather than the default "[N]" 104 | // syntax. Calling LeafUseDotNotation with no arguments toggles the 105 | // flag on/off; otherwise, the argument sets the flag value 'true'/'false'. 106 | func LeafUseDotNotation(b ...bool) { 107 | if len(b) == 0 { 108 | useDotNotation = !useDotNotation 109 | return 110 | } 111 | useDotNotation = b[0] 112 | } 113 | -------------------------------------------------------------------------------- /leafnode_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestLNHeader(t *testing.T) { 9 | fmt.Println("\n---------------- leafnode_test.go ...") 10 | } 11 | 12 | func TestLeafNodes(t *testing.T) { 13 | json1 := []byte(`{ 14 | "friends": [ 15 | { 16 | "skills": [ 17 | 44, 12 18 | ] 19 | } 20 | ] 21 | }`) 22 | 23 | json2 := []byte(`{ 24 | "friends": 25 | { 26 | "skills": [ 27 | 44, 12 28 | ] 29 | } 30 | 31 | }`) 32 | 33 | m, _ := NewMapJson(json1) 34 | ln := m.LeafNodes() 35 | fmt.Println("\njson1-LeafNodes:") 36 | for _, v := range ln { 37 | fmt.Printf("%#v\n", v) 38 | } 39 | p := m.LeafPaths() 40 | fmt.Println("\njson1-LeafPaths:") 41 | for _, v := range p { 42 | fmt.Printf("%#v\n", v) 43 | } 44 | 45 | m, _ = NewMapJson(json2) 46 | ln = m.LeafNodes() 47 | fmt.Println("\njson2-LeafNodes:") 48 | for _, v := range ln { 49 | fmt.Printf("%#v\n", v) 50 | } 51 | v := m.LeafValues() 52 | fmt.Println("\njson1-LeafValues:") 53 | for _, v := range v { 54 | fmt.Printf("%#v\n", v) 55 | } 56 | 57 | json3 := []byte(`{ "a":"list", "of":["data", "of", 3, "types", true]}`) 58 | m, _ = NewMapJson(json3) 59 | ln = m.LeafNodes() 60 | fmt.Println("\njson3-LeafNodes:") 61 | for _, v := range ln { 62 | fmt.Printf("%#v\n", v) 63 | } 64 | v = m.LeafValues() 65 | fmt.Println("\njson3-LeafValues:") 66 | for _, v := range v { 67 | fmt.Printf("%#v\n", v) 68 | } 69 | p = m.LeafPaths() 70 | fmt.Println("\njson3-LeafPaths:") 71 | for _, v := range p { 72 | fmt.Printf("%#v\n", v) 73 | } 74 | 75 | xmldata2 := []byte(` 76 | 77 | Item 2 is blue 78 | 79 | 80 | 81 | 82 | `) 83 | m, err := NewMapXml(xmldata2) 84 | if err != nil { 85 | t.Fatal(err.Error()) 86 | } 87 | fmt.Println("\nxml2data2-LeafValues:") 88 | ln = m.LeafNodes() 89 | for _, v := range ln { 90 | fmt.Printf("%#v\n", v) 91 | } 92 | fmt.Println("\nxml2data2-LeafValues(NoAttributes):") 93 | ln = m.LeafNodes(NoAttributes) 94 | for _, v := range ln { 95 | fmt.Printf("%#v\n", v) 96 | } 97 | 98 | // no-hyphen 99 | PrependAttrWithHyphen(false) 100 | m, err = NewMapXml(xmldata2) 101 | if err != nil { 102 | t.Fatal(err.Error()) 103 | } 104 | fmt.Println("\nno-hyphen-xml2data2-LeafValues:") 105 | ln = m.LeafNodes() 106 | for _, v := range ln { 107 | fmt.Printf("%#v\n", v) 108 | } 109 | fmt.Println("\nno-hyphen-xml2data2-LeafValues(NoAttributes):") 110 | ln = m.LeafNodes(NoAttributes) 111 | for _, v := range ln { 112 | fmt.Printf("%#v\n", v) 113 | } 114 | 115 | // restore default 116 | PrependAttrWithHyphen(true) 117 | } 118 | 119 | func TestLeafDotNotation(t *testing.T) { 120 | xmldata2 := []byte(` 121 | 122 | Item 2 is blue 123 | 124 | 125 | 126 | 127 | `) 128 | m, err := NewMapXml(xmldata2) 129 | if err != nil { 130 | t.Fatal(err.Error()) 131 | } 132 | fmt.Println("\nDotNotation-LeafValues:") 133 | LeafUseDotNotation() 134 | defer LeafUseDotNotation() 135 | ln := m.LeafNodes() 136 | for _, v := range ln { 137 | fmt.Printf("%#v\n", v) 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /misc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Charles Banning. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file 4 | 5 | // misc.go - mimic functions (+others) called out in: 6 | // https://groups.google.com/forum/#!topic/golang-nuts/jm_aGsJNbdQ 7 | // Primarily these methods let you retrive XML structure information. 8 | 9 | package mxj 10 | 11 | import ( 12 | "fmt" 13 | "sort" 14 | "strings" 15 | ) 16 | 17 | // Return the root element of the Map. If there is not a single key in Map, 18 | // then an error is returned. 19 | func (mv Map) Root() (string, error) { 20 | mm := map[string]interface{}(mv) 21 | if len(mm) != 1 { 22 | return "", fmt.Errorf("Map does not have singleton root. Len: %d.", len(mm)) 23 | } 24 | for k, _ := range mm { 25 | return k, nil 26 | } 27 | return "", nil 28 | } 29 | 30 | // If the path is an element with sub-elements, return a list of the sub-element 31 | // keys. (The list is alphabeticly sorted.) NOTE: Map keys that are prefixed with 32 | // '-', a hyphen, are considered attributes; see m.Attributes(path). 33 | func (mv Map) Elements(path string) ([]string, error) { 34 | e, err := mv.ValueForPath(path) 35 | if err != nil { 36 | return nil, err 37 | } 38 | switch e.(type) { 39 | case map[string]interface{}: 40 | ee := e.(map[string]interface{}) 41 | elems := make([]string, len(ee)) 42 | var i int 43 | for k, _ := range ee { 44 | if len(attrPrefix) > 0 && strings.Index(k, attrPrefix) == 0 { 45 | continue // skip attributes 46 | } 47 | elems[i] = k 48 | i++ 49 | } 50 | elems = elems[:i] 51 | // alphabetic sort keeps things tidy 52 | sort.Strings(elems) 53 | return elems, nil 54 | } 55 | return nil, fmt.Errorf("no elements for path: %s", path) 56 | } 57 | 58 | // If the path is an element with attributes, return a list of the attribute 59 | // keys. (The list is alphabeticly sorted.) NOTE: Map keys that are not prefixed with 60 | // '-', a hyphen, are not treated as attributes; see m.Elements(path). Also, if the 61 | // attribute prefix is "" - SetAttrPrefix("") or PrependAttrWithHyphen(false) - then 62 | // there are no identifiable attributes. 63 | func (mv Map) Attributes(path string) ([]string, error) { 64 | a, err := mv.ValueForPath(path) 65 | if err != nil { 66 | return nil, err 67 | } 68 | switch a.(type) { 69 | case map[string]interface{}: 70 | aa := a.(map[string]interface{}) 71 | attrs := make([]string, len(aa)) 72 | var i int 73 | for k, _ := range aa { 74 | if len(attrPrefix) == 0 || strings.Index(k, attrPrefix) != 0 { 75 | continue // skip non-attributes 76 | } 77 | attrs[i] = k[len(attrPrefix):] 78 | i++ 79 | } 80 | attrs = attrs[:i] 81 | // alphabetic sort keeps things tidy 82 | sort.Strings(attrs) 83 | return attrs, nil 84 | } 85 | return nil, fmt.Errorf("no attributes for path: %s", path) 86 | } 87 | -------------------------------------------------------------------------------- /mxj.go: -------------------------------------------------------------------------------- 1 | // mxj - A collection of map[string]interface{} and associated XML and JSON utilities. 2 | // Copyright 2012-2014 Charles Banning. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file 5 | 6 | package mxj 7 | 8 | import ( 9 | "fmt" 10 | "sort" 11 | ) 12 | 13 | const ( 14 | Cast = true // for clarity - e.g., mxj.NewMapXml(doc, mxj.Cast) 15 | SafeEncoding = true // ditto - e.g., mv.Json(mxj.SafeEncoding) 16 | ) 17 | 18 | type Map map[string]interface{} 19 | 20 | // Allocate a Map. 21 | func New() Map { 22 | m := make(map[string]interface{}, 0) 23 | return m 24 | } 25 | 26 | // Cast a Map to map[string]interface{} 27 | func (mv Map) Old() map[string]interface{} { 28 | return mv 29 | } 30 | 31 | // Return a copy of mv as a newly allocated Map. If the Map only contains string, 32 | // numeric, map[string]interface{}, and []interface{} values, then it can be thought 33 | // of as a "deep copy." Copying a structure (or structure reference) value is subject 34 | // to the noted restrictions. 35 | // NOTE: If 'mv' includes structure values with, possibly, JSON encoding tags 36 | // then only public fields of the structure are in the new Map - and with 37 | // keys that conform to any encoding tag instructions. The structure itself will 38 | // be represented as a map[string]interface{} value. 39 | func (mv Map) Copy() (Map, error) { 40 | // this is the poor-man's deep copy 41 | // not efficient, but it works 42 | j, jerr := mv.Json() 43 | // must handle, we don't know how mv got built 44 | if jerr != nil { 45 | return nil, jerr 46 | } 47 | return NewMapJson(j) 48 | } 49 | 50 | // --------------- StringIndent ... from x2j.WriteMap ------------- 51 | 52 | // Pretty print a Map. 53 | func (mv Map) StringIndent(offset ...int) string { 54 | return writeMap(map[string]interface{}(mv), true, true, offset...) 55 | } 56 | 57 | // Pretty print a Map without the value type information - just key:value entries. 58 | func (mv Map) StringIndentNoTypeInfo(offset ...int) string { 59 | return writeMap(map[string]interface{}(mv), false, true, offset...) 60 | } 61 | 62 | // writeMap - dumps the map[string]interface{} for examination. 63 | // 'typeInfo' causes value type to be printed. 64 | // 'offset' is initial indentation count; typically: Write(m). 65 | func writeMap(m interface{}, typeInfo, root bool, offset ...int) string { 66 | var indent int 67 | if len(offset) == 1 { 68 | indent = offset[0] 69 | } 70 | 71 | var s string 72 | switch m.(type) { 73 | case []interface{}: 74 | if typeInfo { 75 | s += "[[]interface{}]" 76 | } 77 | for _, v := range m.([]interface{}) { 78 | s += "\n" 79 | for i := 0; i < indent; i++ { 80 | s += " " 81 | } 82 | s += writeMap(v, typeInfo, false, indent+1) 83 | } 84 | case map[string]interface{}: 85 | list := make([][2]string, len(m.(map[string]interface{}))) 86 | var n int 87 | for k, v := range m.(map[string]interface{}) { 88 | list[n][0] = k 89 | list[n][1] = writeMap(v, typeInfo, false, indent+1) 90 | n++ 91 | } 92 | sort.Sort(mapList(list)) 93 | for _, v := range list { 94 | if root { 95 | root = false 96 | } else { 97 | s += "\n" 98 | } 99 | for i := 0; i < indent; i++ { 100 | s += " " 101 | } 102 | s += v[0] + " : " + v[1] 103 | } 104 | default: 105 | if typeInfo { 106 | s += fmt.Sprintf("[%T] %+v", m, m) 107 | } else { 108 | s += fmt.Sprintf("%+v", m) 109 | } 110 | } 111 | return s 112 | } 113 | 114 | // ======================== utility =============== 115 | 116 | type mapList [][2]string 117 | 118 | func (ml mapList) Len() int { 119 | return len(ml) 120 | } 121 | 122 | func (ml mapList) Swap(i, j int) { 123 | ml[i], ml[j] = ml[j], ml[i] 124 | } 125 | 126 | func (ml mapList) Less(i, j int) bool { 127 | return ml[i][0] <= ml[j][0] 128 | } 129 | -------------------------------------------------------------------------------- /mxj_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestMxjHeader(t *testing.T) { 9 | fmt.Println("\n---------------- mxj_test.go ...") 10 | } 11 | 12 | func TestMap(t *testing.T) { 13 | m := New() 14 | 15 | m["key"] = interface{}("value") 16 | v := map[string]interface{}{"bool": true, "float": 3.14159, "string": "Now is the time"} 17 | vv := []interface{}{3.1415962535, false, "for all good men"} 18 | v["listkey"] = interface{}(vv) 19 | m["newkey"] = interface{}(v) 20 | 21 | fmt.Println("TestMap, m:") 22 | fmt.Printf("%#v\n", m) 23 | fmt.Println("TestMap, StringIndent -") 24 | fmt.Println(m.StringIndent()) 25 | fmt.Println("TestMap, StringIndent NoTypeInfo -") 26 | fmt.Println(m.StringIndentNoTypeInfo()) 27 | 28 | o := interface{}(m.Old()) 29 | switch o.(type) { 30 | case map[string]interface{}: 31 | // do nothing 32 | default: 33 | t.Fatal("invalid type for m.Old()") 34 | } 35 | 36 | m, _ = NewMapXml([]byte(`HelloWorld`)) 37 | fmt.Println("TestMap, m_fromXML:") 38 | fmt.Printf("%#v\n", m) 39 | fmt.Println("TestMap, StringIndent -") 40 | fmt.Println( m.StringIndent()) 41 | fmt.Println("TestMap, StringIndent NoTypeInfo -") 42 | fmt.Println( m.StringIndentNoTypeInfo()) 43 | 44 | mm, _ := m.Copy() 45 | fmt.Println("TestMap, m.Copy() -\n", mm) 46 | } 47 | -------------------------------------------------------------------------------- /namespace_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestNamespaceHeader(t *testing.T) { 9 | fmt.Println("\n---------------- namespace_test.go ...") 10 | } 11 | 12 | func TestBeautifyXml(t *testing.T) { 13 | fmt.Println("\n---------------- TestBeautifyXml ...") 14 | const flatxml = `123John Brown` 15 | v, err := BeautifyXml([]byte(flatxml), "", " ") 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | fmt.Println(flatxml) 20 | fmt.Println(string(v)) 21 | } 22 | -------------------------------------------------------------------------------- /nan_test.go: -------------------------------------------------------------------------------- 1 | // nan_test.go 2 | 3 | package mxj 4 | 5 | import ( 6 | "fmt" 7 | "testing" 8 | ) 9 | 10 | func TestNan(t *testing.T) { 11 | fmt.Println("\n------------ TestNan ...") 12 | data := []byte("NAN") 13 | 14 | m, err := NewMapXml(data, true) 15 | if err != nil { 16 | t.Fatal("err:", err) 17 | } 18 | v, err := m.ValueForPath("foo.bar") 19 | if err != nil { 20 | t.Fatal("err:", err) 21 | } 22 | if _, ok := v.(string); !ok { 23 | t.Fatal("v not string") 24 | } 25 | fmt.Println("foo.bar:", v) 26 | } 27 | 28 | func TestInf(t *testing.T) { 29 | data := []byte("INF") 30 | 31 | m, err := NewMapXml(data, true) 32 | if err != nil { 33 | t.Fatal("err:", err) 34 | } 35 | v, err := m.ValueForPath("foo.bar") 36 | if err != nil { 37 | t.Fatal("err:", err) 38 | } 39 | if _, ok := v.(string); !ok { 40 | t.Fatal("v not string") 41 | } 42 | fmt.Println("foo.bar:", v) 43 | } 44 | 45 | func TestMinusInf(t *testing.T) { 46 | data := []byte("-INF") 47 | 48 | m, err := NewMapXml(data, true) 49 | if err != nil { 50 | t.Fatal("err:", err) 51 | } 52 | v, err := m.ValueForPath("foo.bar") 53 | if err != nil { 54 | t.Fatal("err:", err) 55 | } 56 | if _, ok := v.(string); !ok { 57 | t.Fatal("v not string") 58 | } 59 | fmt.Println("foo.bar:", v) 60 | } 61 | 62 | func TestCastNanInf(t *testing.T) { 63 | data := []byte("NAN") 64 | 65 | CastNanInf(true) 66 | 67 | m, err := NewMapXml(data, true) 68 | if err != nil { 69 | t.Fatal("err:", err) 70 | } 71 | v, err := m.ValueForPath("foo.bar") 72 | if err != nil { 73 | t.Fatal("err:", err) 74 | } 75 | if _, ok := v.(float64); !ok { 76 | fmt.Printf("%#v\n", v) 77 | t.Fatal("v not float64") 78 | } 79 | fmt.Println("foo.bar:", v) 80 | } 81 | 82 | -------------------------------------------------------------------------------- /newmap_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "testing" 8 | ) 9 | 10 | func TestNewMapHeader(t *testing.T) { 11 | fmt.Println("\n---------------- newmap_test.go ...") 12 | } 13 | 14 | func TestNewMap(t *testing.T) { 15 | j := []byte(`{ "A":"this", "B":"is", "C":"a", "D":"test" }`) 16 | fmt.Println("j:", string(j)) 17 | 18 | m, _ := NewMapJson(j) 19 | fmt.Printf("m: %#v\n", m) 20 | 21 | fmt.Println("\n", `eval - m.NewMap("A:AA", "B:BB", "C:cc", "D:help")`) 22 | n, err := m.NewMap("A:AA", "B:BB", "C:cc", "D:help") 23 | if err != nil { 24 | fmt.Println("err:", err.Error()) 25 | } 26 | j, _ = n.Json() 27 | fmt.Println("n.Json():", string(j)) 28 | x, _ := n.Xml() 29 | fmt.Println("n.Xml():\n", string(x)) 30 | x, _ = n.XmlIndent("", " ") 31 | fmt.Println("n.XmlIndent():\n", string(x)) 32 | 33 | fmt.Println("\n", `eval - m.NewMap("A:AA.A", "B:AA.B", "C:AA.B.cc", "D:hello.help")`) 34 | n, err = m.NewMap("A:AA.A", "B:AA.B", "C:AA.B.cc", "D:hello.help") 35 | if err != nil { 36 | fmt.Println("err:", err.Error()) 37 | } 38 | j, _ = n.Json() 39 | fmt.Println("n.Json():", string(j)) 40 | x, _ = n.Xml() 41 | fmt.Println("n.Xml():\n", string(x)) 42 | x, _ = n.XmlIndent("", " ") 43 | fmt.Println("n.XmlIndent():\n", string(x)) 44 | 45 | var keypairs = []string{"A:xml.AA", "B:xml.AA.hello.again", "C:xml.AA", "D:xml.AA.hello.help"} 46 | fmt.Println("\n", `eval - m.NewMap keypairs:`, keypairs) 47 | n, err = m.NewMap(keypairs...) 48 | if err != nil { 49 | fmt.Println("err:", err.Error()) 50 | } 51 | j, _ = n.Json() 52 | fmt.Println("n.Json():", string(j)) 53 | x, _ = n.Xml() 54 | fmt.Println("n.Xml():\n", string(x)) 55 | x, _ = n.XmlIndent("", " ") 56 | fmt.Println("n.XmlIndent():\n", string(x)) 57 | } 58 | 59 | // Need to normalize from an XML stream the values for "netid" and "idnet". 60 | // Solution: make everything "netid" 61 | // Demo how to re-label a key using mv.NewMap() 62 | 63 | var msg1 = []byte(` 64 | 65 | 66 | 67 | no 68 | default:text 69 | default:word 70 | 71 | 72 | `) 73 | 74 | var msg2 = []byte(` 75 | 76 | 77 | 78 | yes 79 | default:text 80 | default:word 81 | 82 | 83 | `) 84 | 85 | func TestNetId(t *testing.T) { 86 | // let's create a message stream 87 | buf := new(bytes.Buffer) 88 | // load a couple of messages into it 89 | _, _ = buf.Write(msg1) 90 | _, _ = buf.Write(msg2) 91 | 92 | n := 0 93 | for { 94 | n++ 95 | // read the stream as Map values - quit on io.EOF 96 | m, raw, merr := NewMapXmlReaderRaw(buf) 97 | if merr != nil && merr != io.EOF { 98 | // handle error - for demo we just print it and continue 99 | fmt.Printf("msg: %d - merr: %s\n", n, merr.Error()) 100 | continue 101 | } else if merr == io.EOF { 102 | break 103 | } 104 | 105 | // the first keypair retains values if data correct 106 | // the second keypair relabels "idnet" to "netid" 107 | n, _ := m.NewMap("data.netid", "data.idnet:data.netid") 108 | x, _ := n.XmlIndent("", " ") 109 | 110 | fmt.Println("original value:", string(raw)) 111 | fmt.Println("new value:") 112 | fmt.Println(string(x)) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /remove.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import "strings" 4 | 5 | // Removes the path. 6 | func (mv Map) Remove(path string) error { 7 | m := map[string]interface{}(mv) 8 | return remove(m, path) 9 | } 10 | 11 | func remove(m interface{}, path string) error { 12 | val, err := prevValueByPath(m, path) 13 | if err != nil { 14 | return err 15 | } 16 | 17 | lastKey := lastKey(path) 18 | delete(val, lastKey) 19 | 20 | return nil 21 | } 22 | 23 | // returns the last key of the path. 24 | // lastKey("a.b.c") would had returned "c" 25 | func lastKey(path string) string { 26 | keys := strings.Split(path, ".") 27 | key := keys[len(keys)-1] 28 | return key 29 | } 30 | 31 | // returns the path without the last key 32 | // parentPath("a.b.c") whould had returned "a.b" 33 | func parentPath(path string) string { 34 | keys := strings.Split(path, ".") 35 | parentPath := strings.Join(keys[0:len(keys)-1], ".") 36 | return parentPath 37 | } 38 | -------------------------------------------------------------------------------- /remove_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestRemove(t *testing.T) { 8 | m := map[string]interface{}{ 9 | "Div": map[string]interface{}{ 10 | "Colour": "blue", 11 | }, 12 | } 13 | mv := Map(m) 14 | err := mv.Remove("Div.Colour") 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | if v, _ := mv.Exists("Div.Colour"); v { 19 | t.Fatal("removed key still remain") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /rename.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | // RenameKey renames a key in a Map. 9 | // It works only for nested maps. 10 | // It doesn't work for cases when the key is in a list. 11 | func (mv Map) RenameKey(path string, newName string) error { 12 | var v bool 13 | var err error 14 | if v, err = mv.Exists(path); err == nil && !v { 15 | return errors.New("RenameKey: path not found: " + path) 16 | } else if err != nil { 17 | return err 18 | } 19 | if v, err = mv.Exists(parentPath(path) + "." + newName); err == nil && v { 20 | return errors.New("RenameKey: key already exists: " + newName) 21 | } else if err != nil { 22 | return err 23 | } 24 | 25 | m := map[string]interface{}(mv) 26 | return renameKey(m, path, newName) 27 | } 28 | 29 | func renameKey(m interface{}, path string, newName string) error { 30 | val, err := prevValueByPath(m, path) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | oldName := lastKey(path) 36 | val[newName] = val[oldName] 37 | delete(val, oldName) 38 | 39 | return nil 40 | } 41 | 42 | // returns a value which contains a last key in the path 43 | // For example: prevValueByPath("a.b.c", {a{b{c: 3}}}) returns {c: 3} 44 | func prevValueByPath(m interface{}, path string) (map[string]interface{}, error) { 45 | keys := strings.Split(path, ".") 46 | 47 | switch mValue := m.(type) { 48 | case map[string]interface{}: 49 | for key, value := range mValue { 50 | if key == keys[0] { 51 | if len(keys) == 1 { 52 | return mValue, nil 53 | } else { 54 | // keep looking for the full path to the key 55 | return prevValueByPath(value, strings.Join(keys[1:], ".")) 56 | } 57 | } 58 | } 59 | } 60 | return nil, errors.New("prevValueByPath: didn't find path – " + path) 61 | } 62 | -------------------------------------------------------------------------------- /rename_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestRenameKey(t *testing.T) { 9 | fmt.Println("------------ rename_test.go") 10 | m := map[string]interface{}{ 11 | "Div": map[string]interface{}{ 12 | "Colour": "blue", 13 | "Width": "100%", 14 | }, 15 | } 16 | mv := Map(m) 17 | err := mv.RenameKey("Div.Colour", "Color") 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | values, err := mv.ValuesForPath("Div.Color") 22 | if len(values) != 1 { 23 | t.Fatal("didn't add the new key") 24 | } 25 | if values[0] != "blue" { 26 | t.Fatal("value is changed") 27 | } 28 | values, err = mv.ValuesForPath("Div.Colour") 29 | if len(values) > 0 { 30 | t.Fatal("didn't removed the old key") 31 | } 32 | 33 | err = mv.RenameKey("not.existing.path", "newname") 34 | if err == nil { 35 | t.Fatal("should raise an error on a non existing path") 36 | } 37 | 38 | err = mv.RenameKey("Div.Color", "Width") 39 | if err == nil { 40 | t.Fatal("should raise an error if the newName already exists") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /seqnum_test.go: -------------------------------------------------------------------------------- 1 | // seqnum.go 2 | 3 | package mxj 4 | 5 | import ( 6 | "fmt" 7 | "testing" 8 | ) 9 | 10 | var seqdata1 = []byte(` 11 | 12 | 13 | 14 | 15 | `) 16 | 17 | var seqdata2 = []byte(` 18 | 19 | 20 | 21 | 1 22 | hello 23 | true 24 | 25 | 26 | `) 27 | 28 | func TestSeqNumHeader(t *testing.T) { 29 | fmt.Println("\n---------------- seqnum_test.go ...") 30 | } 31 | 32 | func TestSeqNum(t *testing.T) { 33 | IncludeTagSeqNum(true) 34 | 35 | m, err := NewMapXml(seqdata1, Cast) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | fmt.Printf("m1: %#v\n", m) 40 | j, _ := m.JsonIndent("", " ") 41 | fmt.Println(string(j)) 42 | 43 | m, err = NewMapXml(seqdata2, Cast) 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | fmt.Printf("m2: %#v\n", m) 48 | j, _ = m.JsonIndent("", " ") 49 | fmt.Println(string(j)) 50 | 51 | IncludeTagSeqNum(false) 52 | } 53 | -------------------------------------------------------------------------------- /set.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Sets the value for the path 8 | func (mv Map) SetValueForPath(value interface{}, path string) error { 9 | pathAry := strings.Split(path, ".") 10 | parentPathAry := pathAry[0 : len(pathAry)-1] 11 | parentPath := strings.Join(parentPathAry, ".") 12 | 13 | val, err := mv.ValueForPath(parentPath) 14 | if err != nil { 15 | return err 16 | } 17 | if val == nil { 18 | return nil // we just ignore the request if there's no val 19 | } 20 | 21 | key := pathAry[len(pathAry)-1] 22 | cVal := val.(map[string]interface{}) 23 | cVal[key] = value 24 | 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /set_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSetValueForPath(t *testing.T) { 8 | m := map[string]interface{}{ 9 | "Div": map[string]interface{}{ 10 | "Colour": "blue", 11 | "Font": map[string]interface{}{ 12 | "Family": "sans", 13 | }, 14 | }, 15 | } 16 | mv := Map(m) 17 | 18 | // testing setting a new key 19 | err := mv.SetValueForPath("big", "Div.Font.Size") 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | val, err := mv.ValueForPathString("Div.Font.Size") 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | if val != "big" { 28 | t.Fatal("key's value hasn't changed") 29 | } 30 | 31 | // testing setting a new value to en existing key 32 | err = mv.SetValueForPath("red", "Div.Colour") 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | val, err = mv.ValueForPathString("Div.Colour") 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | if val != "red" { 41 | t.Fatal("existig key's value hasn't changed") 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /setfieldsep.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | // Per: https://github.com/clbanning/mxj/issues/37#issuecomment-278651862 4 | var fieldSep string = ":" 5 | 6 | // SetFieldSeparator changes the default field separator, ":", for the 7 | // newVal argument in mv.UpdateValuesForPath and the optional 'subkey' arguments 8 | // in mv.ValuesForKey and mv.ValuesForPath. 9 | // 10 | // E.g., if the newVal value is "http://blah/blah", setting the field separator 11 | // to "|" will allow the newVal specification, "|http://blah/blah" to parse 12 | // properly. If called with no argument or an empty string value, the field 13 | // separator is set to the default, ":". 14 | func SetFieldSeparator(s ...string) { 15 | if len(s) == 0 || s[0] == "" { 16 | fieldSep = ":" // the default 17 | return 18 | } 19 | fieldSep = s[0] 20 | } 21 | -------------------------------------------------------------------------------- /snakecase_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestStakeCase(t *testing.T) { 9 | PrependAttrWithHyphen(true) 10 | fmt.Println("\n----------- TestSnakeCase") 11 | CoerceKeysToSnakeCase() 12 | defer CoerceKeysToSnakeCase() 13 | 14 | data1 := `something` 15 | data2 := `something` 16 | 17 | m, err := NewMapXml([]byte(data1)) 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | 22 | x, err := m.Xml() 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | if string(x) != data2 { 27 | t.Fatal(string(x), "!=", data2) 28 | } 29 | 30 | // Use-case from: https://github.com/clbanning/mxj/pull/33#issuecomment-273724506 31 | data1 = ` 32 | 33 | srx100 34 | srx100b 35 | srx100b 36 | 37 | 38 | junos 39 | JUNOS Software Release [11.2R4.3] 40 | 41 | 42 | ` 43 | data2 = ` 44 | 45 | srx100 46 | srx100b 47 | srx100b 48 | 49 | 50 | junos 51 | JUNOS Software Release [11.2R4.3] 52 | 53 | 54 | ` 55 | 56 | ms, err := NewMapXmlSeq([]byte(data1)) 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | 61 | x, err = ms.XmlIndent("", "") 62 | if err != nil { 63 | t.Fatal(err) 64 | } 65 | if string(x) != data2 { 66 | t.Fatal(string(x), "!=", data2) 67 | } 68 | } 69 | 70 | -------------------------------------------------------------------------------- /songtext.xml: -------------------------------------------------------------------------------- 1 | 2 | help me! 3 | 4 | 5 | 6 | Henry was a renegade 7 | Didn't like to play it safe 8 | One component at a time 9 | There's got to be a better way 10 | Oh, people came from miles around 11 | Searching for a steady job 12 | Welcome to the Motor Town 13 | Booming like an atom bomb 14 | 15 | 16 | Oh, Henry was the end of the story 17 | Then everything went wrong 18 | And we'll return it to its former glory 19 | But it just takes so long 20 | 21 | 22 | 23 | It's going to take a long time 24 | It's going to take it, but we'll make it one day 25 | It's going to take a long time 26 | It's going to take it, but we'll make it one day 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /strict.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Charles Banning. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file 4 | 5 | // strict.go actually addresses setting xml.Decoder attribute 6 | // values. This'll let you parse non-standard XML. 7 | 8 | package mxj 9 | 10 | import ( 11 | "encoding/xml" 12 | ) 13 | 14 | // CustomDecoder can be used to specify xml.Decoder attribute 15 | // values, e.g., Strict:false, to be used. By default CustomDecoder 16 | // is nil. If CustomeDecoder != nil, then mxj.XmlCharsetReader variable is 17 | // ignored and must be set as part of the CustomDecoder value, if needed. 18 | // Usage: 19 | // mxj.CustomDecoder = &xml.Decoder{Strict:false} 20 | var CustomDecoder *xml.Decoder 21 | 22 | // useCustomDecoder copy over public attributes from customDecoder 23 | func useCustomDecoder(d *xml.Decoder) { 24 | d.Strict = CustomDecoder.Strict 25 | d.AutoClose = CustomDecoder.AutoClose 26 | d.Entity = CustomDecoder.Entity 27 | d.CharsetReader = CustomDecoder.CharsetReader 28 | d.DefaultSpace = CustomDecoder.DefaultSpace 29 | } 30 | 31 | -------------------------------------------------------------------------------- /strict_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestStrictModeXml(t *testing.T) { 10 | fmt.Println("----------------- TestStrictModeXml ...") 11 | data := []byte(` Bill & Hallett Duc & 123xx E `) 12 | 13 | CustomDecoder = &xml.Decoder{Strict:false} 14 | m, err := NewMapXml(data) 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | fmt.Println("m:",m) 19 | } 20 | 21 | func TestStrictModeXmlSeq(t *testing.T) { 22 | fmt.Println("----------------- TestStrictModeXmlSeq ...") 23 | data := []byte(` Bill & Hallett Duc & 123xx E `) 24 | 25 | CustomDecoder = &xml.Decoder{Strict:false} 26 | m, err := NewMapXmlSeq(data) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | fmt.Println("m:",m) 31 | } 32 | 33 | func TestStrictModeFail(t *testing.T) { 34 | fmt.Println("----------------- TestStrictFail ...") 35 | data := []byte(` Bill & Hallett Duc & 123xx E `) 36 | 37 | CustomDecoder = nil 38 | _, err := NewMapXml(data) 39 | if err == nil { 40 | t.Fatal("error not caught: NewMapXml") 41 | } 42 | _, err = NewMapXmlSeq(data) 43 | if err == nil { 44 | t.Fatal("error not caught: NewMapXmlSeq") 45 | } 46 | fmt.Println("OK") 47 | } 48 | 49 | -------------------------------------------------------------------------------- /struct.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2017 Charles Banning. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file 4 | 5 | package mxj 6 | 7 | import ( 8 | "encoding/json" 9 | "errors" 10 | "reflect" 11 | 12 | // "github.com/fatih/structs" 13 | ) 14 | 15 | // Create a new Map value from a structure. Error returned if argument is not a structure. 16 | // Only public structure fields are decoded in the Map value. See github.com/fatih/structs#Map 17 | // for handling of "structs" tags. 18 | 19 | // DEPRECATED - import github.com/fatih/structs and cast result of structs.Map to mxj.Map. 20 | // import "github.com/fatih/structs" 21 | // ... 22 | // sm, err := structs.Map() 23 | // if err != nil { 24 | // // handle error 25 | // } 26 | // m := mxj.Map(sm) 27 | // Alernatively uncomment the old source and import in struct.go. 28 | func NewMapStruct(structVal interface{}) (Map, error) { 29 | return nil, errors.New("deprecated - see package documentation") 30 | /* 31 | if !structs.IsStruct(structVal) { 32 | return nil, errors.New("NewMapStruct() error: argument is not type Struct") 33 | } 34 | return structs.Map(structVal), nil 35 | */ 36 | } 37 | 38 | // Marshal a map[string]interface{} into a structure referenced by 'structPtr'. Error returned 39 | // if argument is not a pointer or if json.Unmarshal returns an error. 40 | // json.Unmarshal structure encoding rules are followed to encode public structure fields. 41 | func (mv Map) Struct(structPtr interface{}) error { 42 | // should check that we're getting a pointer. 43 | if reflect.ValueOf(structPtr).Kind() != reflect.Ptr { 44 | return errors.New("mv.Struct() error: argument is not type Ptr") 45 | } 46 | 47 | m := map[string]interface{}(mv) 48 | j, err := json.Marshal(m) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | return json.Unmarshal(j, structPtr) 54 | } 55 | -------------------------------------------------------------------------------- /struct_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestStructHeader(t *testing.T) { 9 | fmt.Println("\n---------------- struct_test.go ...") 10 | } 11 | 12 | /* 13 | func TestNewMapStruct(t *testing.T) { 14 | type str struct { 15 | IntVal int `json:"int"` 16 | StrVal string `json:"str"` 17 | FloatVal float64 `json:"float"` 18 | BoolVal bool `json:"bool"` 19 | private string 20 | } 21 | s := str{IntVal: 4, StrVal: "now's the time", FloatVal: 3.14159, BoolVal: true, private: "It's my party"} 22 | 23 | m, merr := NewMapStruct(s) 24 | if merr != nil { 25 | t.Fatal("merr:", merr.Error()) 26 | } 27 | 28 | fmt.Printf("NewMapStruct, s: %#v\n", s) 29 | fmt.Printf("NewMapStruct, m: %#v\n", m) 30 | 31 | m, merr = NewMapStruct(s) 32 | if merr != nil { 33 | t.Fatal("merr:", merr.Error()) 34 | } 35 | 36 | fmt.Printf("NewMapStruct, s: %#v\n", s) 37 | fmt.Printf("NewMapStruct, m: %#v\n", m) 38 | } 39 | 40 | func TestNewMapStructError(t *testing.T) { 41 | var s string 42 | _, merr := NewMapStruct(s) 43 | if merr == nil { 44 | t.Fatal("NewMapStructError, merr is nil") 45 | } 46 | 47 | fmt.Println("NewMapStructError, merr:", merr.Error()) 48 | } 49 | */ 50 | 51 | func TestStruct(t *testing.T) { 52 | type str struct { 53 | IntVal int `json:"int"` 54 | StrVal string `json:"str"` 55 | FloatVal float64 `json:"float"` 56 | BoolVal bool `json:"bool"` 57 | private string 58 | } 59 | var s str 60 | m := Map{"int": 4, "str": "now's the time", "float": 3.14159, "bool": true, "private": "Somewhere over the rainbow"} 61 | 62 | mverr := m.Struct(&s) 63 | if mverr != nil { 64 | t.Fatal("mverr:", mverr.Error()) 65 | } 66 | 67 | fmt.Printf("Struct, m: %#v\n", m) 68 | fmt.Printf("Struct, s: %#v\n", s) 69 | } 70 | 71 | func TestStructError(t *testing.T) { 72 | type str struct { 73 | IntVal int `json:"int"` 74 | StrVal string `json:"str"` 75 | FloatVal float64 `json:"float"` 76 | BoolVal bool `json:"bool"` 77 | } 78 | var s str 79 | mv := Map{"int": 4, "str": "now's the time", "float": 3.14159, "bool": true} 80 | 81 | mverr := mv.Struct(s) 82 | if mverr == nil { 83 | t.Fatal("StructError, no error returned") 84 | } 85 | fmt.Println("StructError, mverr:", mverr.Error()) 86 | } 87 | -------------------------------------------------------------------------------- /structvalue_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | type result struct { 10 | XMLName xml.Name `xml:"xml"` 11 | Content string `xml:"content"` 12 | } 13 | 14 | func TestStructValue(t *testing.T) { 15 | fmt.Println("----------------- structvalue_test.go ...") 16 | 17 | data, err := Map(map[string]interface{}{ 18 | "data": result{Content: "content"}, 19 | }).Xml() 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | 24 | if string(data) != "content" { 25 | t.Fatal("encoding error:", string(data)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /x2j-wrapper/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2016 Charles Banning . 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Google Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /x2j-wrapper/README: -------------------------------------------------------------------------------- 1 | x2j.go - Unmarshal arbitrary XML docs to map[string]interface{} or JSON and extract values (using wildcards, if necessary). 2 | 3 | NOTICE: 4 | 5 | 26mar18: This is provided to aid transitioning applications using the "x2j" package to the "mxj" package. 6 | 7 | USAGE 8 | 9 | The package is fairly well self-documented. (http://godoc.org/github.com/clbanning/x2j) 10 | The one really useful function is: 11 | 12 | - Unmarshal(doc []byte, v interface{}) error 13 | where v is a pointer to a variable of type 'map[string]interface{}', 'string', or 14 | any other type supported by xml.Unmarshal(). 15 | 16 | To retrieve a value for specific tag use: 17 | 18 | - DocValue(doc, path string, attrs ...string) (interface{},error) 19 | - MapValue(m map[string]interface{}, path string, attr map[string]interface{}, recast ...bool) (interface{}, error) 20 | 21 | The 'path' argument is a period-separated tag hierarchy - also known as dot-notation. 22 | It is the program's responsibility to cast the returned value to the proper type; possible 23 | types are the normal JSON unmarshaling types: string, float64, bool, []interface, map[string]interface{}. 24 | 25 | To retrieve all values associated with a tag occurring anywhere in the XML document use: 26 | 27 | - ValuesForTag(doc, tag string) ([]interface{}, error) 28 | - ValuesForKey(m map[string]interface{}, key string) []interface{} 29 | 30 | Demos: http://play.golang.org/p/m8zP-cpk0O 31 | http://play.golang.org/p/cIteTS1iSg 32 | http://play.golang.org/p/vd8pMiI21b 33 | 34 | Returned values should be one of map[string]interface, []interface{}, or string. 35 | 36 | All the values assocated with a tag-path that may include one or more wildcard characters - 37 | '*' - can also be retrieved using: 38 | 39 | - ValuesFromTagPath(doc, path string, getAttrs ...bool) ([]interface{}, error) 40 | - ValuesFromKeyPath(map[string]interface{}, path string, getAttrs ...bool) []interface{} 41 | 42 | Demos: http://play.golang.org/p/kUQnZ8VuhS 43 | http://play.golang.org/p/l1aMHYtz7G 44 | 45 | NOTE: care should be taken when using "*" at the end of a path - i.e., "books.book.*". See 46 | the x2jpath_test.go case on how the wildcard returns all key values and collapses list values; 47 | the same message structure can load a []interface{} or a map[string]interface{} (or an interface{}) 48 | value for a tag. 49 | 50 | See the test cases in "x2jpath_test.go" and programs in "example" subdirectory for more. 51 | 52 | XML PARSING CONVENTIONS 53 | 54 | - Attributes are parsed to map[string]interface{} values by prefixing a hyphen, '-', 55 | to the attribute label. [See https://godoc.org/github.com/clbanning/mxj#SetAttrPrefix for options.] 56 | - If the element is a simple element and has attributes, the element value 57 | is given the key '#text' for its map[string]interface{} representation. (See 58 | the 'atomFeedString.xml' test data, below.) 59 | 60 | BULK PROCESSING OF MESSAGE FILES 61 | 62 | Sometime messages may be logged into files for transmission via FTP (e.g.) and subsequent 63 | processing. You can use the bulk XML message processor to convert files of XML messages into 64 | map[string]interface{} values with custom processing and error handler functions. See 65 | the notes and test code for: 66 | 67 | - XmlMsgsFromFile(fname string, phandler func(map[string]interface{}) bool, ehandler func(error) bool,recast ...bool) error 68 | [See https://godoc.org/github.com/clbanning/mxj#HandleXmlReader for general version.] 69 | 70 | IMPLEMENTATION NOTES 71 | 72 | Nothing fancy here, just brute force. 73 | 74 | - Use xml.Decoder to parse the XML doc and build a tree. 75 | - Walk the tree and load values into a map[string]interface{} variable, 'm', as 76 | appropriate. 77 | - Use json.Marshaler to convert 'm' to JSON. 78 | 79 | As for testing: 80 | 81 | - Copy an XML doc into 'x2j_test.xml'. 82 | - Run "go test" and you'll get a full dump. 83 | ("pathTestString.xml" and "atomFeedString.xml" are test data from "read_test.go" 84 | in the encoding/xml directory of the standard package library.) 85 | 86 | MOTIVATION 87 | 88 | I make extensive use of JSON for messaging and typically unmarshal the messages into 89 | map[string]interface{} variables. This is easily done using json.Unmarshal from the 90 | standard Go libraries. Unfortunately, many legacy solutions use structured 91 | XML messages; in those environments the applications would have to be refitted to 92 | interoperate with my components. 93 | 94 | The better solution is to just provide an alternative HTTP handler that receives 95 | XML doc messages and parses it into a map[string]interface{} variable and then reuse 96 | all the JSON-based code. The Go xml.Unmarshal() function does not provide the same 97 | option of unmarshaling XML messages into map[string]interface{} variables. So I wrote 98 | a couple of small functions to fill this gap. 99 | 100 | Of course, once the XML doc was unmarshal'd into a map[string]interface{} variable it 101 | was just a matter of calling json.Marshal() to provide it as a JSON string. Hence 'x2j' 102 | rather than just 'x2m'. 103 | 104 | USES 105 | 106 | - putting a XML API on our message hub middleware (http://jsonhub.net) 107 | - loading XML data into NoSQL database, such as, mongoDB 108 | 109 | 110 | -------------------------------------------------------------------------------- /x2j-wrapper/atomFeedString.xml: -------------------------------------------------------------------------------- 1 | 2 | Code Review - My issueshttp://codereview.appspot.com/rietveld<>rietveld: an attempt at pubsubhubbub 3 | 2009-10-04T01:35:58+00:00email-address-removedurn:md5:134d9179c41f806be79b3a5f7877d19a 4 | An attempt at adding pubsubhubbub support to Rietveld. 5 | http://code.google.com/p/pubsubhubbub 6 | http://code.google.com/p/rietveld/issues/detail?id=155 7 | 8 | The server side of the protocol is trivial: 9 | 1. add a &lt;link rel=&quot;hub&quot; href=&quot;hub-server&quot;&gt; tag to all 10 | feeds that will be pubsubhubbubbed. 11 | 2. every time one of those feeds changes, tell the hub 12 | with a simple POST request. 13 | 14 | I have tested this by adding debug prints to a local hub 15 | server and checking that the server got the right publish 16 | requests. 17 | 18 | I can&#39;t quite get the server to work, but I think the bug 19 | is not in my code. I think that the server expects to be 20 | able to grab the feed and see the feed&#39;s actual URL in 21 | the link rel=&quot;self&quot;, but the default value for that drops 22 | the :port from the URL, and I cannot for the life of me 23 | figure out how to get the Atom generator deep inside 24 | django not to do that, or even where it is doing that, 25 | or even what code is running to generate the Atom feed. 26 | (I thought I knew but I added some assert False statements 27 | and it kept running!) 28 | 29 | Ignoring that particular problem, I would appreciate 30 | feedback on the right way to get the two values at 31 | the top of feeds.py marked NOTE(rsc). 32 | 33 | 34 | rietveld: correct tab handling 35 | 2009-10-03T23:02:17+00:00email-address-removedurn:md5:0a2a4f19bb815101f0ba2904aed7c35a 36 | This fixes the buggy tab rendering that can be seen at 37 | http://codereview.appspot.com/116075/diff/1/2 38 | 39 | The fundamental problem was that the tab code was 40 | not being told what column the text began in, so it 41 | didn&#39;t know where to put the tab stops. Another problem 42 | was that some of the code assumed that string byte 43 | offsets were the same as column offsets, which is only 44 | true if there are no tabs. 45 | 46 | In the process of fixing this, I cleaned up the arguments 47 | to Fold and ExpandTabs and renamed them Break and 48 | _ExpandTabs so that I could be sure that I found all the 49 | call sites. I also wanted to verify that ExpandTabs was 50 | not being used from outside intra_region_diff.py. 51 | 52 | 53 | ` 54 | 55 | -------------------------------------------------------------------------------- /x2j-wrapper/goofy_test.go: -------------------------------------------------------------------------------- 1 | package x2j 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestGoofy(t *testing.T) { 10 | var doc = `` 11 | type goofy struct { 12 | S string 13 | Sp *string 14 | } 15 | g := new(goofy) 16 | g.S = "Now is the time for" 17 | tmp := "all good men to come to" 18 | g.Sp = &tmp 19 | 20 | m, err := DocToMap(doc) 21 | if err != nil { 22 | fmt.Println("err:",err.Error()) 23 | return 24 | } 25 | 26 | m["goofyVal"] = interface{}(g) 27 | m["byteVal"] = interface{}([]byte(`the aid of their country`)) 28 | m["nilVal"] = interface{}(nil) 29 | 30 | fmt.Println("\nTestGoofy ... MapToDoc:",m) 31 | var v []byte 32 | v,err = json.Marshal(m) 33 | if err != nil { 34 | fmt.Println("err:",err.Error()) 35 | } 36 | fmt.Println("v:",string(v)) 37 | 38 | type goofier struct { 39 | G *goofy 40 | B []byte 41 | N *string 42 | } 43 | gg := new(goofier) 44 | gg.G = g 45 | gg.B = []byte(`the tree of freedom must periodically be`) 46 | gg.N = nil 47 | m["goofierVal"] = interface{}(gg) 48 | 49 | fmt.Println("\nTestGoofier ... MapToDoc:",m) 50 | v,err = json.Marshal(m) 51 | if err != nil { 52 | fmt.Println("err:",err.Error()) 53 | } 54 | fmt.Println("v:",string(v)) 55 | } 56 | 57 | -------------------------------------------------------------------------------- /x2j-wrapper/pathTestString.xml: -------------------------------------------------------------------------------- 1 | 2 | 1 3 | 4 | 5 | A 6 | 7 | 8 | B 9 | 10 | 11 | C 12 | D 13 | 14 | <_> 15 | E 16 | 17 | 18 | 2 19 | 20 | 21 | -------------------------------------------------------------------------------- /x2j-wrapper/reader2j.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2018 Charles Banning. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file 4 | // io.Reader --> map[string]interface{} or JSON string 5 | // nothing magic - just implements generic Go case 6 | 7 | package x2j 8 | 9 | import ( 10 | "encoding/json" 11 | "io" 12 | 13 | "github.com/clbanning/mxj/v2" 14 | ) 15 | 16 | // ToMap() - parse a XML io.Reader to a map[string]interface{} 17 | func ToMap(rdr io.Reader, recast ...bool) (map[string]interface{}, error) { 18 | var r bool 19 | if len(recast) == 1 { 20 | r = recast[0] 21 | } 22 | return mxj.NewMapXmlReader(rdr, r) 23 | } 24 | 25 | // ToJson() - parse a XML io.Reader to a JSON string 26 | func ToJson(rdr io.Reader, recast ...bool) (string, error) { 27 | var r bool 28 | if len(recast) == 1 { 29 | r = recast[0] 30 | } 31 | m, merr := mxj.NewMapXmlReader(rdr, r) 32 | if m == nil || merr != nil { 33 | return "", merr 34 | } 35 | 36 | b, berr := json.Marshal(m) 37 | if berr != nil { 38 | return "", berr 39 | } 40 | 41 | return string(b), nil 42 | } 43 | 44 | // ToJsonIndent - the pretty form of ReaderToJson 45 | func ToJsonIndent(rdr io.Reader, recast ...bool) (string, error) { 46 | var r bool 47 | if len(recast) == 1 { 48 | r = recast[0] 49 | } 50 | m, merr := mxj.NewMapXmlReader(rdr, r) 51 | if m == nil || merr != nil { 52 | return "", merr 53 | } 54 | 55 | b, berr := json.MarshalIndent(m, "", " ") 56 | if berr != nil { 57 | return "", berr 58 | } 59 | 60 | // NOTE: don't have to worry about safe JSON marshaling with json.Marshal, since '<' and '>" are reservedin XML. 61 | return string(b), nil 62 | } 63 | 64 | // ReaderValuesFromTagPath - io.Reader version of ValuesFromTagPath() 65 | func ReaderValuesFromTagPath(rdr io.Reader, path string, getAttrs ...bool) ([]interface{}, error) { 66 | var a bool 67 | if len(getAttrs) == 1 { 68 | a = getAttrs[0] 69 | } 70 | m, err := mxj.NewMapXmlReader(rdr) 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | return ValuesFromKeyPath(m, path, a), nil 76 | } 77 | 78 | // ReaderValuesForTag - io.Reader version of ValuesForTag() 79 | func ReaderValuesForTag(rdr io.Reader, tag string) ([]interface{}, error) { 80 | m, err := mxj.NewMapXmlReader(rdr) 81 | if err != nil { 82 | return nil, err 83 | } 84 | 85 | return ValuesForKey(m, tag), nil 86 | } 87 | -------------------------------------------------------------------------------- /x2j-wrapper/reader2j_test.go: -------------------------------------------------------------------------------- 1 | package x2j 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | var doc = `barworld` 10 | 11 | 12 | func TestToMap(t *testing.T) { 13 | fmt.Println("\nToMap - Read doc:",doc) 14 | rdr := bytes.NewBufferString(doc) 15 | m,err := ToMap(rdr) 16 | if err != nil { 17 | fmt.Println("err:",err.Error()) 18 | } 19 | fmt.Println(WriteMap(m)) 20 | } 21 | 22 | func TestToJson(t *testing.T) { 23 | fmt.Println("\nToJson - Read doc:",doc) 24 | rdr := bytes.NewBufferString(doc) 25 | s,err := ToJson(rdr) 26 | if err != nil { 27 | fmt.Println("err:",err.Error()) 28 | } 29 | fmt.Println("json:",s) 30 | } 31 | 32 | func TestToJsonIndent(t *testing.T) { 33 | fmt.Println("\nToJsonIndent - Read doc:",doc) 34 | rdr := bytes.NewBufferString(doc) 35 | s,err := ToJsonIndent(rdr) 36 | if err != nil { 37 | fmt.Println("err:",err.Error()) 38 | } 39 | fmt.Println("json:",s) 40 | } 41 | 42 | func TestBulkParser(t *testing.T) { 43 | s := doc + `an`+ doc 44 | fmt.Println("\nBulkParser (with error) - Read doc:",s) 45 | rdr := bytes.NewBufferString(s) 46 | err := XmlMsgsFromReader(rdr,phandler,ehandler) 47 | if err != nil { 48 | fmt.Println("reader terminated:",err.Error()) 49 | } 50 | } 51 | 52 | func phandler(m map[string]interface{}) bool { 53 | fmt.Println("phandler m:",m) 54 | return true 55 | } 56 | 57 | func ehandler(err error) bool { 58 | fmt.Println("ehandler err:",err.Error()) 59 | return true 60 | } 61 | 62 | func TestBulkParserToJson(t *testing.T) { 63 | s := doc + `an`+ doc 64 | fmt.Println("\nBulkParser (with error) - Read doc:",s) 65 | rdr := bytes.NewBufferString(s) 66 | err := XmlMsgsFromReaderAsJson(rdr,phandlerj,ehandler) 67 | if err != nil { 68 | fmt.Println("reader terminated:",err.Error()) 69 | } 70 | } 71 | 72 | func phandlerj(s string) bool { 73 | fmt.Println("phandlerj s:",s) 74 | return true 75 | } 76 | 77 | -------------------------------------------------------------------------------- /x2j-wrapper/songTextString.xml: -------------------------------------------------------------------------------- 1 | 2 | help me! 3 | 4 | 5 | 6 | Henry was a renegade 7 | Didn't like to play it safe 8 | One component at a time 9 | There's got to be a better way 10 | Oh, people came from miles around 11 | Searching for a steady job 12 | Welcome to the Motor Town 13 | Booming like an atom bomb 14 | 15 | 16 | Oh, Henry was the end of the story 17 | Then everything went wrong 18 | And we'll return it to its former glory 19 | But it just takes so long 20 | 21 | 22 | 23 | It's going to take a long time 24 | It's going to take it, but we'll make it one day 25 | It's going to take a long time 26 | It's going to take it, but we'll make it one day 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /x2j-wrapper/x2j_bulk.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2018 Charles Banning. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file 4 | 5 | // x2j_bulk.go: Process files with multiple XML messages. 6 | // Extends x2m_bulk.go to work with JSON strings rather than map[string]interface{}. 7 | 8 | package x2j 9 | 10 | import ( 11 | "bytes" 12 | "io" 13 | "os" 14 | "regexp" 15 | 16 | "github.com/clbanning/mxj/v2" 17 | ) 18 | 19 | // XmlMsgsFromFileAsJson() 20 | // 'fname' is name of file 21 | // 'phandler' is the JSON string processing handler. Return of 'false' stops further processing. 22 | // 'ehandler' is the parsing error handler. Return of 'false' stops further processing and returns error. 23 | // Note: phandler() and ehandler() calls are blocking, so reading and processing of messages is serialized. 24 | // This means that you can stop reading the file on error or after processing a particular message. 25 | // To have reading and handling run concurrently, pass arguments to a go routine in handler and return true. 26 | func XmlMsgsFromFileAsJson(fname string, phandler func(string)(bool), ehandler func(error)(bool), recast ...bool) error { 27 | var r bool 28 | if len(recast) == 1 { 29 | r = recast[0] 30 | } 31 | fi, fierr := os.Stat(fname) 32 | if fierr != nil { 33 | return fierr 34 | } 35 | fh, fherr := os.Open(fname) 36 | if fherr != nil { 37 | return fherr 38 | } 39 | defer fh.Close() 40 | buf := make([]byte,fi.Size()) 41 | _, rerr := fh.Read(buf) 42 | if rerr != nil { 43 | return rerr 44 | } 45 | doc := string(buf) 46 | 47 | // xml.Decoder doesn't properly handle whitespace in some doc 48 | // see songTextString.xml test case ... 49 | reg,_ := regexp.Compile("[ \t\n\r]*<") 50 | doc = reg.ReplaceAllString(doc,"<") 51 | b := bytes.NewBufferString(doc) 52 | 53 | for { 54 | s, serr := XmlBufferToJson(b,r) 55 | if serr != nil && serr != io.EOF { 56 | if ok := ehandler(serr); !ok { 57 | // caused reader termination 58 | return serr 59 | } 60 | } 61 | if s != "" { 62 | if ok := phandler(s); !ok { 63 | break 64 | } 65 | } 66 | if serr == io.EOF { 67 | break 68 | } 69 | } 70 | return nil 71 | } 72 | 73 | // XmlBufferToJson - process XML message from a bytes.Buffer 74 | // 'b' is the buffer 75 | // Optional argument 'recast' coerces values to float64 or bool where possible. 76 | func XmlBufferToJson(b *bytes.Buffer,recast ...bool) (string,error) { 77 | var r bool 78 | if len(recast) == 1 { 79 | r = recast[0] 80 | } 81 | 82 | m, err := mxj.NewMapXmlReader(b, r) 83 | // n,err := XmlBufferToTree(b) 84 | if err != nil { 85 | return "", err 86 | } 87 | 88 | // m := make(map[string]interface{}) 89 | // m[n.key] = n.treeToMap(r) 90 | 91 | j, jerr := m.Json() 92 | return string(j), jerr 93 | } 94 | 95 | // ============================= io.Reader version for stream processing ====================== 96 | 97 | // XmlMsgsFromReaderAsJson() - io.Reader version of XmlMsgsFromFileAsJson 98 | // 'rdr' is an io.Reader for an XML message (stream) 99 | // 'phandler' is the JSON string processing handler. Return of 'false' stops further processing. 100 | // 'ehandler' is the parsing error handler. Return of 'false' stops further processing and returns error. 101 | // Note: phandler() and ehandler() calls are blocking, so reading and processing of messages is serialized. 102 | // This means that you can stop reading the file on error or after processing a particular message. 103 | // To have reading and handling run concurrently, pass arguments to a go routine in handler and return true. 104 | func XmlMsgsFromReaderAsJson(rdr io.Reader, phandler func(string)(bool), ehandler func(error)(bool), recast ...bool) error { 105 | var r bool 106 | if len(recast) == 1 { 107 | r = recast[0] 108 | } 109 | 110 | for { 111 | s, serr := ToJson(rdr,r) 112 | if serr != nil && serr != io.EOF { 113 | if ok := ehandler(serr); !ok { 114 | // caused reader termination 115 | return serr 116 | } 117 | } 118 | if s != "" { 119 | if ok := phandler(s); !ok { 120 | break 121 | } 122 | } 123 | if serr == io.EOF { 124 | break 125 | } 126 | } 127 | return nil 128 | } 129 | 130 | -------------------------------------------------------------------------------- /x2j-wrapper/x2j_findPath.go: -------------------------------------------------------------------------------- 1 | // x2j_findPath - utility functions to retrieve path to node in dot-notation 2 | // Copyright 2012-2018 Charles Banning. All rights reserved. 3 | // Use of this source code is governed by a BSD-style 4 | // license that can be found in the LICENSE file 5 | 6 | package x2j 7 | 8 | import ( 9 | "strings" 10 | 11 | "github.com/clbanning/mxj/v2" 12 | ) 13 | 14 | //----------------------------- find all paths to a key -------------------------------- 15 | // Want eventually to extract shortest path and call GetValuesAtKeyPath() 16 | // This will get all the possible paths. These can be scanned for len(path) and sequence. 17 | 18 | // Get all paths through the doc (in dot-notation) that terminate with the specified tag. 19 | // Results can be used with ValuesAtTagPath() and ValuesFromTagPath(). 20 | func PathsForTag(doc string, key string) ([]string, error) { 21 | m, err := mxj.NewMapXml([]byte(doc)) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | ss := PathsForKey(m, key) 27 | return ss, nil 28 | } 29 | 30 | // Extract the shortest path from all possible paths - from PathsForTag(). 31 | // Paths are strings using dot-notation. 32 | func PathForTagShortest(doc string, key string) (string, error) { 33 | m, err := mxj.NewMapXml([]byte(doc)) 34 | if err != nil { 35 | return "", err 36 | } 37 | 38 | s := PathForKeyShortest(m, key) 39 | return s, nil 40 | } 41 | 42 | // Get all paths through the doc (in dot-notation) that terminate with the specified tag. 43 | // Results can be used with ValuesAtTagPath() and ValuesFromTagPath(). 44 | func BytePathsForTag(doc []byte, key string) ([]string, error) { 45 | m, err := mxj.NewMapXml(doc) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | ss := PathsForKey(m, key) 51 | return ss, nil 52 | } 53 | 54 | // Extract the shortest path from all possible paths - from PathsForTag(). 55 | // Paths are strings using dot-notation. 56 | func BytePathForTagShortest(doc []byte, key string) (string, error) { 57 | m, err := ByteDocToMap(doc) 58 | if err != nil { 59 | return "", err 60 | } 61 | 62 | s := PathForKeyShortest(m, key) 63 | return s, nil 64 | } 65 | 66 | // Get all paths through the map (in dot-notation) that terminate with the specified key. 67 | // Results can be used with ValuesAtKeyPath() and ValuesFromKeyPath(). 68 | func PathsForKey(m map[string]interface{}, key string) []string { 69 | breadbasket := make(map[string]bool,0) 70 | breadcrumb := "" 71 | 72 | hasKeyPath(breadcrumb, m, key, &breadbasket) 73 | if len(breadbasket) == 0 { 74 | return nil 75 | } 76 | 77 | // unpack map keys to return 78 | res := make([]string,len(breadbasket)) 79 | var i int 80 | for k,_ := range breadbasket { 81 | res[i] = k 82 | i++ 83 | } 84 | 85 | return res 86 | } 87 | 88 | // Extract the shortest path from all possible paths - from PathsForKey(). 89 | // Paths are strings using dot-notation. 90 | func PathForKeyShortest(m map[string]interface{}, key string) string { 91 | paths := PathsForKey(m,key) 92 | 93 | lp := len(paths) 94 | if lp == 0 { 95 | return "" 96 | } 97 | if lp == 1 { 98 | return paths[0] 99 | } 100 | 101 | shortest := paths[0] 102 | shortestLen := len(strings.Split(shortest,".")) 103 | 104 | for i := 1 ; i < len(paths) ; i++ { 105 | vlen := len(strings.Split(paths[i],".")) 106 | if vlen < shortestLen { 107 | shortest = paths[i] 108 | shortestLen = vlen 109 | } 110 | } 111 | 112 | return shortest 113 | } 114 | 115 | // hasKeyPath - if the map 'key' exists append it to KeyPath.path and increment KeyPath.depth 116 | // This is really just a breadcrumber that saves all trails that hit the prescribed 'key'. 117 | func hasKeyPath(crumb string, iv interface{}, key string, basket *map[string]bool) { 118 | switch iv.(type) { 119 | case map[string]interface{}: 120 | vv := iv.(map[string]interface{}) 121 | if _, ok := vv[key]; ok { 122 | if crumb == "" { 123 | crumb = key 124 | } else { 125 | crumb += "." + key 126 | } 127 | // *basket = append(*basket, crumb) 128 | (*basket)[crumb] = true 129 | } 130 | // walk on down the path, key could occur again at deeper node 131 | for k, v := range vv { 132 | // create a new breadcrumb, add the one we're at to the crumb-trail 133 | var nbc string 134 | if crumb == "" { 135 | nbc = k 136 | } else { 137 | nbc = crumb + "." + k 138 | } 139 | hasKeyPath(nbc, v, key, basket) 140 | } 141 | case []interface{}: 142 | // crumb-trail doesn't change, pass it on 143 | for _, v := range iv.([]interface{}) { 144 | hasKeyPath(crumb, v, key, basket) 145 | } 146 | } 147 | } 148 | 149 | -------------------------------------------------------------------------------- /x2j-wrapper/x2j_test.xml: -------------------------------------------------------------------------------- 1 | 2 | help me! 3 | 4 | 5 | 6 | Henry was a renegade 7 | Didn't like to play it safe 8 | One component at a time 9 | There's got to be a better way 10 | Oh, people came from miles around 11 | Searching for a steady job 12 | Welcome to the Motor Town 13 | Booming like an atom bomb 14 | 15 | 16 | Oh, Henry was the end of the story 17 | Then everything went wrong 18 | And we'll return it to its former glory 19 | But it just takes so long 20 | 21 | 22 | 23 | It's going to take a long time 24 | It's going to take it, but we'll make it one day 25 | It's going to take a long time 26 | It's going to take it, but we'll make it one day 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /x2j-wrapper/x2j_valuesAt.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2018 Charles Banning. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file 4 | 5 | // x2j_valuesAt.go: Extract values from an arbitrary XML doc that are at same level as "key". 6 | // Tag path can include wildcard characters. 7 | 8 | package x2j 9 | 10 | import ( 11 | "strings" 12 | 13 | "github.com/clbanning/mxj/v2" 14 | ) 15 | 16 | // ------------------- sweep up everything for some point in the node tree --------------------- 17 | 18 | // ValuesAtTagPath - deliver all values at the same level of the document as the specified key. 19 | // See ValuesAtKeyPath(). 20 | // If there are no values for the path 'nil' is returned. 21 | // A return value of (nil, nil) means that there were no values and no errors parsing the doc. 22 | // 'doc' is the XML document 23 | // 'path' is a dot-separated path of tag nodes 24 | // 'getAttrs' can be set 'true' to return attribute values for "*"-terminated path 25 | // If a node is '*', then everything beyond is scanned for values. 26 | // E.g., "doc.books' might return a single value 'book' of type []interface{}, but 27 | // "doc.books.*" could return all the 'book' entries as []map[string]interface{}. 28 | // "doc.books.*.author" might return all the 'author' tag values as []string - or 29 | // "doc.books.*.author.lastname" might be required, depending on he schema. 30 | func ValuesAtTagPath(doc, path string, getAttrs ...bool) ([]interface{}, error) { 31 | var a bool 32 | if len(getAttrs) == 1 { 33 | a = getAttrs[0] 34 | } 35 | m, err := mxj.NewMapXml([]byte(doc)) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | v := ValuesAtKeyPath(m, path, a) 41 | return v, nil 42 | } 43 | 44 | // ValuesAtKeyPath - deliver all values at the same depth in a map[string]interface{} value 45 | // If v := ValuesAtKeyPath(m,"x.y.z") 46 | // then there exists a _,vv := range v 47 | // such that v.(map[string]interface{})[z] == ValuesFromKeyPath(m,"x.y.z") 48 | // If there are no values for the path 'nil' is returned. 49 | // 'm' is the map to be walked 50 | // 'path' is a dot-separated path of key values 51 | // 'getAttrs' can be set 'true' to return attribute values for "*"-terminated path 52 | // If a node is '*', then everything beyond is walked. 53 | // E.g., see ValuesFromTagPath documentation. 54 | func ValuesAtKeyPath(m map[string]interface{}, path string, getAttrs ...bool) []interface{} { 55 | var a bool 56 | if len(getAttrs) == 1 { 57 | a = getAttrs[0] 58 | } 59 | keys := strings.Split(path, ".") 60 | lenKeys := len(keys) 61 | ret := make([]interface{}, 0) 62 | if lenKeys > 1 { 63 | // use function in x2j_valuesFrom.go 64 | valuesFromKeyPath(&ret, m, keys[:lenKeys-1], a) 65 | if len(ret) == 0 { 66 | return nil 67 | } 68 | } else { 69 | ret = append(ret,interface{}(m)) 70 | } 71 | 72 | // scan the value set and see if key occurs 73 | key := keys[lenKeys-1] 74 | // wildcard is special 75 | if key == "*" { 76 | return ret 77 | } 78 | for _, v := range ret { 79 | switch v.(type) { 80 | case map[string]interface{}: 81 | if _, ok := v.(map[string]interface{})[key]; ok { 82 | return ret 83 | } 84 | } 85 | } 86 | 87 | // no instance of key in penultimate value set 88 | return nil 89 | } 90 | 91 | -------------------------------------------------------------------------------- /x2j-wrapper/x2j_valuesFrom.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2018 Charles Banning. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file 4 | 5 | // x2j_valuesFrom.go: Extract values from an arbitrary XML doc. Tag path can include wildcard characters. 6 | 7 | package x2j 8 | 9 | import ( 10 | "strings" 11 | 12 | "github.com/clbanning/mxj/v2" 13 | ) 14 | 15 | // ------------------- sweep up everything for some point in the node tree --------------------- 16 | 17 | // ValuesFromTagPath - deliver all values for a path node from a XML doc 18 | // If there are no values for the path 'nil' is returned. 19 | // A return value of (nil, nil) means that there were no values and no errors parsing the doc. 20 | // 'doc' is the XML document 21 | // 'path' is a dot-separated path of tag nodes 22 | // 'getAttrs' can be set 'true' to return attribute values for "*"-terminated path 23 | // If a node is '*', then everything beyond is scanned for values. 24 | // E.g., "doc.books' might return a single value 'book' of type []interface{}, but 25 | // "doc.books.*" could return all the 'book' entries as []map[string]interface{}. 26 | // "doc.books.*.author" might return all the 'author' tag values as []string - or 27 | // "doc.books.*.author.lastname" might be required, depending on he schema. 28 | func ValuesFromTagPath(doc, path string, getAttrs ...bool) ([]interface{}, error) { 29 | var a bool 30 | if len(getAttrs) == 1 { 31 | a = getAttrs[0] 32 | } 33 | m, err := mxj.NewMapXml([]byte(doc)) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | v := ValuesFromKeyPath(m, path, a) 39 | return v, nil 40 | } 41 | 42 | // ValuesFromKeyPath - deliver all values for a path node from a map[string]interface{} 43 | // If there are no values for the path 'nil' is returned. 44 | // 'm' is the map to be walked 45 | // 'path' is a dot-separated path of key values 46 | // 'getAttrs' can be set 'true' to return attribute values for "*"-terminated path 47 | // If a node is '*', then everything beyond is walked. 48 | // E.g., see ValuesFromTagPath documentation. 49 | func ValuesFromKeyPath(m map[string]interface{}, path string, getAttrs ...bool) []interface{} { 50 | var a bool 51 | if len(getAttrs) == 1 { 52 | a = getAttrs[0] 53 | } 54 | keys := strings.Split(path, ".") 55 | ret := make([]interface{}, 0) 56 | valuesFromKeyPath(&ret, m, keys, a) 57 | if len(ret) == 0 { 58 | return nil 59 | } 60 | return ret 61 | } 62 | 63 | func valuesFromKeyPath(ret *[]interface{}, m interface{}, keys []string, getAttrs bool) { 64 | lenKeys := len(keys) 65 | 66 | // load 'm' values into 'ret' 67 | // expand any lists 68 | if lenKeys == 0 { 69 | switch m.(type) { 70 | case map[string]interface{}: 71 | *ret = append(*ret, m) 72 | case []interface{}: 73 | for _, v := range m.([]interface{}) { 74 | *ret = append(*ret, v) 75 | } 76 | default: 77 | *ret = append(*ret, m) 78 | } 79 | return 80 | } 81 | 82 | // key of interest 83 | key := keys[0] 84 | switch key { 85 | case "*": // wildcard - scan all values 86 | switch m.(type) { 87 | case map[string]interface{}: 88 | for k, v := range m.(map[string]interface{}) { 89 | if string(k[:1]) == "-" && !getAttrs { // skip attributes? 90 | continue 91 | } 92 | valuesFromKeyPath(ret, v, keys[1:], getAttrs) 93 | } 94 | case []interface{}: 95 | for _, v := range m.([]interface{}) { 96 | switch v.(type) { 97 | // flatten out a list of maps - keys are processed 98 | case map[string]interface{}: 99 | for kk, vv := range v.(map[string]interface{}) { 100 | if string(kk[:1]) == "-" && !getAttrs { // skip attributes? 101 | continue 102 | } 103 | valuesFromKeyPath(ret, vv, keys[1:], getAttrs) 104 | } 105 | default: 106 | valuesFromKeyPath(ret, v, keys[1:], getAttrs) 107 | } 108 | } 109 | } 110 | default: // key - must be map[string]interface{} 111 | switch m.(type) { 112 | case map[string]interface{}: 113 | if v, ok := m.(map[string]interface{})[key]; ok { 114 | valuesFromKeyPath(ret, v, keys[1:], getAttrs) 115 | } 116 | case []interface{}: // may be buried in list 117 | for _, v := range m.([]interface{}) { 118 | switch v.(type) { 119 | case map[string]interface{}: 120 | if vv, ok := v.(map[string]interface{})[key]; ok { 121 | valuesFromKeyPath(ret, vv, keys[1:], getAttrs) 122 | } 123 | } 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /x2j-wrapper/x2jat_test.go: -------------------------------------------------------------------------------- 1 | package x2j 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | var doc1 = ` 9 | 10 | 11 | 12 | William H. Gaddis 13 | The Recognitions 14 | One of the great seminal American novels of the 20th century. 15 | 16 | 17 | Austin Tappan Wright 18 | Islandia 19 | An example of earlier 20th century American utopian fiction. 20 | 21 | 22 | John Hawkes 23 | The Beetle Leg 24 | A lyrical novel about the construction of Ft. Peck Dam in Montana. 25 | 26 | 27 | 28 | T.E. 29 | Porter 30 | 31 | King's Day 32 | A magical novella. 33 | 34 | 35 | 36 | ` 37 | var doc2 = ` 38 | 39 | 40 | 41 | William H. Gaddis 42 | 43 | The Recognitions 44 | One of the great seminal American novels of the 20th century. 45 | 46 | 47 | JR 48 | Won the National Book Award 49 | 50 | 51 | 52 | John Hawkes 53 | 54 | 55 | The Beetle Leg 56 | 57 | 58 | The Blood Oranges 59 | 60 | 61 | 62 | 63 | 64 | ` 65 | var msg1 = ` 66 | 67 | test 68 | This is a long cold winter 69 | ` 70 | 71 | var msg2 = ` 72 | 73 | 74 | test 75 | This is a long cold winter 76 | 77 | 78 | test2 79 | I hope we have a cool summer, though 80 | 81 | ` 82 | 83 | func TestValuesAtKeyPath(t *testing.T) { 84 | fmt.Println("\n============================ x2jat_test.go") 85 | fmt.Println("\n=============== TestValuesAtKeyPath ...") 86 | fmt.Println("\nValuesAtKeyPath ... doc1#author") 87 | m, _ := DocToMap(doc1) 88 | ss := PathsForKey(m,"author") 89 | fmt.Println("ss:", ss) 90 | for _,v := range ss { 91 | vv := ValuesAtKeyPath(m,v,true) 92 | fmt.Println("vv:", vv) 93 | } 94 | 95 | fmt.Println("\nValuesAtKeyPath ... doc1#first_name") 96 | // m, _ := DocToMap(doc1) 97 | ss = PathsForKey(m,"first_name") 98 | fmt.Println("ss:", ss) 99 | for _,v := range ss { 100 | vv := ValuesAtKeyPath(m,v,true) 101 | fmt.Println("vv:", vv) 102 | } 103 | 104 | fmt.Println("\nGetKeyPaths...doc2#book") 105 | m, _ = DocToMap(doc2) 106 | ss = PathsForKey(m,"book") 107 | fmt.Println("ss:", ss) 108 | for _,v := range ss { 109 | vv := ValuesAtKeyPath(m,v,true) 110 | fmt.Println("vv:", vv) 111 | } 112 | s := PathForKeyShortest(m,"book") 113 | vv := ValuesAtKeyPath(m,s) 114 | fmt.Println("vv,shortest_path:",vv) 115 | 116 | fmt.Println("\nValuesAtKeyPath ... msg1#pub") 117 | m, _ = DocToMap(msg1) 118 | ss = PathsForKey(m,"pub") 119 | fmt.Println("ss:", ss) 120 | for _,v := range ss { 121 | vv := ValuesAtKeyPath(m,v,true) 122 | fmt.Println("vv:", vv) 123 | } 124 | 125 | fmt.Println("\nValuesAtKeyPath ... msg2#pub") 126 | m, _ = DocToMap(msg2) 127 | ss = PathsForKey(m,"pub") 128 | fmt.Println("ss:", ss) 129 | for _,v := range ss { 130 | vv := ValuesAtKeyPath(m,v,true) 131 | fmt.Println("vv:", vv) 132 | } 133 | } 134 | 135 | func TestValuesAtTagPath(t *testing.T) { 136 | fmt.Println("\n=============== TestValuesAtTagPath ...") 137 | fmt.Println("\nValuesAtTagPath ... doc1#author") 138 | m, _ := DocToMap(doc1) 139 | ss := PathsForKey(m,"author") 140 | fmt.Println("ss:", ss) 141 | for _,v := range ss { 142 | vv,_ := ValuesAtTagPath(doc1,v,true) 143 | fmt.Println("vv:", vv) 144 | } 145 | 146 | fmt.Println("\nValuesAtTagPath ... doc1#first_name") 147 | // m, _ := DocToMap(doc1) 148 | ss = PathsForKey(m,"first_name") 149 | fmt.Println("ss:", ss) 150 | for _,v := range ss { 151 | vv,_ := ValuesAtTagPath(doc1,v,true) 152 | fmt.Println("vv:", vv) 153 | } 154 | 155 | fmt.Println("\nValuesAtTagPath...doc2#book") 156 | m, _ = DocToMap(doc2) 157 | ss = PathsForKey(m,"book") 158 | fmt.Println("ss:", ss) 159 | for _,v := range ss { 160 | vv,_ := ValuesAtTagPath(doc2,v,true) 161 | fmt.Println("vv:", vv) 162 | } 163 | s := PathForKeyShortest(m,"book") 164 | vv,_ := ValuesAtTagPath(doc2,s) 165 | fmt.Println("vv,shortest_path:",vv) 166 | } 167 | 168 | -------------------------------------------------------------------------------- /x2j-wrapper/x2jfindPath_test.go: -------------------------------------------------------------------------------- 1 | package x2j 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | var doc01 = ` 9 | 10 | 11 | 12 | William H. Gaddis 13 | The Recognitions 14 | One of the great seminal American novels of the 20th century. 15 | 16 | 17 | Austin Tappan Wright 18 | Islandia 19 | An example of earlier 20th century American utopian fiction. 20 | 21 | 22 | John Hawkes 23 | The Beetle Leg 24 | A lyrical novel about the construction of Ft. Peck Dam in Montana. 25 | 26 | 27 | 28 | T.E. 29 | Porter 30 | 31 | King's Day 32 | A magical novella. 33 | 34 | 35 | 36 | ` 37 | var doc02 = ` 38 | 39 | 40 | 41 | William H. Gaddis 42 | 43 | The Recognitions 44 | One of the great seminal American novels of the 20th century. 45 | 46 | 47 | JR 48 | Won the National Book Award 49 | 50 | 51 | 52 | John Hawkes 53 | 54 | 55 | The Beetle Leg 56 | 57 | 58 | The Blood Oranges 59 | 60 | 61 | 62 | 63 | 64 | ` 65 | 66 | // the basic demo/test case - a small bibliography with mixed element types 67 | func TestPathsForKey(t *testing.T) { 68 | fmt.Println("\n================================ x2jfindPath_test.go") 69 | fmt.Println("\n=============== TestPathsForKey ...") 70 | fmt.Println("\nPathsForKey... doc01#author") 71 | m, _ := DocToMap(doc01) 72 | ss := PathsForKey(m, "author") 73 | fmt.Println("ss:", ss) 74 | 75 | fmt.Println("\nPathsForKey... doc01#books") 76 | // m, _ := DocToMap(doc01) 77 | ss = PathsForKey(m, "books") 78 | fmt.Println("ss:", ss) 79 | 80 | fmt.Println("\nPathsForKey...doc02#book") 81 | m, _ = DocToMap(doc02) 82 | ss = PathsForKey(m, "book") 83 | fmt.Println("ss:", ss) 84 | 85 | fmt.Println("\nPathForKeyShortest...doc02#book") 86 | m, _ = DocToMap(doc02) 87 | s := PathForKeyShortest(m, "book") 88 | fmt.Println("s:", s) 89 | } 90 | 91 | // the basic demo/test case - a small bibliography with mixed element types 92 | func TestPathsForTag(t *testing.T) { 93 | fmt.Println("\n=============== TestPathsForTag ...") 94 | fmt.Println("\nPathsForTag... doc01#author") 95 | ss, _ := PathsForTag(doc01, "author") 96 | fmt.Println("ss:", ss) 97 | 98 | fmt.Println("\nPathsForTag... doc01#books") 99 | ss, _ = PathsForTag(doc01, "books") 100 | fmt.Println("ss:", ss) 101 | 102 | fmt.Println("\nPathsForTag...doc02#book") 103 | ss, _ = PathsForTag(doc02, "book") 104 | fmt.Println("ss:", ss) 105 | 106 | fmt.Println("\nPathForTagShortest...doc02#book") 107 | s, _ := PathForTagShortest(doc02, "book") 108 | fmt.Println("s:", s) 109 | } 110 | -------------------------------------------------------------------------------- /x2j-wrapper/x2jpath_test.go: -------------------------------------------------------------------------------- 1 | package x2j 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | // the basic demo/test case - a small bibliography with mixed element types 9 | func TestValuesFromTagPath(t *testing.T) { 10 | var doc = ` 11 | 12 | 13 | 14 | William H. Gaddis 15 | The Recognitions 16 | One of the great seminal American novels of the 20th century. 17 | 18 | 19 | Austin Tappan Wright 20 | Islandia 21 | An example of earlier 20th century American utopian fiction. 22 | 23 | 24 | John Hawkes 25 | The Beetle Leg 26 | A lyrical novel about the construction of Ft. Peck Dam in Montana. 27 | 28 | 29 | 30 | T.E. 31 | Porter 32 | 33 | King's Day 34 | A magical novella. 35 | 36 | 37 | 38 | ` 39 | var doc2 = ` 40 | 41 | 42 | 43 | William H. Gaddis 44 | The Recognitions 45 | One of the great seminal American novels of the 20th century. 46 | 47 | 48 | 49 | ` 50 | fmt.Println("\nTestValuesFromTagPath()\n",doc) 51 | 52 | m,_ := DocToMap(doc) 53 | fmt.Println("map:",WriteMap(m)) 54 | 55 | v,_ := ValuesFromTagPath(doc,"doc.books") 56 | fmt.Println("path == doc.books: len(v):",len(v)) 57 | for key,val := range v { 58 | fmt.Println(key,":",val) 59 | } 60 | 61 | v,_ = ValuesFromTagPath(doc,"doc.books.*") 62 | fmt.Println("path == doc.books.*: len(v):",len(v)) 63 | for key,val := range v { 64 | fmt.Println(key,":",val) 65 | } 66 | 67 | v,_ = ValuesFromTagPath(doc,"doc.books.book") 68 | fmt.Println("path == doc.books.book: len(v):",len(v)) 69 | for key,val := range v { 70 | fmt.Println(key,":",val) 71 | } 72 | 73 | v,_ = ValuesFromTagPath(doc2,"doc.books.book") 74 | fmt.Println("doc == doc2 / path == doc.books.book: len(v):",len(v)) 75 | for key,val := range v { 76 | fmt.Println(key,":",val) 77 | } 78 | 79 | v,_ = ValuesFromTagPath(doc,"doc.books.book.*") 80 | fmt.Println("path == doc.books.book.*: len(v):",len(v)) 81 | for key,val := range v { 82 | fmt.Println(key,":",val) 83 | } 84 | 85 | v,_ = ValuesFromTagPath(doc2,"doc.books.book.*") 86 | fmt.Println("doc == doc2 / path == doc.books.book.*: len(v):",len(v)) 87 | for key,val := range v { 88 | fmt.Println(key,":",val) 89 | } 90 | 91 | v,_ = ValuesFromTagPath(doc,"doc.books.*.author") 92 | fmt.Println("path == doc.books.*.author: len(v):",len(v)) 93 | for key,val := range v { 94 | fmt.Println(key,":",val) 95 | } 96 | 97 | v,_ = ValuesFromTagPath(doc,"doc.*.*.author") 98 | fmt.Println("path == doc.*.*.author: len(v):",len(v)) 99 | for key,val := range v { 100 | fmt.Println(key,":",val) 101 | } 102 | 103 | v,_ = ValuesFromTagPath(doc,"doc.*.*.title") 104 | fmt.Println("path == doc.*.*.title: len(v):",len(v)) 105 | for key,val := range v { 106 | fmt.Println(key,":",val) 107 | } 108 | 109 | v,_ = ValuesFromTagPath(doc,"doc.*.*.*") 110 | fmt.Println("path == doc.*.*.*: len(v):",len(v)) 111 | for key,val := range v { 112 | fmt.Println(key,":",val) 113 | } 114 | 115 | v,_ = ValuesFromTagPath(doc,"doc.*.*.*.*") 116 | fmt.Println("path == doc.*.*.*.*: len(v):",len(v)) 117 | for key,val := range v { 118 | fmt.Println(key,":",val) 119 | } 120 | } 121 | 122 | // demo how to compensate for irregular tag labels in data 123 | // "netid" vs. "idnet" 124 | func TestValuesFromTagPath2(t *testing.T) { 125 | var doc1 = ` 126 | 127 | 128 | 129 | no 130 | default:text 131 | default:word 132 | 133 | 134 | ` 135 | var doc2 = ` 136 | 137 | 138 | 139 | yes 140 | default:text 141 | default:word 142 | 143 | 144 | ` 145 | var docs = []string{doc1,doc2} 146 | 147 | for n,doc := range docs { 148 | fmt.Println("\nTestValuesFromTagPath2(), iteration:",n,"\n",doc) 149 | 150 | m,_ := DocToMap(doc) 151 | fmt.Println("map:",WriteMap(m)) 152 | 153 | v,_ := ValuesFromTagPath(doc,"data.*") 154 | fmt.Println("\npath == data.*: len(v):",len(v)) 155 | for key,val := range v { 156 | fmt.Println(key,":",val) 157 | } 158 | for key,val := range v[0].(map[string]interface{}) { 159 | fmt.Println("\t",key,":",val) 160 | } 161 | 162 | v,_ = ValuesFromTagPath(doc,"data.*.*") 163 | fmt.Println("\npath == data.*.*: len(v):",len(v)) 164 | for key,val := range v { 165 | fmt.Println(key,":",val) 166 | } 167 | } 168 | } 169 | 170 | -------------------------------------------------------------------------------- /x2j-wrapper/x2junmarshal_test.go: -------------------------------------------------------------------------------- 1 | package x2j 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "encoding/xml" 7 | ) 8 | 9 | func TestUnmarshal(t *testing.T) { 10 | var doc = []byte(` Mayer Hawthorne A Long Time Henry was a renegade Didn't like to play it safe `) 11 | 12 | fmt.Println("\nUnmarshal test ... *map[string]interface{}, *string") 13 | m := make(map[string]interface{},0) 14 | err := Unmarshal(doc,&m) 15 | if err != nil { 16 | fmt.Println("err:",err.Error()) 17 | } 18 | fmt.Println("m:",m) 19 | 20 | var s string 21 | err = Unmarshal(doc,&s) 22 | if err != nil { 23 | fmt.Println("err:",err.Error()) 24 | } 25 | fmt.Println("s:",s) 26 | } 27 | 28 | func TestStructValue(t *testing.T) { 29 | var doc = []byte(`clbanning
unknown
`) 30 | type Info struct { 31 | XMLName xml.Name `xml:"info"` 32 | Name string `xml:"name"` 33 | Address string `xml:"address"` 34 | } 35 | 36 | var myInfo Info 37 | 38 | fmt.Println("\nUnmarshal test ... struct:",string(doc)) 39 | err := Unmarshal(doc,&myInfo) 40 | if err != nil { 41 | fmt.Println("err:",err.Error()) 42 | } else { 43 | fmt.Printf("myInfo: %+v\n",myInfo) 44 | } 45 | } 46 | 47 | func TestMapValue(t *testing.T) { 48 | var doc = ` Mayer Hawthorne A Long Time Henry was a renegade Didn't like to play it safe ` 49 | 50 | fmt.Println("\nTestMapValue of doc.song.verse w/ len(attrs) == 0.") 51 | fmt.Println("doc:",doc) 52 | v,err := DocValue(doc,"doc.song.verse") 53 | if err != nil { 54 | fmt.Println("err:",err.Error()) 55 | } 56 | fmt.Println("result:",v) 57 | } 58 | -------------------------------------------------------------------------------- /x2j-wrapper/x2m_bulk.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2018 Charles Banning. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file 4 | 5 | // x2m_bulk.go: Process files with multiple XML messages. 6 | 7 | package x2j 8 | 9 | import ( 10 | "bytes" 11 | "io" 12 | "os" 13 | "regexp" 14 | 15 | "github.com/clbanning/mxj/v2" 16 | ) 17 | 18 | // XmlMsgsFromFile() 19 | // 'fname' is name of file 20 | // 'phandler' is the map processing handler. Return of 'false' stops further processing. 21 | // 'ehandler' is the parsing error handler. Return of 'false' stops further processing and returns error. 22 | // Note: phandler() and ehandler() calls are blocking, so reading and processing of messages is serialized. 23 | // This means that you can stop reading the file on error or after processing a particular message. 24 | // To have reading and handling run concurrently, pass arguments to a go routine in handler and return true. 25 | func XmlMsgsFromFile(fname string, phandler func(map[string]interface{})(bool), ehandler func(error)(bool), recast ...bool) error { 26 | var r bool 27 | if len(recast) == 1 { 28 | r = recast[0] 29 | } 30 | fi, fierr := os.Stat(fname) 31 | if fierr != nil { 32 | return fierr 33 | } 34 | fh, fherr := os.Open(fname) 35 | if fherr != nil { 36 | return fherr 37 | } 38 | defer fh.Close() 39 | buf := make([]byte,fi.Size()) 40 | _, rerr := fh.Read(buf) 41 | if rerr != nil { 42 | return rerr 43 | } 44 | doc := string(buf) 45 | 46 | // xml.Decoder doesn't properly handle whitespace in some doc 47 | // see songTextString.xml test case ... 48 | reg,_ := regexp.Compile("[ \t\n\r]*<") 49 | doc = reg.ReplaceAllString(doc,"<") 50 | b := bytes.NewBufferString(doc) 51 | 52 | for { 53 | m, merr := mxj.NewMapXmlReader(b,r) 54 | if merr != nil && merr != io.EOF { 55 | if ok := ehandler(merr); !ok { 56 | // caused reader termination 57 | return merr 58 | } 59 | } 60 | if m != nil { 61 | if ok := phandler(m); !ok { 62 | break 63 | } 64 | } 65 | if merr == io.EOF { 66 | break 67 | } 68 | } 69 | return nil 70 | } 71 | 72 | // XmlBufferToMap - process XML message from a bytes.Buffer 73 | // 'b' is the buffer 74 | // Optional argument 'recast' coerces map values to float64 or bool where possible. 75 | func XmlBufferToMap(b *bytes.Buffer,recast ...bool) (map[string]interface{},error) { 76 | var r bool 77 | if len(recast) == 1 { 78 | r = recast[0] 79 | } 80 | 81 | return mxj.NewMapXmlReader(b, r) 82 | } 83 | 84 | // ============================= io.Reader version for stream processing ====================== 85 | 86 | // XmlMsgsFromReader() - io.Reader version of XmlMsgsFromFile 87 | // 'rdr' is an io.Reader for an XML message (stream) 88 | // 'phandler' is the map processing handler. Return of 'false' stops further processing. 89 | // 'ehandler' is the parsing error handler. Return of 'false' stops further processing and returns error. 90 | // Note: phandler() and ehandler() calls are blocking, so reading and processing of messages is serialized. 91 | // This means that you can stop reading the file on error or after processing a particular message. 92 | // To have reading and handling run concurrently, pass arguments to a go routine in handler and return true. 93 | func XmlMsgsFromReader(rdr io.Reader, phandler func(map[string]interface{})(bool), ehandler func(error)(bool), recast ...bool) error { 94 | var r bool 95 | if len(recast) == 1 { 96 | r = recast[0] 97 | } 98 | 99 | for { 100 | m, merr := ToMap(rdr,r) 101 | if merr != nil && merr != io.EOF { 102 | if ok := ehandler(merr); !ok { 103 | // caused reader termination 104 | return merr 105 | } 106 | } 107 | if m != nil { 108 | if ok := phandler(m); !ok { 109 | break 110 | } 111 | } 112 | if merr == io.EOF { 113 | break 114 | } 115 | } 116 | return nil 117 | } 118 | 119 | -------------------------------------------------------------------------------- /x2j-wrapper/x2m_bulk.xml: -------------------------------------------------------------------------------- 1 | 2 | help me! 3 | 4 | 5 | 6 | Henry was a renegade 7 | Didn't like to play it safe 8 | One component at a time 9 | There's got to be a better way 10 | Oh, people came from miles around 11 | Searching for a steady job 12 | Welcome to the Motor Town 13 | Booming like an atom bomb 14 | 15 | 16 | Oh, Henry was the end of the story 17 | Then everything went wrong 18 | And we'll return it to its former glory 19 | But it just takes so long 20 | 21 | 22 | 23 | It's going to take a long time 24 | It's going to take it, but we'll make it one day 25 | It's going to take a long time 26 | It's going to take it, but we'll make it one day 27 | 28 | 29 | 30 | 31 | help me! 32 | 33 | 34 | 35 | Henry was a renegade 36 | Didn't like to play it safe 37 | One component at a time 38 | There's got to be a better way 39 | Oh, people came from miles around 40 | Searching for a steady job 41 | Welcome to the Motor Town 42 | Booming like an atom bomb 43 | 44 | 45 | 46 | 47 | 48 | help me! 49 | 50 | 51 | It's going to take a long time 52 | It's going to take it, but we'll make it one day 53 | It's going to take a long time 54 | It's going to take it, but we'll make it one day 55 | 56 | 57 | 58 | 59 | help me! 60 | 61 | 62 | It's going to take a long time 63 | It's going to take it, but we'll make it one day 64 | It's going to take a long time 65 | It's going to take it, but we'll make it one day 66 | 67 | 68 | -------------------------------------------------------------------------------- /x2j-wrapper/xml.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2018 Charles Banning. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file 4 | 5 | package x2j 6 | 7 | var castNanInf bool 8 | 9 | // Cast "Nan", "Inf", "-Inf" XML values to 'float64'. 10 | // By default, these values will be decoded as 'string'. 11 | func CastNanInf(b bool) { 12 | castNanInf = b 13 | } 14 | -------------------------------------------------------------------------------- /xml3_test.go: -------------------------------------------------------------------------------- 1 | // xml3_test.go - patch tests 2 | 3 | package mxj 4 | 5 | import ( 6 | "fmt" 7 | "testing" 8 | ) 9 | 10 | func TestXml3(t *testing.T) { 11 | fmt.Println("\n------------ xml3_test.go") 12 | } 13 | 14 | // for: https://github.com/clbanning/mxj/pull/26 15 | func TestOnlyAttributes(t *testing.T) { 16 | fmt.Println("========== TestOnlyAttributes") 17 | dom, err := NewMapXml([]byte(` 18 | 19 |
20 | 21 | )`)) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | xml, err := dom.XmlIndent("", " ") 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | fmt.Println(string(xml)) 30 | } 31 | 32 | func TestOnlyAttributesSeq(t *testing.T) { 33 | fmt.Println("========== TestOnlyAttributesSeq") 34 | dom, err := NewMapXmlSeq([]byte(` 35 | 36 |
37 | 38 | )`)) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | xml, err := dom.XmlIndent("", " ") 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | fmt.Println(string(xml)) 47 | } 48 | 49 | func TestDecodeSimpleValuesAsMap(t *testing.T) { 50 | fmt.Println("========== TestDecodeSimpleValuesAsMap") 51 | DecodeSimpleValuesAsMap() 52 | 53 | xml := ` 54 | 30102 55 | Mini Drone Inteligente - Branco 56 | 149.90 57 | ` 58 | m, err := NewMapXml([]byte(xml)) 59 | if err != nil { 60 | t.Fatal(err) 61 | } 62 | fmt.Println("xml:", string(xml)) 63 | fmt.Printf("m : %v\n", m) 64 | 65 | fmt.Println("========== (default)") 66 | DecodeSimpleValuesAsMap() 67 | m, err = NewMapXml([]byte(xml)) 68 | if err != nil { 69 | t.Fatal(err) 70 | } 71 | fmt.Printf("m : %v\n", m) 72 | } 73 | -------------------------------------------------------------------------------- /xmlseq2.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2016, 2019 Charles Banning. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file 4 | 5 | package mxj 6 | 7 | // ---------------- expose Map methods to MapSeq type --------------------------- 8 | 9 | // Pretty print a Map. 10 | func (msv MapSeq) StringIndent(offset ...int) string { 11 | return writeMap(map[string]interface{}(msv), true, true, offset...) 12 | } 13 | 14 | // Pretty print a Map without the value type information - just key:value entries. 15 | func (msv MapSeq) StringIndentNoTypeInfo(offset ...int) string { 16 | return writeMap(map[string]interface{}(msv), false, true, offset...) 17 | } 18 | 19 | -------------------------------------------------------------------------------- /xmlseq_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func TestXmlSeqHeader(t *testing.T) { 11 | fmt.Println("\n---------------- xmlseq_test.go ...") 12 | } 13 | 14 | func TestNewMapXmlSeq(t *testing.T) { 15 | x := []byte(` 16 | 17 | 18 | William T. Gaddis 19 | Gaddis is one of the most influential but little know authors in America. 20 | The Recognitions 21 | 22 | One of the great seminal American novels of the 20th century. 23 | Without it Thomas Pynchon probably wouldn't have written Gravity's Rainbow. 24 | 25 | 26 | Austin Tappan Wright 27 | Islandia 28 | An example of earlier 20th century American utopian fiction. 29 | 30 | 31 | John Hawkes 32 | The Beetle Leg 33 | 34 | A lyrical novel about the construction of Ft. Peck Dam in Montana. 35 | 36 | 37 | 38 | 39 | T.E. 40 | Porter 41 | 42 | King's Day 43 | A magical novella. 44 | 45 | 46 | `) 47 | 48 | msv, err := NewMapXmlSeq(x) 49 | if err != nil && err != io.EOF { 50 | t.Fatal("err:", err.Error()) 51 | } 52 | fmt.Println("NewMapXmlSeq, x:\n", string(x)) 53 | fmt.Println("NewMapXmlSeq, s:\n", msv.StringIndent()) 54 | 55 | b, err := msv.XmlIndent("", " ") 56 | if err != nil { 57 | t.Fatal("err:", err) 58 | } 59 | fmt.Println("NewMapXmlSeq, msv.XmlIndent():\n", string(b)) 60 | } 61 | 62 | func TestXmlSeqDecodeError(t *testing.T) { 63 | fmt.Println("------------ TestXmlSeqDecodeError ...") 64 | x := []byte(` 65 | 66 | 67 | William T. Gaddis 68 | Gaddis is one of the most influential but little know authors in America. 69 | The Recognitions 70 | 71 | One of the great seminal American novels of the 20th century. 72 | Without it Thomas Pynchon probably wouldn't have written Gravity's Rainbow. 73 | 74 | `) 75 | 76 | _, err := NewMapXmlSeq(x) 77 | if err == nil { 78 | t.Fatal("didn't catch EndElement error") 79 | } 80 | fmt.Println("err ok:", err) 81 | } 82 | 83 | func BenchmarkMapToXml(b *testing.B) { 84 | xmlBytes, err := os.ReadFile("./largexml.xml") 85 | if err != nil { 86 | b.Fatal("err:", err) 87 | } 88 | 89 | msv, err := NewMapXmlSeq(xmlBytes) 90 | if err != nil { 91 | b.Fatal("err:", err) 92 | } 93 | 94 | for i := 0; i < b.N; i++ { 95 | _, err = msv.XmlIndent("", " ") 96 | if err != nil { 97 | b.Fatal("err:", err) 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /xmlseq_whitespace_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import "testing" 4 | 5 | var whiteSpaceDataSeqTest = []byte(` 6 | 7 | William T. Gaddis 8 | The Recognitions 9 | One of the great seminal American novels of the 20th century. 10 | 11 | 12 | Austin Tappan Wright 13 | Islandia 14 | An example of earlier 20th century American utopian fiction. 15 | 16 | 17 | John Hawkes 18 | The Beetle Leg 19 | A lyrical novel about the construction of Ft. Peck Dam in Montana. 20 | 21 | `) 22 | 23 | func TestNewMapXmlSeqWhiteSpace(t *testing.T) { 24 | t.Run("Testing NewMapFormattedXmlSeq with WhiteSpacing", func(t *testing.T) { 25 | DisableTrimWhiteSpace(true) 26 | 27 | m, err := NewMapFormattedXmlSeq(whiteSpaceDataSeqTest) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | 32 | m1 := MapSeq(m) 33 | x, err := m1.XmlIndent("", " ") 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | 38 | if string(x) != string(whiteSpaceDataSeqTest) { 39 | t.Fatalf("expected\n'%s' \ngot \n'%s'", whiteSpaceDataSeqTest, x) 40 | } 41 | }) 42 | DisableTrimWhiteSpace(false) 43 | } 44 | -------------------------------------------------------------------------------- /xmppStream_test.go: -------------------------------------------------------------------------------- 1 | package mxj 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "testing" 8 | ) 9 | 10 | func TestXMPPStreamTag(t *testing.T) { 11 | fmt.Println("----------- TestXMPPStreamTag ...") 12 | var data = ` 13 | 18 | 19 | 20 | 21 | 22 | ` 23 | 24 | HandleXMPPStreamTag() 25 | defer HandleXMPPStreamTag() 26 | buf := bytes.NewBufferString(data) 27 | for { 28 | m, raw, err := NewMapXmlReaderRaw(buf) 29 | if err == io.EOF { 30 | break 31 | } 32 | if err != nil { 33 | t.Fatal("err:", err) 34 | } 35 | fmt.Println(string(raw)) 36 | fmt.Println(m) 37 | } 38 | } 39 | 40 | func TestXMPPStreamTagSeq(t *testing.T) { 41 | fmt.Println("----------- TestXMPPStreamTagSeq ...") 42 | var data = ` 43 | 48 | 49 | 50 | 51 | 52 | ` 53 | 54 | HandleXMPPStreamTag() 55 | defer HandleXMPPStreamTag() 56 | buf := bytes.NewBufferString(data) 57 | for { 58 | m, raw, err := NewMapXmlSeqReaderRaw(buf) 59 | if err == io.EOF { 60 | break 61 | } 62 | if err != nil { 63 | t.Fatal("err:", err) 64 | } 65 | fmt.Println(string(raw)) 66 | fmt.Println(m) 67 | } 68 | } 69 | --------------------------------------------------------------------------------