├── .travis.yml
├── examples
├── x2jcmd.xml
├── metrics_data.zip
├── gitissue2.dat
├── goofy_map.go
├── x2jcmd.go
├── gitissue2_2.go
├── gitissue1.go
├── gitissue2.go
├── gonuts3.go
├── gonuts4.go
├── partial.go
├── bom.go
├── reddit01.go
├── reddit02.go
├── gonuts9.go
├── append.go
├── gonuts1a.go
├── books.go
├── leafnodes.go
├── gonuts5a.go
├── gonuts5.go
├── gonuts6.go
├── gonuts1.go
├── order.go
├── gonuts10seq.go
├── gonuts10.go
└── README
├── go.mod
├── files_test.badjson
├── files_test_dup.json
├── files_test_dup.xml
├── files_test.json
├── go.sum
├── files_test_indent.xml
├── files_test.badxml
├── files_test.xml
├── files_test_indent.json
├── exists.go
├── x2j-wrapper
├── xml.go
├── pathTestString.xml
├── x2j_test.xml
├── songTextString.xml
├── goofy_test.go
├── LICENSE
├── x2junmarshal_test.go
├── reader2j_test.go
├── reader2j.go
├── x2m_bulk.xml
├── atomFeedString.xml
├── x2jfindPath_test.go
├── x2j_valuesAt.go
├── x2m_bulk.go
├── x2j_bulk.go
├── x2j_valuesFrom.go
├── x2j_findPath.go
├── x2jat_test.go
├── x2jpath_test.go
└── README
├── remove_test.go
├── structvalue_test.go
├── set.go
├── j2x_test.go
├── xmlseq2.go
├── namespace_test.go
├── setfieldsep.go
├── remove.go
├── gob.go
├── set_test.go
├── rename_test.go
├── seqnum_test.go
├── strict.go
├── LICENSE
├── keyvalues2_test.go
├── songtext.xml
├── exists_test.go
├── _deprecate
└── stuff.go
├── keystolower_test.go
├── mxj_test.go
├── xmlseq_whitespace_test.go
├── strict_test.go
├── data_test.go
├── gob_test.go
├── xmppStream_test.go
├── badxml_test.go
├── j2x
├── j2x_test.go
└── j2x.go
├── rename.go
├── nan_test.go
├── xml3_test.go
├── cast_test.go
├── struct.go
├── isvalid_test.go
├── snakecase_test.go
├── struct_test.go
├── bom_test.go
├── escapechars_test.go
├── misc.go
├── escapechars.go
├── bulk_test.go
├── atomFeedString.xml
├── global_map_prefix_test.go
├── xmlseq_test.go
├── bulkraw_test.go
├── newmap_test.go
├── leafnode_test.go
├── json_test.go
├── attrprefix_test.go
├── leafnode.go
├── mxj.go
├── files_test.go
├── anyxml_test.go
└── anyxml.go
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | go:
4 | - 1.x
--------------------------------------------------------------------------------
/examples/x2jcmd.xml:
--------------------------------------------------------------------------------
1 | is a test
2 | is another
3 |
--------------------------------------------------------------------------------
/examples/metrics_data.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clbanning/mxj/HEAD/examples/metrics_data.zip
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/examples/gitissue2.dat:
--------------------------------------------------------------------------------
1 |
2 |
3 | QQ
4 |
5 |
--------------------------------------------------------------------------------
/files_test.badjson:
--------------------------------------------------------------------------------
1 | { "this":"is", "a":"test", "file":"for", "files_test.go":"case" }
2 | { "with":"some", "bad":JSON, "in":"it" }
3 |
--------------------------------------------------------------------------------
/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.json:
--------------------------------------------------------------------------------
1 | { "this":"is", "a":"test", "file":"for", "files_test.go":"case" }
2 | { "with":"just", "two":2, "JSON":"values", "true":true }
3 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/files_test_indent.xml:
--------------------------------------------------------------------------------
1 |
2 | for files.go
3 | test
4 |
5 | doc
6 | test case
7 | some
8 |
--------------------------------------------------------------------------------
/files_test.badxml:
--------------------------------------------------------------------------------
1 |
2 | test
3 | for files.go
4 |
5 |
6 | some
7 | doc
8 | test case
9 |
10 |
--------------------------------------------------------------------------------
/files_test.xml:
--------------------------------------------------------------------------------
1 |
2 | test
3 | for files.go
4 |
5 |
6 | some
7 | doc
8 | test case
9 |
10 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/_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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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(`clbanningunknown`)
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 '&'.
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 "&"
45 | // - or "<" that is parsed to "<".
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 <link rel="hub" href="hub-server"> 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'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's actual URL in
21 | the link rel="self", 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'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/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 <link rel="hub" href="hub-server"> 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'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's actual URL in
21 | the link rel="self", 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'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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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_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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 + ">" + 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("" + rt + ">"); 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 + ">" + 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(`` + rt + `>`); 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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------