新增go modules支持,自行管理第三方包依赖,方便开发者使用

This commit is contained in:
john
2018-10-22 11:13:00 +08:00
parent 77e13a9a16
commit 7fbbf09b0e
1605 changed files with 383807 additions and 25 deletions

View File

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

View File

@ -0,0 +1,177 @@
package mxj
import (
"encoding/xml"
"reflect"
)
const (
DefaultElementTag = "element"
)
// Encode arbitrary value as XML.
//
// Note: unmarshaling the resultant
// XML may not return the original value, since tag labels may have been injected
// to create the XML representation of the value.
/*
Encode an arbitrary JSON object.
package main
import (
"encoding/json"
"fmt"
"gitee.com/johng/gf/third/github.com/clbanning/mxj"
)
func main() {
jsondata := []byte(`[
{ "somekey":"somevalue" },
"string",
3.14159265,
true
]`)
var i interface{}
err := json.Unmarshal(jsondata, &i)
if err != nil {
// do something
}
x, err := mxj.AnyXmlIndent(i, "", " ", "mydoc")
if err != nil {
// do something else
}
fmt.Println(string(x))
}
output:
<mydoc>
<somekey>somevalue</somekey>
<element>string</element>
<element>3.14159265</element>
<element>true</element>
</mydoc>
*/
// Alternative values for DefaultRootTag and DefaultElementTag can be set as:
// AnyXml( v, myRootTag, myElementTag).
func AnyXml(v interface{}, tags ...string) ([]byte, error) {
if reflect.TypeOf(v).Kind() == reflect.Struct {
return xml.Marshal(v)
}
var err error
s := new(string)
p := new(pretty)
var rt, et string
if len(tags) == 1 || len(tags) == 2 {
rt = tags[0]
} else {
rt = DefaultRootTag
}
if len(tags) == 2 {
et = tags[1]
} else {
et = DefaultElementTag
}
var ss string
var b []byte
switch v.(type) {
case []interface{}:
ss = "<" + rt + ">"
for _, vv := range v.([]interface{}) {
switch vv.(type) {
case map[string]interface{}:
m := vv.(map[string]interface{})
if len(m) == 1 {
for tag, val := range m {
err = mapToXmlIndent(false, s, tag, val, p)
}
} else {
err = mapToXmlIndent(false, s, et, vv, p)
}
default:
err = mapToXmlIndent(false, s, et, vv, p)
}
if err != nil {
break
}
}
ss += *s + "</" + rt + ">"
b = []byte(ss)
case map[string]interface{}:
m := Map(v.(map[string]interface{}))
b, err = m.Xml(rt)
default:
err = mapToXmlIndent(false, s, rt, v, p)
b = []byte(*s)
}
return b, err
}
// Encode an arbitrary value as a pretty XML string.
// Alternative values for DefaultRootTag and DefaultElementTag can be set as:
// AnyXmlIndent( v, "", " ", myRootTag, myElementTag).
func AnyXmlIndent(v interface{}, prefix, indent string, tags ...string) ([]byte, error) {
if reflect.TypeOf(v).Kind() == reflect.Struct {
return xml.MarshalIndent(v, prefix, indent)
}
var err error
s := new(string)
p := new(pretty)
p.indent = indent
p.padding = prefix
var rt, et string
if len(tags) == 1 || len(tags) == 2 {
rt = tags[0]
} else {
rt = DefaultRootTag
}
if len(tags) == 2 {
et = tags[1]
} else {
et = DefaultElementTag
}
var ss string
var b []byte
switch v.(type) {
case []interface{}:
ss = "<" + rt + ">\n"
p.Indent()
for _, vv := range v.([]interface{}) {
switch vv.(type) {
case map[string]interface{}:
m := vv.(map[string]interface{})
if len(m) == 1 {
for tag, val := range m {
err = mapToXmlIndent(true, s, tag, val, p)
}
} else {
p.start = 1 // we 1 tag in
err = mapToXmlIndent(true, s, et, vv, p)
*s += "\n"
}
default:
p.start = 0 // in case trailing p.start = 1
err = mapToXmlIndent(true, s, et, vv, p)
}
if err != nil {
break
}
}
ss += *s + "</" + rt + ">"
b = []byte(ss)
case map[string]interface{}:
m := Map(v.(map[string]interface{}))
b, err = m.XmlIndent(prefix, indent, rt)
default:
err = mapToXmlIndent(true, s, rt, v, p)
b = []byte(*s)
}
return b, err
}

View File

@ -0,0 +1,110 @@
package mxj
import (
"encoding/json"
"fmt"
"testing"
)
func TestAnyXmlHeader(t *testing.T) {
fmt.Println("\n---------------- anyxml_test.go ...")
}
var anydata = []byte(`[
{
"somekey": "somevalue"
},
{
"somekey": "somevalue"
},
{
"somekey": "somevalue",
"someotherkey": "someothervalue"
},
"string",
3.14159265,
true
]`)
type MyStruct struct {
Somekey string `xml:"somekey"`
B float32 `xml:"floatval"`
}
func TestAnyXml(t *testing.T) {
var i interface{}
err := json.Unmarshal(anydata, &i)
if err != nil {
t.Fatal(err)
}
x, err := AnyXml(i)
if err != nil {
t.Fatal(err)
}
fmt.Println("[]->x:", string(x))
a := []interface{}{"try", "this", 3.14159265, true}
x, err = AnyXml(a)
if err != nil {
t.Fatal(err)
}
fmt.Println("a->x:", string(x))
x, err = AnyXml(a, "myRootTag", "myElementTag")
if err != nil {
t.Fatal(err)
}
fmt.Println("a->x:", string(x))
x, err = AnyXml(3.14159625)
if err != nil {
t.Fatal(err)
}
fmt.Println("f->x:", string(x))
s := MyStruct{"somevalue", 3.14159625}
x, err = AnyXml(s)
if err != nil {
t.Fatal(err)
}
fmt.Println("s->x:", string(x))
}
func TestAnyXmlIndent(t *testing.T) {
var i interface{}
err := json.Unmarshal(anydata, &i)
if err != nil {
t.Fatal(err)
}
x, err := AnyXmlIndent(i, "", " ")
if err != nil {
t.Fatal(err)
}
fmt.Println("[]->x:\n", string(x))
a := []interface{}{"try", "this", 3.14159265, true}
x, err = AnyXmlIndent(a, "", " ")
if err != nil {
t.Fatal(err)
}
fmt.Println("a->x:\n", string(x))
x, err = AnyXmlIndent(3.14159625, "", " ")
if err != nil {
t.Fatal(err)
}
fmt.Println("f->x:\n", string(x))
x, err = AnyXmlIndent(3.14159625, "", " ", "myRootTag", "myElementTag")
if err != nil {
t.Fatal(err)
}
fmt.Println("f->x:\n", string(x))
s := MyStruct{"somevalue", 3.14159625}
x, err = AnyXmlIndent(s, "", " ")
if err != nil {
t.Fatal(err)
}
fmt.Println("s->x:\n", string(x))
}

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us" updated="2009-10-04T01:35:58+00:00"><title>Code Review - My issues</title><link href="http://codereview.appspot.com/" rel="alternate"></link><link href="http://codereview.appspot.com/rss/mine/rsc" rel="self"></link><id>http://codereview.appspot.com/</id><author><name>rietveld&lt;&gt;</name></author><entry><title>rietveld: an attempt at pubsubhubbub
</title><link href="http://codereview.appspot.com/126085" rel="alternate"></link><updated>2009-10-04T01:35:58+00:00</updated><author><name>email-address-removed</name></author><id>urn:md5:134d9179c41f806be79b3a5f7877d19a</id><summary type="html">
An attempt at adding pubsubhubbub support to Rietveld.
http://code.google.com/p/pubsubhubbub
http://code.google.com/p/rietveld/issues/detail?id=155
The server side of the protocol is trivial:
1. add a &amp;lt;link rel=&amp;quot;hub&amp;quot; href=&amp;quot;hub-server&amp;quot;&amp;gt; tag to all
feeds that will be pubsubhubbubbed.
2. every time one of those feeds changes, tell the hub
with a simple POST request.
I have tested this by adding debug prints to a local hub
server and checking that the server got the right publish
requests.
I can&amp;#39;t quite get the server to work, but I think the bug
is not in my code. I think that the server expects to be
able to grab the feed and see the feed&amp;#39;s actual URL in
the link rel=&amp;quot;self&amp;quot;, but the default value for that drops
the :port from the URL, and I cannot for the life of me
figure out how to get the Atom generator deep inside
django not to do that, or even where it is doing that,
or even what code is running to generate the Atom feed.
(I thought I knew but I added some assert False statements
and it kept running!)
Ignoring that particular problem, I would appreciate
feedback on the right way to get the two values at
the top of feeds.py marked NOTE(rsc).
</summary></entry><entry><title>rietveld: correct tab handling
</title><link href="http://codereview.appspot.com/124106" rel="alternate"></link><updated>2009-10-03T23:02:17+00:00</updated><author><name>email-address-removed</name></author><id>urn:md5:0a2a4f19bb815101f0ba2904aed7c35a</id><summary type="html">
This fixes the buggy tab rendering that can be seen at
http://codereview.appspot.com/116075/diff/1/2
The fundamental problem was that the tab code was
not being told what column the text began in, so it
didn&amp;#39;t know where to put the tab stops. Another problem
was that some of the code assumed that string byte
offsets were the same as column offsets, which is only
true if there are no tabs.
In the process of fixing this, I cleaned up the arguments
to Fold and ExpandTabs and renamed them Break and
_ExpandTabs so that I could be sure that I found all the
call sites. I also wanted to verify that ExpandTabs was
not being used from outside intra_region_diff.py.
</summary></entry></feed> `

View File

@ -0,0 +1,148 @@
// attrprefix_test.go - change attrPrefix var
package mxj
import (
"fmt"
"testing"
)
var data = []byte(`
<doc>
<elem1 attr1="this" attr2="is">a test</elem1>
<elem2 attr1="this" attr2="is not">a test</elem2>
</doc>
`)
func TestPrefixDefault(t *testing.T) {
fmt.Println("----------------- TestPrefixDefault ...")
m, err := NewMapXml(data)
if err != nil {
t.Fatal(err)
}
vals, err := m.ValuesForKey("-attr1")
if err != nil {
t.Fatal(err)
}
if len(vals) != 2 {
t.Fatal("didn't get 2 -attr1 vals", len(vals))
}
vals, err = m.ValuesForKey("-attr2")
if err != nil {
t.Fatal(err)
}
if len(vals) != 2 {
t.Fatal("didn't get 2 -attr2 vals", len(vals))
}
}
func TestPrefixNoHyphen(t *testing.T) {
fmt.Println("----------------- TestPrefixNoHyphen ...")
PrependAttrWithHyphen(false)
m, err := NewMapXml(data)
if err != nil {
t.Fatal(err)
}
vals, err := m.ValuesForKey("attr1")
if err != nil {
t.Fatal(err)
}
if len(vals) != 2 {
t.Fatal("didn't get 2 attr1 vals", len(vals))
}
vals, err = m.ValuesForKey("attr2")
if err != nil {
t.Fatal(err)
}
if len(vals) != 2 {
t.Fatal("didn't get 2 attr2 vals", len(vals))
}
}
func TestPrefixUnderscore(t *testing.T) {
fmt.Println("----------------- TestPrefixUnderscore ...")
SetAttrPrefix("_")
m, err := NewMapXml(data)
if err != nil {
t.Fatal(err)
}
vals, err := m.ValuesForKey("_attr1")
if err != nil {
t.Fatal(err)
}
if len(vals) != 2 {
t.Fatal("didn't get 2 _attr1 vals", len(vals))
}
vals, err = m.ValuesForKey("_attr2")
if err != nil {
t.Fatal(err)
}
if len(vals) != 2 {
t.Fatal("didn't get 2 _attr2 vals", len(vals))
}
}
func TestPrefixAt(t *testing.T) {
fmt.Println("----------------- TestPrefixAt ...")
SetAttrPrefix("@")
m, err := NewMapXml(data)
if err != nil {
t.Fatal(err)
}
vals, err := m.ValuesForKey("@attr1")
if err != nil {
t.Fatal(err)
}
if len(vals) != 2 {
t.Fatal("didn't get 2 @attr1 vals", len(vals))
}
vals, err = m.ValuesForKey("@attr2")
if err != nil {
t.Fatal(err)
}
if len(vals) != 2 {
t.Fatal("didn't get 2 @attr2 vals", len(vals))
}
}
func TestMarshalPrefixDefault(t *testing.T) {
fmt.Println("----------------- TestMarshalPrefixDefault ...")
m, err := NewMapXml(data)
if err != nil {
t.Fatal(err)
}
x, err := m.XmlIndent("", " ")
if err != nil {
t.Fatal(err)
}
fmt.Println(string(x))
}
func TestMarshalPrefixNoHyphen(t *testing.T) {
fmt.Println("----------------- TestMarshalPrefixNoHyphen ...")
PrependAttrWithHyphen(false)
m, err := NewMapXml(data)
if err != nil {
t.Fatal(err)
}
_, err = m.XmlIndent("", " ")
if err == nil {
t.Fatal("error not reported for invalid key label")
}
fmt.Println("err ok:", err)
}
func TestMarshalPrefixUnderscore(t *testing.T) {
fmt.Println("----------------- TestMarshalPrefixUnderscore ...")
SetAttrPrefix("_")
m, err := NewMapXml(data)
if err != nil {
t.Fatal(err)
}
x, err := m.XmlIndent("", " ")
if err != nil {
t.Fatal(err)
}
fmt.Println(string(x))
}

View File

@ -0,0 +1,68 @@
// trying to recreate a panic
package mxj
import (
"bytes"
"fmt"
"testing"
)
var baddata = []byte(`
something strange
<Allitems>
<Item>
</Item>
<Item>
<link>http://www.something.com</link>
<description>Some description goes here.</description>
</Item>
</Allitems>
`)
func TestBadXml(t *testing.T) {
fmt.Println("\n---------------- badxml_test.go")
fmt.Println("TestBadXml ...")
m, err := NewMapXml(baddata)
if err != nil {
t.Fatalf("err: didn't find xml.StartElement")
}
fmt.Printf("m: %v\n", m)
j, _ := m.Xml()
fmt.Println("m:", string(j))
}
func TestBadXmlSeq(t *testing.T) {
fmt.Println("TestBadXmlSeq ...")
m, err := NewMapXmlSeq(baddata)
if err != nil {
t.Fatalf("err: didn't find xmlStartElement")
}
fmt.Printf("m: %v\n", m)
j, _ := m.XmlSeq()
fmt.Println("m:", string(j))
}
func TestBadXmlReader(t *testing.T) {
fmt.Println("TestBadXmlReader ...")
r := bytes.NewReader(baddata)
m, err := NewMapXmlReader(r)
if err != nil {
t.Fatalf("err: didn't find xml.StartElement")
}
fmt.Printf("m: %v\n", m)
j, _ := m.Xml()
fmt.Println("m:", string(j))
}
func TestBadXmlSeqReader(t *testing.T) {
fmt.Println("TestBadXmlSeqReader ...")
r := bytes.NewReader(baddata)
m, err := NewMapXmlSeqReader(r)
if err != nil {
t.Fatalf("err: didn't find xmlStartElement")
}
fmt.Printf("m: %v\n", m)
j, _ := m.XmlSeq()
fmt.Println("m:", string(j))
}

View File

@ -0,0 +1,88 @@
// bomxml.go - test handling Byte-Order-Mark headers
package mxj
import (
"bytes"
"fmt"
"io"
"testing"
)
// Check for Byte-Order-Mark header.
var boms = [][]byte{
{'\xef', '\xbb', '\xbf'},
{'\xfe', '\xff'},
{'\xff', '\xfe'},
{'\x00', '\x00', '\xfe', '\xff'},
{'\xff', '\xfe', '\x00', '\x00'},
}
func TestBom(t *testing.T) {
fmt.Println("\n--------------- bom_test.go")
fmt.Println("TestBom ...")
// use just UTF-8 BOM ... no alternative CharSetReader
if _, err := NewMapXml(boms[0]); err != io.EOF {
t.Fatalf("NewMapXml err; %v\n", err)
}
if _, err := NewMapXmlSeq(boms[0]); err != io.EOF {
t.Fatalf("NewMapXmlSeq err: %v\n", err)
}
}
var bomdata = append(boms[0], []byte(`<Allitems>
<Item>
</Item>
<Item>
<link>http://www.something.com</link>
<description>Some description goes here.</description>
</Item>
</Allitems>`)...)
func TestBomData(t *testing.T) {
fmt.Println("TestBomData ...")
m, err := NewMapXml(bomdata)
if err != nil {
t.Fatalf("err: didn't find xml.StartElement")
}
fmt.Printf("m: %v\n", m)
j, _ := m.Xml()
fmt.Println("m:", string(j))
}
func TestBomDataSeq(t *testing.T) {
fmt.Println("TestBomDataSeq ...")
m, err := NewMapXmlSeq(bomdata)
if err != nil {
t.Fatalf("err: didn't find xml.StartElement")
}
fmt.Printf("m: %v\n", m)
j, _ := m.XmlSeq()
fmt.Println("m:", string(j))
}
func TestBomDataReader(t *testing.T) {
fmt.Println("TestBomDataReader ...")
r := bytes.NewReader(bomdata)
m, err := NewMapXmlReader(r)
if err != nil {
t.Fatalf("err: didn't find xml.StartElement")
}
fmt.Printf("m: %v\n", m)
j, _ := m.Xml()
fmt.Println("m:", string(j))
}
func TestBomDataSeqReader(t *testing.T) {
fmt.Println("TestBomDataSeqReader ...")
r := bytes.NewReader(bomdata)
m, err := NewMapXmlSeqReader(r)
if err != nil {
t.Fatalf("err: didn't find xml.StartElement")
}
fmt.Printf("m: %v\n", m)
j, _ := m.XmlSeq()
fmt.Println("m:", string(j))
}

View File

@ -0,0 +1,107 @@
// bulk_test.go - uses Handler and Writer functions to process some streams as a demo.
package mxj
import (
"bytes"
"fmt"
"testing"
)
func TestBulkHeader(t *testing.T) {
fmt.Println("\n---------------- bulk_test.go ...")
}
var jsonWriter = new(bytes.Buffer)
var xmlWriter = new(bytes.Buffer)
var jsonErrLog = new(bytes.Buffer)
var xmlErrLog = new(bytes.Buffer)
func TestXmlReader(t *testing.T) {
// create Reader for xmldata
xmlReader := bytes.NewReader(xmldata)
// read XML from Readerand pass Map value with the raw XML to handler
err := HandleXmlReader(xmlReader, bxmaphandler, bxerrhandler)
if err != nil {
t.Fatal("err:", err.Error())
}
// get the JSON
j := make([]byte, jsonWriter.Len())
_, _ = jsonWriter.Read(j)
// get the errors
e := make([]byte, xmlErrLog.Len())
_, _ = xmlErrLog.Read(e)
// print the input
fmt.Println("XmlReader, xmldata:\n", string(xmldata))
// print the result
fmt.Println("XmlReader, result :\n", string(j))
// print the errors
fmt.Println("XmlReader, errors :\n", string(e))
}
func bxmaphandler(m Map) bool {
j, err := m.JsonIndent("", " ", true)
if err != nil {
return false
}
_, _ = jsonWriter.Write(j)
// put in a NL to pretty up printing the Writer
_, _ = jsonWriter.Write([]byte("\n"))
return true
}
func bxerrhandler(err error) bool {
// write errors to file
_, _ = xmlErrLog.Write([]byte(err.Error()))
_, _ = xmlErrLog.Write([]byte("\n")) // pretty up
return true
}
func TestJsonReader(t *testing.T) {
jsonReader := bytes.NewReader(jsondata)
// read all the JSON
err := HandleJsonReader(jsonReader, bjmaphandler, bjerrhandler)
if err != nil {
t.Fatal("err:", err.Error())
}
// get the XML
x := make([]byte, xmlWriter.Len())
_, _ = xmlWriter.Read(x)
// get the errors
e := make([]byte, jsonErrLog.Len())
_, _ = jsonErrLog.Read(e)
// print the input
fmt.Println("JsonReader, jsondata:\n", string(jsondata))
// print the result
fmt.Println("JsonReader, result :\n", string(x))
// print the errors
fmt.Println("JsonReader, errors :\n", string(e))
}
func bjmaphandler(m Map) bool {
x, err := m.XmlIndent(" ", " ")
if err != nil {
return false
}
_, _ = xmlWriter.Write(x)
// put in a NL to pretty up printing the Writer
_, _ = xmlWriter.Write([]byte("\n"))
return true
}
func bjerrhandler(err error) bool {
// write errors to file
_, _ = jsonErrLog.Write([]byte(err.Error()))
_, _ = jsonErrLog.Write([]byte("\n")) // pretty up
return true
}

View File

@ -0,0 +1,113 @@
// bulk_test.go - uses Handler and Writer functions to process some streams as a demo.
package mxj
import (
"bytes"
"fmt"
"testing"
)
func TestBulkRawHeader(t *testing.T) {
fmt.Println("\n---------------- bulkraw_test.go ...")
}
// use data from bulk_test.go
var jsonWriterRaw = new(bytes.Buffer)
var xmlWriterRaw = new(bytes.Buffer)
var jsonErrLogRaw = new(bytes.Buffer)
var xmlErrLogRaw = new(bytes.Buffer)
func TestXmlReaderRaw(t *testing.T) {
// create Reader for xmldata
xmlReader := bytes.NewReader(xmldata)
// read XML from Reader and pass Map value with the raw XML to handler
err := HandleXmlReaderRaw(xmlReader, bxmaphandlerRaw, bxerrhandlerRaw)
if err != nil {
t.Fatal("err:", err.Error())
}
// get the JSON
j := make([]byte, jsonWriterRaw.Len())
_, _ = jsonWriterRaw.Read(j)
// get the errors
e := make([]byte, xmlErrLogRaw.Len())
_, _ = xmlErrLogRaw.Read(e)
// print the input
fmt.Println("XmlReaderRaw, xmldata:\n", string(xmldata))
// print the result
fmt.Println("XmlReaderRaw, result :\n", string(j))
// print the errors
fmt.Println("XmlReaderRaw, errors :\n", string(e))
}
func bxmaphandlerRaw(m Map, raw []byte) bool {
j, err := m.JsonIndent("", " ", true)
if err != nil {
return false
}
_, _ = jsonWriterRaw.Write(j)
// put in a NL to pretty up printing the Writer
_, _ = jsonWriterRaw.Write([]byte("\n"))
return true
}
func bxerrhandlerRaw(err error, raw []byte) bool {
// write errors to file
_, _ = xmlErrLogRaw.Write([]byte(err.Error()))
_, _ = xmlErrLogRaw.Write([]byte("\n")) // pretty up
_, _ = xmlErrLogRaw.Write(raw)
_, _ = xmlErrLogRaw.Write([]byte("\n")) // pretty up
return true
}
func TestJsonReaderRaw(t *testing.T) {
jsonReader := bytes.NewReader(jsondata)
// read all the JSON
err := HandleJsonReaderRaw(jsonReader, bjmaphandlerRaw, bjerrhandlerRaw)
if err != nil {
t.Fatal("err:", err.Error())
}
// get the XML
x := make([]byte, xmlWriterRaw.Len())
_, _ = xmlWriterRaw.Read(x)
// get the errors
e := make([]byte, jsonErrLogRaw.Len())
_, _ = jsonErrLogRaw.Read(e)
// print the input
fmt.Println("JsonReaderRaw, jsondata:\n", string(jsondata))
// print the result
fmt.Println("JsonReaderRaw, result :\n", string(x))
// print the errors
fmt.Println("JsonReaderRaw, errors :\n", string(e))
}
func bjmaphandlerRaw(m Map, raw []byte) bool {
x, err := m.XmlIndent(" ", " ")
if err != nil {
return false
}
_, _ = xmlWriterRaw.Write(x)
// put in a NL to pretty up printing the Writer
_, _ = xmlWriterRaw.Write([]byte("\n"))
return true
}
func bjerrhandlerRaw(err error, raw []byte) bool {
// write errors to file
_, _ = jsonErrLogRaw.Write([]byte(err.Error()))
_, _ = jsonErrLogRaw.Write([]byte("\n")) // pretty up, Error() from json.Unmarshal !NL
_, _ = jsonErrLogRaw.Write(raw)
_, _ = jsonErrLogRaw.Write([]byte("\n")) // pretty up
return true
}

View File

@ -0,0 +1,38 @@
package mxj
var xmldata = []byte(`
<book>
<author>William H. Gaddis</author>
<title>The Recognitions</title>
<review>One of the seminal American novels of the 20th century.</review>
</book>
<book>
<author>William H. Gaddis</author>
<title>JR</title>
<review>Won the National Book Award.</end_tag_error>
</book>
<book>
<author>Austin Tappan Wright</author>
<title>Islandia</title>
<review>An example of earlier 20th century American utopian fiction.</review>
</book>
<book>
<author>John Hawkes</author>
<title>The Beetle Leg</title>
<review>A lyrical novel about the construction of Ft. Peck Dam in Montana.</review>
</book>
<book>
<author>
<first_name>T.E.</first_name>
<last_name>Porter</last_name>
</author>
<title>King's Day</title>
<review>A magical novella.</review>
</book>`)
var jsondata = []byte(`
{"book":{"author":"William H. Gaddis","review":"One of the great seminal American novels of the 20th century.","title":"The Recognitions"}}
{"book":{"author":"Austin Tappan Wright","review":"An example of earlier 20th century American utopian fiction.","title":"Islandia"}}
{"book":{"author":"John Hawkes","review":"A lyrical novel about the construction of Ft. Peck Dam in Montana.","title":"The Beetle Leg"}}
{"book":{"author":{"first_name":"T.E.","last_name":"Porter"},"review":"A magical novella.","title":"King's Day"}}
{ "here":"we", "put":"in", "an":error }`)

View File

@ -0,0 +1,133 @@
// mxj - A collection of map[string]interface{} and associated XML and JSON utilities.
// Copyright 2012-2015 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
/*
Marshal/Unmarshal XML to/from JSON and map[string]interface{} values, and extract/modify values from maps by key or key-path, including wildcards.
mxj supplants the legacy x2j and j2x packages. The subpackage x2j-wrapper is provided to facilitate migrating from the x2j package. The x2j and j2x subpackages provide similar functionality of the old packages but are not function-name compatible with them.
Note: this library was designed for processing ad hoc anonymous messages. Bulk processing large data sets may be much more efficiently performed using the encoding/xml or encoding/json packages from Go's standard library directly.
Related Packages:
checkxml: github.com/clbanning/checkxml provides functions for validating XML data.
Notes:
2018.04.18: mv.Xml/mv.XmlIndent encodes non-map[string]interface{} map values - map[string]string, map[int]uint, etc.
2018.03.29: mv.Gob/NewMapGob support gob encoding/decoding of Maps.
2018.03.26: Added mxj/x2j-wrapper sub-package for migrating from legacy x2j package.
2017.02.22: LeafNode paths can use ".N" syntax rather than "[N]" for list member indexing.
2017.02.21: github.com/clbanning/checkxml provides functions for validating XML data.
2017.02.10: SetFieldSeparator changes field separator for args in UpdateValuesForPath, ValuesFor... methods.
2017.02.06: Support XMPP stream processing - HandleXMPPStreamTag().
2016.11.07: Preserve name space prefix syntax in XmlSeq parser - NewMapXmlSeq(), etc.
2016.06.25: Support overriding default XML attribute prefix, "-", in Map keys - SetAttrPrefix().
2016.05.26: Support customization of xml.Decoder by exposing CustomDecoder variable.
2016.03.19: Escape invalid chars when encoding XML attribute and element values - XMLEscapeChars().
2016.03.02: By default decoding XML with float64 and bool value casting will not cast "NaN", "Inf", and "-Inf".
To cast them to float64, first set flag with CastNanInf(true).
2016.02.22: New mv.Root(), mv.Elements(), mv.Attributes methods let you examine XML document structure.
2016.02.16: Add CoerceKeysToLower() option to handle tags with mixed capitalization.
2016.02.12: Seek for first xml.StartElement token; only return error if io.EOF is reached first (handles BOM).
2015-12-02: NewMapXmlSeq() with mv.XmlSeq() & co. will try to preserve structure of XML doc when re-encoding.
2014-08-02: AnyXml() and AnyXmlIndent() will try to marshal arbitrary values to XML.
SUMMARY
type Map map[string]interface{}
Create a Map value, 'm', from any map[string]interface{} value, 'v':
mv := Map(v)
Unmarshal / marshal XML as a Map value, 'm':
mv, err := NewMapXml(xmlValue) // unmarshal
xmlValue, err := m.Xml() // marshal
Unmarshal XML from an io.Reader as a Map value, 'm':
mv, err := NewMapReader(xmlReader) // repeated calls, as with an os.File Reader, will process stream
mv, raw, err := NewMapReaderRaw(xmlReader) // 'raw' is the raw XML that was decoded
Marshal Map value, 'm', to an XML Writer (io.Writer):
err := mv.XmlWriter(xmlWriter)
raw, err := mv.XmlWriterRaw(xmlWriter) // 'raw' is the raw XML that was written on xmlWriter
Also, for prettified output:
xmlValue, err := mv.XmlIndent(prefix, indent, ...)
err := mv.XmlIndentWriter(xmlWriter, prefix, indent, ...)
raw, err := mv.XmlIndentWriterRaw(xmlWriter, prefix, indent, ...)
Bulk process XML with error handling (note: handlers must return a boolean value):
err := HandleXmlReader(xmlReader, mapHandler(Map), errHandler(error))
err := HandleXmlReaderRaw(xmlReader, mapHandler(Map, []byte), errHandler(error, []byte))
Converting XML to JSON: see Examples for NewMapXml and HandleXmlReader.
There are comparable functions and methods for JSON processing.
Arbitrary structure values can be decoded to / encoded from Map values:
mv, err := NewMapStruct(structVal)
err := mv.Struct(structPointer)
To work with XML tag values, JSON or Map key values or structure field values, decode the XML, JSON
or structure to a Map value, 'm', or cast a map[string]interface{} value to a Map value, 'm', then:
paths := mv.PathsForKey(key)
path := mv.PathForKeyShortest(key)
values, err := mv.ValuesForKey(key, subkeys)
values, err := mv.ValuesForPath(path, subkeys) // 'path' can be dot-notation with wildcards and indexed arrays.
count, err := mv.UpdateValuesForPath(newVal, path, subkeys)
Get everything at once, irrespective of path depth:
leafnodes := mv.LeafNodes()
leafvalues := mv.LeafValues()
A new Map with whatever keys are desired can be created from the current Map and then encoded in XML
or JSON. (Note: keys can use dot-notation. 'oldKey' can also use wildcards and indexed arrays.)
newMap, err := mv.NewMap("oldKey_1:newKey_1", "oldKey_2:newKey_2", ..., "oldKey_N:newKey_N")
newXml, err := newMap.Xml() // for example
newJson, err := newMap.Json() // ditto
XML PARSING CONVENTIONS
Using NewMapXml()
- Attributes are parsed to `map[string]interface{}` values by prefixing a hyphen, `-`,
to the attribute label. (Unless overridden by `PrependAttrWithHyphen(false)` or
`SetAttrPrefix()`.)
- If the element is a simple element and has attributes, the element value
is given the key `#text` for its `map[string]interface{}` representation. (See
the 'atomFeedString.xml' test data, below.)
- XML comments, directives, and process instructions are ignored.
- If CoerceKeysToLower() has been called, then the resultant keys will be lower case.
Using NewMapXmlSeq()
- Attributes are parsed to `map["#attr"]map[<attr_label>]map[string]interface{}`values
where the `<attr_label>` value has "#text" and "#seq" keys - the "#text" key holds the
value for `<attr_label>`.
- All elements, except for the root, have a "#seq" key.
- Comments, directives, and process instructions are unmarshalled into the Map using the
keys "#comment", "#directive", and "#procinst", respectively. (See documentation for more
specifics.)
- Name space syntax is preserved:
- <ns:key>something</ns.key> parses to map["ns:key"]interface{}{"something"}
- xmlns:ns="http://myns.com/ns" parses to map["xmlns:ns"]interface{}{"http://myns.com/ns"}
Both
- By default, "Nan", "Inf", and "-Inf" values are not cast to float64. If you want them
to be cast, set a flag to cast them using CastNanInf(true).
XML ENCODING CONVENTIONS
- 'nil' Map values, which may represent 'null' JSON values, are encoded as "<tag/>".
NOTE: the operation is not symmetric as "<tag/>" elements are decoded as 'tag:""' Map values,
which, then, encode in JSON as '"tag":""' values..
- ALSO: there is no guarantee that the encoded XML doc will be the same as the decoded one. (Go
randomizes the walk through map[string]interface{} values.) If you plan to re-encode the
Map value to XML and want the same sequencing of elements look at NewMapXmlSeq() and
m.XmlSeq() - these try to preserve the element sequencing but with added complexity when
working with the Map representation.
*/
package mxj

View File

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

View File

@ -0,0 +1,91 @@
package mxj
import (
"fmt"
"testing"
)
var s = `"'<>&`
func TestEscapeChars(t *testing.T) {
fmt.Println("\n================== TestEscapeChars")
ss := escapeChars(s)
if ss != `&quot;&apos;&lt;&gt;&amp;` {
t.Fatal(s, ":", ss)
}
fmt.Println(" s:", s)
fmt.Println("ss:", ss)
}
func TestXMLEscapeChars(t *testing.T) {
fmt.Println("================== TestXMLEscapeChars")
XMLEscapeChars(true)
defer XMLEscapeChars(false)
m := map[string]interface{}{"mychars":s}
x, err := AnyXmlIndent(s, "", " ")
if err != nil {
t.Fatal(err)
}
fmt.Println("s:", string(x))
x, err = AnyXmlIndent(m, "", " ")
if err != nil {
t.Fatal(err)
}
fmt.Println("m:", string(x))
}
func TestXMLSeqEscapeChars(t *testing.T) {
fmt.Println("================== TestXMLSeqEscapeChars")
data := []byte(`
<doc>
<shortDescription>&gt;0-2y</shortDescription>
</doc>`)
fmt.Println("data:", string(data))
m, err := NewMapXmlSeq(data)
if err != nil {
t.Fatal(err)
}
fmt.Printf("m: %v\n", m)
XMLEscapeChars(true)
defer XMLEscapeChars(false)
x, err := m.XmlSeqIndent("", " ")
if err != nil {
t.Fatal(err)
}
fmt.Println("m:", string(x))
}
func TestXMLSeqEscapeChars2(t *testing.T) {
fmt.Println("================== TestXMLSeqEscapeChars2")
data := []byte(`
<doc>
<shortDescription test="&amp;something here">&gt;0-2y</shortDescription>
<shortDescription test="something there" quote="&quot;">&lt;10-15</shortDescription>
</doc>`)
fmt.Println("data:", string(data))
m, err := NewMapXmlSeq(data)
if err != nil {
t.Fatal(err)
}
fmt.Printf("m: %v\n", m)
XMLEscapeChars(true)
defer XMLEscapeChars(false)
x, err := m.XmlSeqIndent("", " ")
if err != nil {
t.Fatal(err)
}
fmt.Println("m:", string(x))
}

View File

@ -0,0 +1,347 @@
// +test OMIT
// note - "// Output:" is a key for "go test" to match function ouput with the lines that follow.
// It is also use by "godoc" to build the Output block of the function / method documentation.
// To skip processing Example* functions, use: go test -run "Test*"
// or make sure example function output matches // Output: documentation EXACTLY.
package mxj_test
import (
"bytes"
"fmt"
"gitee.com/johng/gf/third/github.com/clbanning/mxj"
"io"
)
func ExampleHandleXmlReader() {
/*
Bulk processing XML to JSON seems to be a common requirement.
See: bulk_test.go for working example.
Run "go test" in package directory then scroll back to find output.
The logic is as follows.
// need somewhere to write the JSON.
var jsonWriter io.Writer
// probably want to log any errors in reading the XML stream
var xmlErrLogger io.Writer
// func to handle Map value from XML Reader
func maphandler(m mxj.Map) bool {
// marshal Map as JSON
jsonVal, err := m.Json()
if err != nil {
// log error
return false // stops further processing of XML Reader
}
// write JSON somewhere
_, err = jsonWriter.Write(jsonVal)
if err != nil {
// log error
return false // stops further processing of XML Reader
}
// continue - get next XML from Reader
return true
}
// func to handle error from unmarshaling XML Reader
func errhandler(errVal error) bool {
// log error somewhere
_, err := xmlErrLogger.Write([]byte(errVal.Error()))
if err != nil {
// log error
return false // stops further processing of XML Reader
}
// continue processing
return true
}
// func that starts bulk processing of the XML
...
// set up io.Reader for XML data - perhaps an os.File
...
err := mxj.HandleXmlReader(xmlReader, maphandler, errhandler)
if err != nil {
// handle error
}
...
*/
}
func ExampleHandleXmlReaderRaw() {
/*
See: bulkraw_test.go for working example.
Run "go test" in package directory then scroll back to find output.
Basic logic for bulk XML to JSON processing is in HandleXmlReader example;
the only major difference is in handler function signatures so they are passed
the raw XML. (Read documentation on NewXmlReader regarding performance.)
*/
}
func ExampleHandleJsonReader() {
/*
See: bulk_test.go for working example.
Run "go test" in package directory then scroll back to find output.
Basic logic for bulk JSON to XML processing is similar to that for
bulk XML to JSON processing as outlined in the HandleXmlReader example.
The test case is also a good example.
*/
}
func ExampleHandleJsonReaderRaw() {
/*
See: bulkraw_test.go for working example.
Run "go test" in package directory then scroll back to find output.
Basic logic for bulk JSON to XML processing is similar to that for
bulk XML to JSON processing as outlined in the HandleXmlReader example.
The test case is also a good example.
*/
}
/*
func ExampleNewMapXmlReaderRaw() {
// in an http.Handler
mapVal, raw, err := mxj.NewMapXmlReader(req.Body)
if err != nil {
// handle error
}
logger.Print(string(*raw))
// do something with mapVal
}
*/
func ExampleNewMapStruct() {
type str struct {
IntVal int `structs:"int"`
StrVal string `structs:"str"`
FloatVal float64 `structs:"float"`
BoolVal bool `structs:"bool"`
private string
}
strVal := str{IntVal: 4, StrVal: "now's the time", FloatVal: 3.14159, BoolVal: true, private: "Skies are blue"}
mapVal, merr := mxj.NewMapStruct(strVal)
if merr != nil {
// handle error
}
fmt.Printf("strVal: %#v\n", strVal)
fmt.Printf("mapVal: %#v\n", mapVal)
// Note: example output is conformed to pass "go test". "mxj_test" is example_test.go package name.
// NoFail output:
// strVal: mxj_test.str{IntVal:4, StrVal:"now's the time", FloatVal:3.14159, BoolVal:true, private:"Skies are blue"}
// mapVal: mxj.Map{"float":3.14159, "bool":true, "int":4, "str":"now's the time"}
}
func ExampleMap_Struct() {
type str struct {
IntVal int `json:"int"`
StrVal string `json:"str"`
FloatVal float64 `json:"float"`
BoolVal bool `json:"bool"`
private string
}
mapVal := mxj.Map{"int": 4, "str": "now's the time", "float": 3.14159, "bool": true, "private": "Somewhere over the rainbow"}
var strVal str
mverr := mapVal.Struct(&strVal)
if mverr != nil {
// handle error
}
fmt.Printf("mapVal: %#v\n", mapVal)
fmt.Printf("strVal: %#v\n", strVal)
// Note: example output is conformed to pass "go test". "mxj_test" is example_test.go package name.
// Unordered output:
// mapVal: mxj.Map{"int":4, "str":"now's the time", "float":3.14159, "bool":true, "private":"Somewhere over the rainbow"}
// strVal: mxj_test.str{IntVal:4, StrVal:"now's the time", FloatVal:3.14159, BoolVal:true, private:""}
}
func ExampleMap_ValuesForPath() {
// a snippet from examples/gonuts1.go
// How to compensate for irregular tag labels in data.
// Need to extract from an XML stream the values for "netid" and "idnet".
// Solution: use a wildcard path "data.*" to anonymize the "netid" and "idnet" tags.
var msg1 = []byte(`
<?xml version="1.0" encoding="UTF-8"?>
<data>
<netid>
<disable>no</disable>
<text1>default:text</text1>
<word1>default:word</word1>
</netid>
</data>
`)
var msg2 = []byte(`
<?xml version="1.0" encoding="UTF-8"?>
<data>
<idnet>
<disable>yes</disable>
<text1>default:text</text1>
<word1>default:word</word1>
</idnet>
</data>
`)
// let's create a message stream
buf := new(bytes.Buffer)
// load a couple of messages into it
_, _ = buf.Write(msg1)
_, _ = buf.Write(msg2)
n := 0
for {
n++
// Read the stream as Map values - quit on io.EOF.
// Get the raw XML as well as the Map value.
m, merr := mxj.NewMapXmlReader(buf)
if merr != nil && merr != io.EOF {
// handle error - for demo we just print it and continue
fmt.Printf("msg: %d - merr: %s\n", n, merr.Error())
continue
} else if merr == io.EOF {
break
}
// get the values for "netid" or "idnet" key using path == "data.*"
values, _ := m.ValuesForPath("data.*")
fmt.Println("\nmsg:", n, "> path == data.* - got array of values, len:", len(values))
for i, val := range values {
fmt.Println("ValuesForPath result array member -", i, ":", val)
fmt.Println(" k:v pairs for array member:", i)
for key, val := range val.(map[string]interface{}) {
// You'd probably want to process the value, as appropriate.
// Here we just print it out.
fmt.Println("\t\t", key, ":", val)
}
}
}
// NoFail output:
// msg: 1 > path == data.* - got array of values, len: 1
// ValuesForPath result array member - 0 : map[disable:no text1:default:text word1:default:word]
// k:v pairs for array member: 0
// disable : no
// text1 : default:text
// word1 : default:word
//
// msg: 2 > path == data.* - got array of values, len: 1
// ValuesForPath result array member - 0 : map[disable:yes text1:default:text word1:default:word]
// k:v pairs for array member: 0
// disable : yes
// text1 : default:text
// word1 : default:word
}
func ExampleMap_UpdateValuesForPath() {
/*
var biblioDoc = []byte(`
<biblio>
<author>
<name>William Gaddis</name>
<books>
<book>
<title>The Recognitions</title>
<date>1955</date>
<review>A novel that changed the face of American literature.</review>
</book>
<book>
<title>JR</title>
<date>1975</date>
<review>Winner of National Book Award for Fiction.</review>
</book>
</books>
</author>
</biblio>`)
...
m, merr := mxj.NewMapXml(biblioDoc)
if merr != nil {
// handle error
}
// change 'review' for a book
count, err := m.UpdateValuesForPath("review:National Book Award winner." "*.*.*.*", "title:JR")
if err != nil {
// handle error
}
...
// change 'date' value from string type to float64 type
// Note: the following is equivalent to m, merr := NewMapXml(biblioDoc, mxj.Cast).
path := m.PathForKeyShortest("date")
v, err := m.ValuesForPath(path)
if err != nil {
// handle error
}
var total int
for _, vv := range v {
oldVal := "date:" + vv.(string)
newVal := "date:" + vv.(string) + ":num"
n, err := m.UpdateValuesForPath(newVal, path, oldVal)
if err != nil {
// handle error
}
total += n
}
...
*/
}
func ExampleMap_Copy() {
// Hand-crafted Map values that include structures do NOT Copy() as expected,
// since to simulate a deep copy the original Map value is JSON encoded then decoded.
type str struct {
IntVal int `json:"int"`
StrVal string `json:"str"`
FloatVal float64 `json:"float"`
BoolVal bool `json:"bool"`
private string
}
s := str{IntVal: 4, StrVal: "now's the time", FloatVal: 3.14159, BoolVal: true, private: "Skies are blue"}
m := make(map[string]interface{}, 0)
m["struct"] = interface{}(s)
m["struct_ptr"] = interface{}(&s)
m["misc"] = interface{}(`Now is the time`)
mv := mxj.Map(m)
cp, _ := mv.Copy()
fmt.Printf("mv:\n%s\n", mv.StringIndent(2))
fmt.Printf("cp:\n%s\n", cp.StringIndent(2))
// NoFail output:
// mv:
// misc : [string] Now is the time
// struct : [mxj_test.str] {IntVal:4 StrVal:now's the time FloatVal:3.14159 BoolVal:true private:Skies are blue}
// struct_ptr : [*mxj_test.str] &{IntVal:4 StrVal:now's the time FloatVal:3.14159 BoolVal:true private:Skies are blue}
// cp:
// misc : [string] Now is the time
// struct :
// bool : [bool] true
// float : [float64] 3.14159
// int : [float64] 4
// str : [string] now's the time
// struct_ptr :
// bool : [bool] true
// float : [float64] 3.14159
// int : [float64] 4
// str : [string] now's the time
//
}

View File

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

View File

@ -0,0 +1,37 @@
package mxj
import (
"testing"
)
func TestExists(t *testing.T) {
m := map[string]interface{}{
"Div": map[string]interface{}{
"Colour": "blue",
},
}
mv := Map(m)
if !mv.Exists("Div.Colour") {
t.Fatal("Haven't found an existing element")
}
if mv.Exists("Div.Color") {
t.Fatal("Have found a non existing element")
}
}
func TestExistsWithSubKeys(t *testing.T) {
mv, err := NewMapXml(doc2)
if err != nil {
t.Fatal("err:", err.Error())
}
if !mv.Exists("doc.books.book", "-seq:1") {
t.Fatal("Haven't found an existing element")
}
if mv.Exists("doc.books.book", "-seq:2") {
t.Fatal("Have found a non existing element")
}
}

View File

@ -0,0 +1,287 @@
package mxj
import (
"fmt"
"io"
"os"
)
type Maps []Map
func NewMaps() Maps {
return make(Maps, 0)
}
type MapRaw struct {
M Map
R []byte
}
// NewMapsFromXmlFile - creates an array from a file of JSON values.
func NewMapsFromJsonFile(name string) (Maps, error) {
fi, err := os.Stat(name)
if err != nil {
return nil, err
}
if !fi.Mode().IsRegular() {
return nil, fmt.Errorf("file %s is not a regular file", name)
}
fh, err := os.Open(name)
if err != nil {
return nil, err
}
defer fh.Close()
am := make([]Map, 0)
for {
m, raw, err := NewMapJsonReaderRaw(fh)
if err != nil && err != io.EOF {
return am, fmt.Errorf("error: %s - reading: %s", err.Error(), string(raw))
}
if len(m) > 0 {
am = append(am, m)
}
if err == io.EOF {
break
}
}
return am, nil
}
// ReadMapsFromJsonFileRaw - creates an array of MapRaw from a file of JSON values.
func NewMapsFromJsonFileRaw(name string) ([]MapRaw, error) {
fi, err := os.Stat(name)
if err != nil {
return nil, err
}
if !fi.Mode().IsRegular() {
return nil, fmt.Errorf("file %s is not a regular file", name)
}
fh, err := os.Open(name)
if err != nil {
return nil, err
}
defer fh.Close()
am := make([]MapRaw, 0)
for {
mr := new(MapRaw)
mr.M, mr.R, err = NewMapJsonReaderRaw(fh)
if err != nil && err != io.EOF {
return am, fmt.Errorf("error: %s - reading: %s", err.Error(), string(mr.R))
}
if len(mr.M) > 0 {
am = append(am, *mr)
}
if err == io.EOF {
break
}
}
return am, nil
}
// NewMapsFromXmlFile - creates an array from a file of XML values.
func NewMapsFromXmlFile(name string) (Maps, error) {
fi, err := os.Stat(name)
if err != nil {
return nil, err
}
if !fi.Mode().IsRegular() {
return nil, fmt.Errorf("file %s is not a regular file", name)
}
fh, err := os.Open(name)
if err != nil {
return nil, err
}
defer fh.Close()
am := make([]Map, 0)
for {
m, raw, err := NewMapXmlReaderRaw(fh)
if err != nil && err != io.EOF {
return am, fmt.Errorf("error: %s - reading: %s", err.Error(), string(raw))
}
if len(m) > 0 {
am = append(am, m)
}
if err == io.EOF {
break
}
}
return am, nil
}
// NewMapsFromXmlFileRaw - creates an array of MapRaw from a file of XML values.
// NOTE: the slice with the raw XML is clean with no extra capacity - unlike NewMapXmlReaderRaw().
// It is slow at parsing a file from disk and is intended for relatively small utility files.
func NewMapsFromXmlFileRaw(name string) ([]MapRaw, error) {
fi, err := os.Stat(name)
if err != nil {
return nil, err
}
if !fi.Mode().IsRegular() {
return nil, fmt.Errorf("file %s is not a regular file", name)
}
fh, err := os.Open(name)
if err != nil {
return nil, err
}
defer fh.Close()
am := make([]MapRaw, 0)
for {
mr := new(MapRaw)
mr.M, mr.R, err = NewMapXmlReaderRaw(fh)
if err != nil && err != io.EOF {
return am, fmt.Errorf("error: %s - reading: %s", err.Error(), string(mr.R))
}
if len(mr.M) > 0 {
am = append(am, *mr)
}
if err == io.EOF {
break
}
}
return am, nil
}
// ------------------------ Maps writing -------------------------
// These are handy-dandy methods for dumping configuration data, etc.
// JsonString - analogous to mv.Json()
func (mvs Maps) JsonString(safeEncoding ...bool) (string, error) {
var s string
for _, v := range mvs {
j, err := v.Json()
if err != nil {
return s, err
}
s += string(j)
}
return s, nil
}
// JsonStringIndent - analogous to mv.JsonIndent()
func (mvs Maps) JsonStringIndent(prefix, indent string, safeEncoding ...bool) (string, error) {
var s string
var haveFirst bool
for _, v := range mvs {
j, err := v.JsonIndent(prefix, indent)
if err != nil {
return s, err
}
if haveFirst {
s += "\n"
} else {
haveFirst = true
}
s += string(j)
}
return s, nil
}
// XmlString - analogous to mv.Xml()
func (mvs Maps) XmlString() (string, error) {
var s string
for _, v := range mvs {
x, err := v.Xml()
if err != nil {
return s, err
}
s += string(x)
}
return s, nil
}
// XmlStringIndent - analogous to mv.XmlIndent()
func (mvs Maps) XmlStringIndent(prefix, indent string) (string, error) {
var s string
for _, v := range mvs {
x, err := v.XmlIndent(prefix, indent)
if err != nil {
return s, err
}
s += string(x)
}
return s, nil
}
// JsonFile - write Maps to named file as JSON
// Note: the file will be created, if necessary; if it exists it will be truncated.
// If you need to append to a file, open it and use JsonWriter method.
func (mvs Maps) JsonFile(file string, safeEncoding ...bool) error {
var encoding bool
if len(safeEncoding) == 1 {
encoding = safeEncoding[0]
}
s, err := mvs.JsonString(encoding)
if err != nil {
return err
}
fh, err := os.Create(file)
if err != nil {
return err
}
defer fh.Close()
fh.WriteString(s)
return nil
}
// JsonFileIndent - write Maps to named file as pretty JSON
// Note: the file will be created, if necessary; if it exists it will be truncated.
// If you need to append to a file, open it and use JsonIndentWriter method.
func (mvs Maps) JsonFileIndent(file, prefix, indent string, safeEncoding ...bool) error {
var encoding bool
if len(safeEncoding) == 1 {
encoding = safeEncoding[0]
}
s, err := mvs.JsonStringIndent(prefix, indent, encoding)
if err != nil {
return err
}
fh, err := os.Create(file)
if err != nil {
return err
}
defer fh.Close()
fh.WriteString(s)
return nil
}
// XmlFile - write Maps to named file as XML
// Note: the file will be created, if necessary; if it exists it will be truncated.
// If you need to append to a file, open it and use XmlWriter method.
func (mvs Maps) XmlFile(file string) error {
s, err := mvs.XmlString()
if err != nil {
return err
}
fh, err := os.Create(file)
if err != nil {
return err
}
defer fh.Close()
fh.WriteString(s)
return nil
}
// XmlFileIndent - write Maps to named file as pretty XML
// Note: the file will be created,if necessary; if it exists it will be truncated.
// If you need to append to a file, open it and use XmlIndentWriter method.
func (mvs Maps) XmlFileIndent(file, prefix, indent string) error {
s, err := mvs.XmlStringIndent(prefix, indent)
if err != nil {
return err
}
fh, err := os.Create(file)
if err != nil {
return err
}
defer fh.Close()
fh.WriteString(s)
return nil
}

View File

@ -0,0 +1,2 @@
{ "this":"is", "a":"test", "file":"for", "files_test.go":"case" }
{ "with":"some", "bad":JSON, "in":"it" }

View File

@ -0,0 +1,9 @@
<doc>
<some>test</some>
<data>for files.go</data>
</doc>
<msg>
<just>some</just>
<another>doc</other>
<for>test case</for>
</msg>

View File

@ -0,0 +1,168 @@
package mxj
import (
"fmt"
"testing"
)
func TestFilesHeader(t *testing.T) {
fmt.Println("\n---------------- files_test.go ...")
}
func TestNewJsonFile(t *testing.T) {
fmt.Println("NewMapsFromJsonFile()")
am, err := NewMapsFromJsonFile("files_test.json")
if err != nil {
t.Fatal(err.Error())
}
for _, v := range am {
fmt.Printf("%v\n", v)
}
am, err = NewMapsFromJsonFile("nil")
if err == nil {
t.Fatal("no error returned for read of nil file")
}
fmt.Println("caught error: ", err.Error())
am, err = NewMapsFromJsonFile("files_test.badjson")
if err == nil {
t.Fatal("no error returned for read of badjson file")
}
fmt.Println("caught error: ", err.Error())
}
func TestNewJsonFileRaw(t *testing.T) {
fmt.Println("NewMapsFromJsonFileRaw()")
mr, err := NewMapsFromJsonFileRaw("files_test.json")
if err != nil {
t.Fatal(err.Error())
}
for _, v := range mr {
fmt.Printf("%v\n", v)
}
mr, err = NewMapsFromJsonFileRaw("nil")
if err == nil {
t.Fatal("no error returned for read of nil file")
}
fmt.Println("caught error: ", err.Error())
mr, err = NewMapsFromJsonFileRaw("files_test.badjson")
if err == nil {
t.Fatal("no error returned for read of badjson file")
}
fmt.Println("caught error: ", err.Error())
}
func TestNewXmFile(t *testing.T) {
fmt.Println("NewMapsFromXmlFile()")
am, err := NewMapsFromXmlFile("files_test.xml")
if err != nil {
t.Fatal(err.Error())
}
for _, v := range am {
fmt.Printf("%v\n", v)
}
am, err = NewMapsFromXmlFile("nil")
if err == nil {
t.Fatal("no error returned for read of nil file")
}
fmt.Println("caught error: ", err.Error())
am, err = NewMapsFromXmlFile("files_test.badxml")
if err == nil {
t.Fatal("no error returned for read of badjson file")
}
fmt.Println("caught error: ", err.Error())
}
func TestNewXmFileRaw(t *testing.T) {
fmt.Println("NewMapsFromXmlFileRaw()")
mr, err := NewMapsFromXmlFileRaw("files_test.xml")
if err != nil {
t.Fatal(err.Error())
}
for _, v := range mr {
fmt.Printf("%v\n", v)
}
mr, err = NewMapsFromXmlFileRaw("nil")
if err == nil {
t.Fatal("no error returned for read of nil file")
}
fmt.Println("caught error: ", err.Error())
mr, err = NewMapsFromXmlFileRaw("files_test.badxml")
if err == nil {
t.Fatal("no error returned for read of badjson file")
}
fmt.Println("caught error: ", err.Error())
}
func TestMaps(t *testing.T) {
fmt.Println("TestMaps()")
mvs := NewMaps()
for i := 0; i < 2; i++ {
m, _ := NewMapJson([]byte(`{ "this":"is", "a":"test" }`))
mvs = append(mvs, m)
}
fmt.Println("mvs:", mvs)
s, _ := mvs.JsonString()
fmt.Println("JsonString():", s)
s, _ = mvs.JsonStringIndent("", " ")
fmt.Println("JsonStringIndent():", s)
s, _ = mvs.XmlString()
fmt.Println("XmlString():", s)
s, _ = mvs.XmlStringIndent("", " ")
fmt.Println("XmlStringIndent():", s)
}
func TestJsonFile(t *testing.T) {
am, err := NewMapsFromJsonFile("files_test.json")
if err != nil {
t.Fatal(err.Error())
}
for _, v := range am {
fmt.Printf("%v\n", v)
}
err = am.JsonFile("files_test_dup.json")
if err != nil {
t.Fatal(err.Error())
}
fmt.Println("files_test_dup.json written")
err = am.JsonFileIndent("files_test_indent.json", "", " ")
if err != nil {
t.Fatal(err.Error())
}
fmt.Println("files_test_indent.json written")
}
func TestXmlFile(t *testing.T) {
am, err := NewMapsFromXmlFile("files_test.xml")
if err != nil {
t.Fatal(err.Error())
}
for _, v := range am {
fmt.Printf("%v\n", v)
}
err = am.XmlFile("files_test_dup.xml")
if err != nil {
t.Fatal(err.Error())
}
fmt.Println("files_test_dup.xml written")
err = am.XmlFileIndent("files_test_indent.xml", "", " ")
if err != nil {
t.Fatal(err.Error())
}
fmt.Println("files_test_indent.xml written")
}

View File

@ -0,0 +1,2 @@
{ "this":"is", "a":"test", "file":"for", "files_test.go":"case" }
{ "with":"just", "two":2, "JSON":"values", "true":true }

View File

@ -0,0 +1,9 @@
<doc>
<some>test</some>
<data>for files.go</data>
</doc>
<msg>
<just>some</just>
<another>doc</another>
<for>test case</for>
</msg>

View File

@ -0,0 +1 @@
{"a":"test","file":"for","files_test.go":"case","this":"is"}{"JSON":"values","true":true,"two":2,"with":"just"}

View File

@ -0,0 +1 @@
<doc><data>for files.go</data><some>test</some></doc><msg><another>doc</another><for>test case</for><just>some</just></msg>

View File

@ -0,0 +1,12 @@
{
"a": "test",
"file": "for",
"files_test.go": "case",
"this": "is"
}
{
"JSON": "values",
"true": true,
"two": 2,
"with": "just"
}

View File

@ -0,0 +1,8 @@
<doc>
<data>for files.go</data>
<some>test</some>
</doc><msg>
<another>doc</another>
<for>test case</for>
<just>some</just>
</msg>

View File

@ -0,0 +1,35 @@
// gob.go - Encode/Decode a Map into a gob object.
package mxj
import (
"bytes"
"encoding/gob"
)
// NewMapGob returns a Map value for a gob object that has been
// encoded from a map[string]interface{} (or compatible type) value.
// It is intended to provide symmetric handling of Maps that have
// been encoded using mv.Gob.
func NewMapGob(gobj []byte) (Map, error) {
m := make(map[string]interface{}, 0)
if len(gobj) == 0 {
return m, nil
}
r := bytes.NewReader(gobj)
dec := gob.NewDecoder(r)
if err := dec.Decode(&m); err != nil {
return m, err
}
return m, nil
}
// Gob returns a gob-encoded value for the Map 'mv'.
func (mv Map) Gob() ([]byte, error) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(map[string]interface{}(mv)); err != nil {
return nil, err
}
return buf.Bytes(), nil
}

View File

@ -0,0 +1,64 @@
package mxj
import (
"bytes"
"encoding/gob"
"fmt"
"testing"
)
var gobData = map[string]interface{}{
"one": 1,
"two": 2.0001,
"three": "tres",
"four": []int{1, 2, 3, 4},
"five": map[string]interface{}{"hi": "there"}}
func TestGobHeader(t *testing.T) {
fmt.Println("\n---------------- gob_test.go ...")
}
func TestNewMapGob(t *testing.T) {
var buf bytes.Buffer
gob.Register(gobData)
enc := gob.NewEncoder(&buf)
if err := enc.Encode(gobData); err != nil {
t.Fatal("enc.Encode err:", err.Error())
}
// decode 'buf' into a Map - map[string]interface{}
m, err := NewMapGob(buf.Bytes())
if err != nil {
t.Fatal("NewMapGob err:", err.Error())
}
fmt.Printf("m: %v\n", m)
}
func TestMapGob(t *testing.T) {
mv := Map(gobData)
g, err := mv.Gob()
if err != nil {
t.Fatal("m.Gob err:", err.Error())
}
// decode 'g' into a map[string]interface{}
m := make(map[string]interface{})
r := bytes.NewReader(g)
dec := gob.NewDecoder(r)
if err := dec.Decode(&m); err != nil {
t.Fatal("dec.Decode err:", err.Error())
}
fmt.Printf("m: %v\n", m)
}
func TestGobSymmetric(t *testing.T) {
mv := Map(gobData)
fmt.Printf("mv: %v\n", mv)
g, err := mv.Gob()
if err != nil {
t.Fatal("m.Gob err:", err.Error())
}
m, err := NewMapGob(g)
if err != nil {
t.Fatal("NewMapGob err:", err.Error())
}
fmt.Printf("m : %v\n", m)
}

View File

@ -0,0 +1,178 @@
// Copyright 2012-2014 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
// j2x.go - For (mostly) backwards compatibility with legacy j2x package.
// Wrappers for end-to-end JSON to XML transformation and value manipulation.
package j2x
import (
. "gitee.com/johng/gf/third/github.com/clbanning/mxj"
"io"
)
// FromJson() --> map[string]interface{}
func JsonToMap(jsonVal []byte) (map[string]interface{}, error) {
return NewMapJson(jsonVal)
}
// interface{} --> ToJson (w/o safe encoding, default) {
func MapToJson(m map[string]interface{}, safeEncoding ...bool) ([]byte, error) {
return Map(m).Json()
}
// FromJson() --> ToXml().
func JsonToXml(jsonVal []byte) ([]byte, error) {
m, err := NewMapJson(jsonVal)
if err != nil {
return nil, err
}
return m.Xml()
}
// FromJson() --> ToXmlWriter().
func JsonToXmlWriter(jsonVal []byte, xmlWriter io.Writer) ([]byte, error) {
m, err := NewMapJson(jsonVal)
if err != nil {
return nil, err
}
return m.XmlWriterRaw(xmlWriter)
}
// FromJsonReader() --> ToXml().
func JsonReaderToXml(jsonReader io.Reader) ([]byte, []byte, error) {
m, jraw, err := NewMapJsonReaderRaw(jsonReader)
if err != nil {
return jraw, nil, err
}
x, xerr := m.Xml()
return jraw, x, xerr
}
// FromJsonReader() --> ToXmlWriter(). Handy for transforming bulk message sets.
func JsonReaderToXmlWriter(jsonReader io.Reader, xmlWriter io.Writer) ([]byte, []byte, error) {
m, jraw, err := NewMapJsonReaderRaw(jsonReader)
if err != nil {
return jraw, nil, err
}
xraw, xerr := m.XmlWriterRaw(xmlWriter)
return jraw, xraw, xerr
}
// JSON wrappers for Map methods implementing key path and value functions.
// Wrap PathsForKey for JSON.
func JsonPathsForKey(jsonVal []byte, key string) ([]string, error) {
m, err := NewMapJson(jsonVal)
if err != nil {
return nil, err
}
paths := m.PathsForKey(key)
return paths, nil
}
// Wrap PathForKeyShortest for JSON.
func JsonPathForKeyShortest(jsonVal []byte, key string) (string, error) {
m, err := NewMapJson(jsonVal)
if err != nil {
return "", err
}
path := m.PathForKeyShortest(key)
return path, nil
}
// Wrap ValuesForKey for JSON.
func JsonValuesForKey(jsonVal []byte, key string, subkeys ...string) ([]interface{}, error) {
m, err := NewMapJson(jsonVal)
if err != nil {
return nil, err
}
return m.ValuesForKey(key, subkeys...)
}
// Wrap ValuesForKeyPath for JSON.
func JsonValuesForKeyPath(jsonVal []byte, path string, subkeys ...string) ([]interface{}, error) {
m, err := NewMapJson(jsonVal)
if err != nil {
return nil, err
}
return m.ValuesForPath(path, subkeys...)
}
// Wrap UpdateValuesForPath for JSON
// 'jsonVal' is XML value
// 'newKeyValue' is the value to replace an existing value at the end of 'path'
// 'path' is the dot-notation path with the key whose value is to be replaced at the end
// (can include wildcard character, '*')
// 'subkeys' are key:value pairs of key:values that must match for the key
func JsonUpdateValsForPath(jsonVal []byte, newKeyValue interface{}, path string, subkeys ...string) ([]byte, error) {
m, err := NewMapJson(jsonVal)
if err != nil {
return nil, err
}
_, err = m.UpdateValuesForPath(newKeyValue, path, subkeys...)
if err != nil {
return nil, err
}
return m.Json()
}
// Wrap NewMap for JSON and return as JSON
// 'jsonVal' is an JSON value
// 'keypairs' are "oldKey:newKey" values that conform to 'keypairs' in (Map)NewMap.
func JsonNewJson(jsonVal []byte, keypairs ...string) ([]byte, error) {
m, err := NewMapJson(jsonVal)
if err != nil {
return nil, err
}
n, err := m.NewMap(keypairs...)
if err != nil {
return nil, err
}
return n.Json()
}
// Wrap NewMap for JSON and return as XML
// 'jsonVal' is an JSON value
// 'keypairs' are "oldKey:newKey" values that conform to 'keypairs' in (Map)NewMap.
func JsonNewXml(jsonVal []byte, keypairs ...string) ([]byte, error) {
m, err := NewMapJson(jsonVal)
if err != nil {
return nil, err
}
n, err := m.NewMap(keypairs...)
if err != nil {
return nil, err
}
return n.Xml()
}
// Wrap LeafNodes for JSON.
// 'jsonVal' is an JSON value
func JsonLeafNodes(jsonVal []byte) ([]LeafNode, error) {
m, err := NewMapJson(jsonVal)
if err != nil {
return nil, err
}
return m.LeafNodes(), nil
}
// Wrap LeafValues for JSON.
// 'jsonVal' is an JSON value
func JsonLeafValues(jsonVal []byte) ([]interface{}, error) {
m, err := NewMapJson(jsonVal)
if err != nil {
return nil, err
}
return m.LeafValues(), nil
}
// Wrap LeafPath for JSON.
// 'xmlVal' is an JSON value
func JsonLeafPath(jsonVal []byte) ([]string, error) {
m, err := NewMapJson(jsonVal)
if err != nil {
return nil, err
}
return m.LeafPaths(), nil
}

View File

@ -0,0 +1,67 @@
// thanks to Chris Malek (chris.r.malek@gmail.com) for suggestion to handle JSON list docs.
package j2x
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
)
func TestJsonToXml_1(t *testing.T) {
// mimic a io.Reader
// Body := bytes.NewReader([]byte(`{"some-null-value":"", "a-non-null-value":"bar"}`))
Body := bytes.NewReader([]byte(`[{"some-null-value":"", "a-non-null-value":"bar"}]`))
//body, err := ioutil.ReadAll(req.Body)
body, err := ioutil.ReadAll(Body)
if err != nil {
t.Fatal(err)
}
fmt.Println(string(body))
//if err != nil {
// t.Fatal(err)
//}
var xmloutput []byte
//xmloutput, err = j2x.JsonToXml(body)
xmloutput, err = JsonToXml(body)
//log.Println(string(xmloutput))
if err != nil {
t.Fatal(err)
// log.Println(err)
// http.Error(rw, "Could not convert to xml", 400)
}
fmt.Println("xmloutput:", string(xmloutput))
}
func TestJsonToXml_2(t *testing.T) {
// mimic a io.Reader
Body := bytes.NewReader([]byte(`{"somekey":[{"value":"1st"},{"value":"2nd"}]}`))
//body, err := ioutil.ReadAll(req.Body)
body, err := ioutil.ReadAll(Body)
if err != nil {
t.Fatal(err)
}
fmt.Println(string(body))
//if err != nil {
// t.Fatal(err)
//}
var xmloutput []byte
//xmloutput, err = j2x.JsonToXml(body)
xmloutput, err = JsonToXml(body)
//log.Println(string(xmloutput))
if err != nil {
t.Fatal(err)
// log.Println(err)
// http.Error(rw, "Could not convert to xml", 400)
}
fmt.Println("xmloutput:", string(xmloutput))
}

View File

@ -0,0 +1,29 @@
package mxj
import (
"fmt"
"testing"
)
var jjdata = []byte(`{ "key1":"string", "key2":34, "key3":true, "key4":"unsafe: <>&", "key5":null }`)
func TestJ2XHeader(t *testing.T) {
fmt.Println("\n---------------- j2x_test .go ...")
}
func TestJ2X(t *testing.T) {
m, err := NewMapJson(jjdata)
if err != nil {
t.Fatal("NewMapJson, err:", err)
}
x, err := m.Xml()
if err != nil {
t.Fatal("m.Xml(), err:", err)
}
fmt.Println("j2x, jdata:", string(jjdata))
fmt.Println("j2x, m :", m)
fmt.Println("j2x, xml :", string(x))
}

View File

@ -0,0 +1,323 @@
// Copyright 2012-2014 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
package mxj
import (
"bytes"
"encoding/json"
"fmt"
"io"
"time"
)
// ------------------------------ write JSON -----------------------
// Just a wrapper on json.Marshal.
// If option safeEncoding is'true' then safe encoding of '<', '>' and '&'
// is preserved. (see encoding/json#Marshal, encoding/json#Encode)
func (mv Map) Json(safeEncoding ...bool) ([]byte, error) {
var s bool
if len(safeEncoding) == 1 {
s = safeEncoding[0]
}
b, err := json.Marshal(mv)
if !s {
b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1)
b = bytes.Replace(b, []byte("\\u003e"), []byte(">"), -1)
b = bytes.Replace(b, []byte("\\u0026"), []byte("&"), -1)
}
return b, err
}
// Just a wrapper on json.MarshalIndent.
// If option safeEncoding is'true' then safe encoding of '<' , '>' and '&'
// is preserved. (see encoding/json#Marshal, encoding/json#Encode)
func (mv Map) JsonIndent(prefix, indent string, safeEncoding ...bool) ([]byte, error) {
var s bool
if len(safeEncoding) == 1 {
s = safeEncoding[0]
}
b, err := json.MarshalIndent(mv, prefix, indent)
if !s {
b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1)
b = bytes.Replace(b, []byte("\\u003e"), []byte(">"), -1)
b = bytes.Replace(b, []byte("\\u0026"), []byte("&"), -1)
}
return b, err
}
// The following implementation is provided for symmetry with NewMapJsonReader[Raw]
// The names will also provide a key for the number of return arguments.
// Writes the Map as JSON on the Writer.
// If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved.
func (mv Map) JsonWriter(jsonWriter io.Writer, safeEncoding ...bool) error {
b, err := mv.Json(safeEncoding...)
if err != nil {
return err
}
_, err = jsonWriter.Write(b)
return err
}
// Writes the Map as JSON on the Writer. []byte is the raw JSON that was written.
// If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved.
func (mv Map) JsonWriterRaw(jsonWriter io.Writer, safeEncoding ...bool) ([]byte, error) {
b, err := mv.Json(safeEncoding...)
if err != nil {
return b, err
}
_, err = jsonWriter.Write(b)
return b, err
}
// Writes the Map as pretty JSON on the Writer.
// If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved.
func (mv Map) JsonIndentWriter(jsonWriter io.Writer, prefix, indent string, safeEncoding ...bool) error {
b, err := mv.JsonIndent(prefix, indent, safeEncoding...)
if err != nil {
return err
}
_, err = jsonWriter.Write(b)
return err
}
// Writes the Map as pretty JSON on the Writer. []byte is the raw JSON that was written.
// If 'safeEncoding' is 'true', then "safe" encoding of '<', '>' and '&' is preserved.
func (mv Map) JsonIndentWriterRaw(jsonWriter io.Writer, prefix, indent string, safeEncoding ...bool) ([]byte, error) {
b, err := mv.JsonIndent(prefix, indent, safeEncoding...)
if err != nil {
return b, err
}
_, err = jsonWriter.Write(b)
return b, err
}
// --------------------------- read JSON -----------------------------
// Decode numericvalues as json.Number type Map values - see encoding/json#Number.
// NOTE: this is for decoding JSON into a Map with NewMapJson(), NewMapJsonReader(),
// etc.; it does not affect NewMapXml(), etc. The XML encoders mv.Xml() and mv.XmlIndent()
// do recognize json.Number types; a JSON object can be decoded to a Map with json.Number
// value types and the resulting Map can be correctly encoded into a XML object.
var JsonUseNumber bool
// Just a wrapper on json.Unmarshal
// Converting JSON to XML is a simple as:
// ...
// mapVal, merr := mxj.NewMapJson(jsonVal)
// if merr != nil {
// // handle error
// }
// xmlVal, xerr := mapVal.Xml()
// if xerr != nil {
// // handle error
// }
// NOTE: as a special case, passing a list, e.g., [{"some-null-value":"", "a-non-null-value":"bar"}],
// will be interpreted as having the root key 'object' prepended - {"object":[ ... ]} - to unmarshal to a Map.
// See mxj/j2x/j2x_test.go.
func NewMapJson(jsonVal []byte) (Map, error) {
// empty or nil begets empty
if len(jsonVal) == 0 {
m := make(map[string]interface{}, 0)
return m, nil
}
// handle a goofy case ...
if jsonVal[0] == '[' {
jsonVal = []byte(`{"object":` + string(jsonVal) + `}`)
}
m := make(map[string]interface{})
// err := json.Unmarshal(jsonVal, &m)
buf := bytes.NewReader(jsonVal)
dec := json.NewDecoder(buf)
if JsonUseNumber {
dec.UseNumber()
}
err := dec.Decode(&m)
return m, err
}
// Retrieve a Map value from an io.Reader.
// NOTE: The raw JSON off the reader is buffered to []byte using a ByteReader. If the io.Reader is an
// os.File, there may be significant performance impact. If the io.Reader is wrapping a []byte
// value in-memory, however, such as http.Request.Body you CAN use it to efficiently unmarshal
// a JSON object.
func NewMapJsonReader(jsonReader io.Reader) (Map, error) {
jb, err := getJson(jsonReader)
if err != nil || len(*jb) == 0 {
return nil, err
}
// Unmarshal the 'presumed' JSON string
return NewMapJson(*jb)
}
// Retrieve a Map value and raw JSON - []byte - from an io.Reader.
// NOTE: The raw JSON off the reader is buffered to []byte using a ByteReader. If the io.Reader is an
// os.File, there may be significant performance impact. If the io.Reader is wrapping a []byte
// value in-memory, however, such as http.Request.Body you CAN use it to efficiently unmarshal
// a JSON object and retrieve the raw JSON in a single call.
func NewMapJsonReaderRaw(jsonReader io.Reader) (Map, []byte, error) {
jb, err := getJson(jsonReader)
if err != nil || len(*jb) == 0 {
return nil, *jb, err
}
// Unmarshal the 'presumed' JSON string
m, merr := NewMapJson(*jb)
return m, *jb, merr
}
// Pull the next JSON string off the stream: just read from first '{' to its closing '}'.
// Returning a pointer to the slice saves 16 bytes - maybe unnecessary, but internal to package.
func getJson(rdr io.Reader) (*[]byte, error) {
bval := make([]byte, 1)
jb := make([]byte, 0)
var inQuote, inJson bool
var parenCnt int
var previous byte
// scan the input for a matched set of {...}
// json.Unmarshal will handle syntax checking.
for {
_, err := rdr.Read(bval)
if err != nil {
if err == io.EOF && inJson && parenCnt > 0 {
return &jb, fmt.Errorf("no closing } for JSON string: %s", string(jb))
}
return &jb, err
}
switch bval[0] {
case '{':
if !inQuote {
parenCnt++
inJson = true
}
case '}':
if !inQuote {
parenCnt--
}
if parenCnt < 0 {
return nil, fmt.Errorf("closing } without opening {: %s", string(jb))
}
case '"':
if inQuote {
if previous == '\\' {
break
}
inQuote = false
} else {
inQuote = true
}
case '\n', '\r', '\t', ' ':
if !inQuote {
continue
}
}
if inJson {
jb = append(jb, bval[0])
if parenCnt == 0 {
break
}
}
previous = bval[0]
}
return &jb, nil
}
// ------------------------------- JSON Reader handler via Map values -----------------------
// Default poll delay to keep Handler from spinning on an open stream
// like sitting on os.Stdin waiting for imput.
var jhandlerPollInterval = time.Duration(1e6)
// While unnecessary, we make HandleJsonReader() have the same signature as HandleXmlReader().
// This avoids treating one or other as a special case and discussing the underlying stdlib logic.
// Bulk process JSON using handlers that process a Map value.
// 'rdr' is an io.Reader for the JSON (stream).
// 'mapHandler' is the Map processing handler. Return of 'false' stops io.Reader processing.
// 'errHandler' is the error processor. Return of 'false' stops io.Reader processing and returns the error.
// Note: mapHandler() and errHandler() calls are blocking, so reading and processing of messages is serialized.
// This means that you can stop reading the file on error or after processing a particular message.
// To have reading and handling run concurrently, pass argument to a go routine in handler and return 'true'.
func HandleJsonReader(jsonReader io.Reader, mapHandler func(Map) bool, errHandler func(error) bool) error {
var n int
for {
m, merr := NewMapJsonReader(jsonReader)
n++
// handle error condition with errhandler
if merr != nil && merr != io.EOF {
merr = fmt.Errorf("[jsonReader: %d] %s", n, merr.Error())
if ok := errHandler(merr); !ok {
// caused reader termination
return merr
}
continue
}
// pass to maphandler
if len(m) != 0 {
if ok := mapHandler(m); !ok {
break
}
} else if merr != io.EOF {
<-time.After(jhandlerPollInterval)
}
if merr == io.EOF {
break
}
}
return nil
}
// Bulk process JSON using handlers that process a Map value and the raw JSON.
// 'rdr' is an io.Reader for the JSON (stream).
// 'mapHandler' is the Map and raw JSON - []byte - processor. Return of 'false' stops io.Reader processing.
// 'errHandler' is the error and raw JSON processor. Return of 'false' stops io.Reader processing and returns the error.
// Note: mapHandler() and errHandler() calls are blocking, so reading and processing of messages is serialized.
// This means that you can stop reading the file on error or after processing a particular message.
// To have reading and handling run concurrently, pass argument(s) to a go routine in handler and return 'true'.
func HandleJsonReaderRaw(jsonReader io.Reader, mapHandler func(Map, []byte) bool, errHandler func(error, []byte) bool) error {
var n int
for {
m, raw, merr := NewMapJsonReaderRaw(jsonReader)
n++
// handle error condition with errhandler
if merr != nil && merr != io.EOF {
merr = fmt.Errorf("[jsonReader: %d] %s", n, merr.Error())
if ok := errHandler(merr, raw); !ok {
// caused reader termination
return merr
}
continue
}
// pass to maphandler
if len(m) != 0 {
if ok := mapHandler(m, raw); !ok {
break
}
} else if merr != io.EOF {
<-time.After(jhandlerPollInterval)
}
if merr == io.EOF {
break
}
}
return nil
}

View File

@ -0,0 +1,137 @@
package mxj
import (
"bytes"
"fmt"
"io"
"testing"
)
var jdata = []byte(`{ "key1":"string", "key2":34, "key3":true, "key4":"unsafe: <>&", "key5":null }`)
var jdata2 = []byte(`{ "key1":"string", "key2":34, "key3":true, "key4":"unsafe: <>&" },
{ "key":"value in new JSON string" }`)
func TestJsonHeader(t *testing.T) {
fmt.Println("\n---------------- json_test.go ...")
}
func TestNewMapJson(t *testing.T) {
m, merr := NewMapJson(jdata)
if merr != nil {
t.Fatal("NewMapJson, merr:", merr.Error())
}
fmt.Println("NewMapJson, jdata:", string(jdata))
fmt.Printf("NewMapJson, m : %#v\n", m)
}
func TestNewMapJsonNumber(t *testing.T) {
JsonUseNumber = true
m, merr := NewMapJson(jdata)
if merr != nil {
t.Fatal("NewMapJson, merr:", merr.Error())
}
fmt.Println("NewMapJson, jdata:", string(jdata))
fmt.Printf("NewMapJson, m : %#v\n", m)
JsonUseNumber = false
}
func TestNewMapJsonError(t *testing.T) {
m, merr := NewMapJson(jdata[:len(jdata)-2])
if merr == nil {
t.Fatal("NewMapJsonError, m:", m)
}
fmt.Println("NewMapJsonError, jdata :", string(jdata[:len(jdata)-2]))
fmt.Println("NewMapJsonError, merror:", merr.Error())
newData := []byte(`{ "this":"is", "in":error }`)
m, merr = NewMapJson(newData)
if merr == nil {
t.Fatal("NewMapJsonError, m:", m)
}
fmt.Println("NewMapJsonError, newData :", string(newData))
fmt.Println("NewMapJsonError, merror :", merr.Error())
}
func TestNewMapJsonReader(t *testing.T) {
rdr := bytes.NewBuffer(jdata2)
for {
m, jb, merr := NewMapJsonReaderRaw(rdr)
if merr != nil && merr != io.EOF {
t.Fatal("NewMapJsonReader, merr:", merr.Error())
}
if merr == io.EOF {
break
}
fmt.Println("NewMapJsonReader, jb:", string(jb))
fmt.Printf("NewMapJsonReader, m : %#v\n", m)
}
}
func TestNewMapJsonReaderNumber(t *testing.T) {
JsonUseNumber = true
rdr := bytes.NewBuffer(jdata2)
for {
m, jb, merr := NewMapJsonReaderRaw(rdr)
if merr != nil && merr != io.EOF {
t.Fatal("NewMapJsonReader, merr:", merr.Error())
}
if merr == io.EOF {
break
}
fmt.Println("NewMapJsonReader, jb:", string(jb))
fmt.Printf("NewMapJsonReader, m : %#v\n", m)
}
JsonUseNumber = false
}
func TestJson(t *testing.T) {
m, _ := NewMapJson(jdata)
j, jerr := m.Json()
if jerr != nil {
t.Fatal("Json, jerr:", jerr.Error())
}
fmt.Println("Json, jdata:", string(jdata))
fmt.Println("Json, j :", string(j))
j, _ = m.Json(true)
fmt.Println("Json, j safe:", string(j))
}
func TestJsonWriter(t *testing.T) {
mv := Map(map[string]interface{}{"this": "is a", "float": 3.14159, "and": "a", "bool": true})
w := new(bytes.Buffer)
raw, err := mv.JsonWriterRaw(w)
if err != nil {
t.Fatal("err:", err.Error())
}
b := make([]byte, w.Len())
_, err = w.Read(b)
if err != nil {
t.Fatal("err:", err.Error())
}
fmt.Println("JsonWriter, raw:", string(raw))
fmt.Println("JsonWriter, b :", string(b))
}

View File

@ -0,0 +1,60 @@
// keystolower_test.go
package mxj
import (
"fmt"
"testing"
)
var tolowerdata1 = []byte(`
<doc>
<element attr="attrValue">value</element>
</doc>
`)
var tolowerdata2 = []byte(`
<DOC>
<Element attR="attrValue">value</Element>
</DOC>
`)
func TestToLower(t *testing.T) {
fmt.Println("\n-------------- keystolower_test.go")
fmt.Println("\nTestToLower ...")
CoerceKeysToLower()
defer CoerceKeysToLower()
m1, err := NewMapXml(tolowerdata1)
if err != nil {
t.Fatal(err)
}
m2, err := NewMapXml(tolowerdata2)
if err != nil {
t.Fatal(err)
}
v1, err := m1.ValuesForPath("doc.element")
if err != nil {
t.Fatal(err)
}
v2, err := m2.ValuesForPath("doc.element")
if err != nil {
t.Fatal(err)
}
if len(v1) != len(v2) {
t.Fatal(err, len(v1), len(v2))
}
m := v1[0].(map[string]interface{})
mm := v2[0].(map[string]interface{})
for k, v := range m {
if vv, ok := mm[k]; !ok {
t.Fatal("key:", k, "not in mm")
} else if v.(string) != vv.(string) {
t.Fatal(v.(string), "not in v2:", vv.(string))
}
}
}

View File

@ -0,0 +1,653 @@
// Copyright 2012-2014 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
// keyvalues.go: Extract values from an arbitrary XML doc. Tag path can include wildcard characters.
package mxj
import (
"errors"
"fmt"
"strconv"
"strings"
)
// ----------------------------- get everything FOR a single key -------------------------
const (
minArraySize = 32
)
var defaultArraySize int = minArraySize
// Adjust the buffers for expected number of values to return from ValuesForKey() and ValuesForPath().
// This can have the effect of significantly reducing memory allocation-copy functions for large data sets.
// Returns the initial buffer size.
func SetArraySize(size int) int {
if size > minArraySize {
defaultArraySize = size
} else {
defaultArraySize = minArraySize
}
return defaultArraySize
}
// Return all values in Map, 'mv', associated with a 'key'. If len(returned_values) == 0, then no match.
// On error, the returned array is 'nil'. NOTE: 'key' can be wildcard, "*".
// 'subkeys' (optional) are "key:val[:type]" strings representing attributes or elements in a list.
// - By default 'val' is of type string. "key:val:bool" and "key:val:float" to coerce them.
// - For attributes prefix the label with a hyphen, '-', e.g., "-seq:3".
// - If the 'key' refers to a list, then "key:value" could select a list member of the list.
// - The subkey can be wildcarded - "key:*" - to require that it's there with some value.
// - If a subkey is preceeded with the '!' character, the key:value[:type] entry is treated as an
// exclusion critera - e.g., "!author:William T. Gaddis".
// - If val contains ":" symbol, use SetFieldSeparator to a unused symbol, perhaps "|".
func (mv Map) ValuesForKey(key string, subkeys ...string) ([]interface{}, error) {
m := map[string]interface{}(mv)
var subKeyMap map[string]interface{}
if len(subkeys) > 0 {
var err error
subKeyMap, err = getSubKeyMap(subkeys...)
if err != nil {
return nil, err
}
}
ret := make([]interface{}, 0, defaultArraySize)
var cnt int
hasKey(m, key, &ret, &cnt, subKeyMap)
return ret[:cnt], nil
}
// hasKey - if the map 'key' exists append it to array
// if it doesn't do nothing except scan array and map values
func hasKey(iv interface{}, key string, ret *[]interface{}, cnt *int, subkeys map[string]interface{}) {
// func hasKey(iv interface{}, key string, ret *[]interface{}, subkeys map[string]interface{}) {
switch iv.(type) {
case map[string]interface{}:
vv := iv.(map[string]interface{})
// see if the current value is of interest
if v, ok := vv[key]; ok {
switch v.(type) {
case map[string]interface{}:
if hasSubKeys(v, subkeys) {
*ret = append(*ret, v)
*cnt++
}
case []interface{}:
for _, av := range v.([]interface{}) {
if hasSubKeys(av, subkeys) {
*ret = append(*ret, av)
*cnt++
}
}
default:
if len(subkeys) == 0 {
*ret = append(*ret, v)
*cnt++
}
}
}
// wildcard case
if key == "*" {
for _, v := range vv {
switch v.(type) {
case map[string]interface{}:
if hasSubKeys(v, subkeys) {
*ret = append(*ret, v)
*cnt++
}
case []interface{}:
for _, av := range v.([]interface{}) {
if hasSubKeys(av, subkeys) {
*ret = append(*ret, av)
*cnt++
}
}
default:
if len(subkeys) == 0 {
*ret = append(*ret, v)
*cnt++
}
}
}
}
// scan the rest
for _, v := range vv {
hasKey(v, key, ret, cnt, subkeys)
}
case []interface{}:
for _, v := range iv.([]interface{}) {
hasKey(v, key, ret, cnt, subkeys)
}
}
}
// ----------------------- get everything for a node in the Map ---------------------------
// Allow indexed arrays in "path" specification. (Request from Abhijit Kadam - abhijitk100@gmail.com.)
// 2014.04.28 - implementation note.
// Implemented as a wrapper of (old)ValuesForPath() because we need look-ahead logic to handle expansion
// of wildcards and unindexed arrays. Embedding such logic into valuesForKeyPath() would have made the
// code much more complicated; this wrapper is straightforward, easy to debug, and doesn't add significant overhead.
// Retrieve all values for a path from the Map. If len(returned_values) == 0, then no match.
// On error, the returned array is 'nil'.
// 'path' is a dot-separated path of key values.
// - If a node in the path is '*', then everything beyond is walked.
// - 'path' can contain indexed array references, such as, "*.data[1]" and "msgs[2].data[0].field" -
// even "*[2].*[0].field".
// 'subkeys' (optional) are "key:val[:type]" strings representing attributes or elements in a list.
// - By default 'val' is of type string. "key:val:bool" and "key:val:float" to coerce them.
// - For attributes prefix the label with a hyphen, '-', e.g., "-seq:3".
// - If the 'path' refers to a list, then "tag:value" would return member of the list.
// - The subkey can be wildcarded - "key:*" - to require that it's there with some value.
// - If a subkey is preceeded with the '!' character, the key:value[:type] entry is treated as an
// exclusion critera - e.g., "!author:William T. Gaddis".
// - If val contains ":" symbol, use SetFieldSeparator to a unused symbol, perhaps "|".
func (mv Map) ValuesForPath(path string, subkeys ...string) ([]interface{}, error) {
// If there are no array indexes in path, use legacy ValuesForPath() logic.
if strings.Index(path, "[") < 0 {
return mv.oldValuesForPath(path, subkeys...)
}
var subKeyMap map[string]interface{}
if len(subkeys) > 0 {
var err error
subKeyMap, err = getSubKeyMap(subkeys...)
if err != nil {
return nil, err
}
}
keys, kerr := parsePath(path)
if kerr != nil {
return nil, kerr
}
vals, verr := valuesForArray(keys, mv)
if verr != nil {
return nil, verr // Vals may be nil, but return empty array.
}
// Need to handle subkeys ... only return members of vals that satisfy conditions.
retvals := make([]interface{}, 0)
for _, v := range vals {
if hasSubKeys(v, subKeyMap) {
retvals = append(retvals, v)
}
}
return retvals, nil
}
func valuesForArray(keys []*key, m Map) ([]interface{}, error) {
var tmppath string
var haveFirst bool
var vals []interface{}
var verr error
lastkey := len(keys) - 1
for i := 0; i <= lastkey; i++ {
if !haveFirst {
tmppath = keys[i].name
haveFirst = true
} else {
tmppath += "." + keys[i].name
}
// Look-ahead: explode wildcards and unindexed arrays.
// Need to handle un-indexed list recursively:
// e.g., path is "stuff.data[0]" rather than "stuff[0].data[0]".
// Need to treat it as "stuff[0].data[0]", "stuff[1].data[0]", ...
if !keys[i].isArray && i < lastkey && keys[i+1].isArray {
// Can't pass subkeys because we may not be at literal end of path.
vv, vverr := m.oldValuesForPath(tmppath)
if vverr != nil {
return nil, vverr
}
for _, v := range vv {
// See if we can walk the value.
am, ok := v.(map[string]interface{})
if !ok {
continue
}
// Work the backend.
nvals, nvalserr := valuesForArray(keys[i+1:], Map(am))
if nvalserr != nil {
return nil, nvalserr
}
vals = append(vals, nvals...)
}
break // have recursed the whole path - return
}
if keys[i].isArray || i == lastkey {
// Don't pass subkeys because may not be at literal end of path.
vals, verr = m.oldValuesForPath(tmppath)
} else {
continue
}
if verr != nil {
return nil, verr
}
if i == lastkey && !keys[i].isArray {
break
}
// Now we're looking at an array - supposedly.
// Is index in range of vals?
if len(vals) <= keys[i].position {
vals = nil
break
}
// Return the array member of interest, if at end of path.
if i == lastkey {
vals = vals[keys[i].position:(keys[i].position + 1)]
break
}
// Extract the array member of interest.
am := vals[keys[i].position:(keys[i].position + 1)]
// must be a map[string]interface{} value so we can keep walking the path
amm, ok := am[0].(map[string]interface{})
if !ok {
vals = nil
break
}
m = Map(amm)
haveFirst = false
}
return vals, nil
}
type key struct {
name string
isArray bool
position int
}
func parsePath(s string) ([]*key, error) {
keys := strings.Split(s, ".")
ret := make([]*key, 0)
for i := 0; i < len(keys); i++ {
if keys[i] == "" {
continue
}
newkey := new(key)
if strings.Index(keys[i], "[") < 0 {
newkey.name = keys[i]
ret = append(ret, newkey)
continue
}
p := strings.Split(keys[i], "[")
newkey.name = p[0]
p = strings.Split(p[1], "]")
if p[0] == "" { // no right bracket
return nil, fmt.Errorf("no right bracket on key index: %s", keys[i])
}
// convert p[0] to a int value
pos, nerr := strconv.ParseInt(p[0], 10, 32)
if nerr != nil {
return nil, fmt.Errorf("cannot convert index to int value: %s", p[0])
}
newkey.position = int(pos)
newkey.isArray = true
ret = append(ret, newkey)
}
return ret, nil
}
// legacy ValuesForPath() - now wrapped to handle special case of indexed arrays in 'path'.
func (mv Map) oldValuesForPath(path string, subkeys ...string) ([]interface{}, error) {
m := map[string]interface{}(mv)
var subKeyMap map[string]interface{}
if len(subkeys) > 0 {
var err error
subKeyMap, err = getSubKeyMap(subkeys...)
if err != nil {
return nil, err
}
}
keys := strings.Split(path, ".")
if keys[len(keys)-1] == "" {
keys = keys[:len(keys)-1]
}
ivals := make([]interface{}, 0, defaultArraySize)
var cnt int
valuesForKeyPath(&ivals, &cnt, m, keys, subKeyMap)
return ivals[:cnt], nil
}
func valuesForKeyPath(ret *[]interface{}, cnt *int, m interface{}, keys []string, subkeys map[string]interface{}) {
lenKeys := len(keys)
// load 'm' values into 'ret'
// expand any lists
if lenKeys == 0 {
switch m.(type) {
case map[string]interface{}:
if subkeys != nil {
if ok := hasSubKeys(m, subkeys); !ok {
return
}
}
*ret = append(*ret, m)
*cnt++
case []interface{}:
for i, v := range m.([]interface{}) {
if subkeys != nil {
if ok := hasSubKeys(v, subkeys); !ok {
continue // only load list members with subkeys
}
}
*ret = append(*ret, (m.([]interface{}))[i])
*cnt++
}
default:
if subkeys != nil {
return // must be map[string]interface{} if there are subkeys
}
*ret = append(*ret, m)
*cnt++
}
return
}
// key of interest
key := keys[0]
switch key {
case "*": // wildcard - scan all values
switch m.(type) {
case map[string]interface{}:
for _, v := range m.(map[string]interface{}) {
// valuesForKeyPath(ret, v, keys[1:], subkeys)
valuesForKeyPath(ret, cnt, v, keys[1:], subkeys)
}
case []interface{}:
for _, v := range m.([]interface{}) {
switch v.(type) {
// flatten out a list of maps - keys are processed
case map[string]interface{}:
for _, vv := range v.(map[string]interface{}) {
// valuesForKeyPath(ret, vv, keys[1:], subkeys)
valuesForKeyPath(ret, cnt, vv, keys[1:], subkeys)
}
default:
// valuesForKeyPath(ret, v, keys[1:], subkeys)
valuesForKeyPath(ret, cnt, v, keys[1:], subkeys)
}
}
}
default: // key - must be map[string]interface{}
switch m.(type) {
case map[string]interface{}:
if v, ok := m.(map[string]interface{})[key]; ok {
// valuesForKeyPath(ret, v, keys[1:], subkeys)
valuesForKeyPath(ret, cnt, v, keys[1:], subkeys)
}
case []interface{}: // may be buried in list
for _, v := range m.([]interface{}) {
switch v.(type) {
case map[string]interface{}:
if vv, ok := v.(map[string]interface{})[key]; ok {
// valuesForKeyPath(ret, vv, keys[1:], subkeys)
valuesForKeyPath(ret, cnt, vv, keys[1:], subkeys)
}
}
}
}
}
}
// hasSubKeys() - interface{} equality works for string, float64, bool
// 'v' must be a map[string]interface{} value to have subkeys
// 'a' can have k:v pairs with v.(string) == "*", which is treated like a wildcard.
func hasSubKeys(v interface{}, subkeys map[string]interface{}) bool {
if len(subkeys) == 0 {
return true
}
switch v.(type) {
case map[string]interface{}:
// do all subKey name:value pairs match?
mv := v.(map[string]interface{})
for skey, sval := range subkeys {
isNotKey := false
if skey[:1] == "!" { // a NOT-key
skey = skey[1:]
isNotKey = true
}
vv, ok := mv[skey]
if !ok { // key doesn't exist
if isNotKey { // key not there, but that's what we want
if kv, ok := sval.(string); ok && kv == "*" {
continue
}
}
return false
}
// wildcard check
if kv, ok := sval.(string); ok && kv == "*" {
if isNotKey { // key is there, and we don't want it
return false
}
continue
}
switch sval.(type) {
case string:
if s, ok := vv.(string); ok && s == sval.(string) {
if isNotKey {
return false
}
continue
}
case bool:
if b, ok := vv.(bool); ok && b == sval.(bool) {
if isNotKey {
return false
}
continue
}
case float64:
if f, ok := vv.(float64); ok && f == sval.(float64) {
if isNotKey {
return false
}
continue
}
}
// key there but didn't match subkey value
if isNotKey { // that's what we want
continue
}
return false
}
// all subkeys matched
return true
}
// not a map[string]interface{} value, can't have subkeys
return false
}
// Generate map of key:value entries as map[string]string.
// 'kv' arguments are "name:value" pairs: attribute keys are designated with prepended hyphen, '-'.
// If len(kv) == 0, the return is (nil, nil).
func getSubKeyMap(kv ...string) (map[string]interface{}, error) {
if len(kv) == 0 {
return nil, nil
}
m := make(map[string]interface{}, 0)
for _, v := range kv {
vv := strings.Split(v, fieldSep)
switch len(vv) {
case 2:
m[vv[0]] = interface{}(vv[1])
case 3:
switch vv[2] {
case "string", "char", "text":
m[vv[0]] = interface{}(vv[1])
case "bool", "boolean":
// ParseBool treats "1"==true & "0"==false
b, err := strconv.ParseBool(vv[1])
if err != nil {
return nil, fmt.Errorf("can't convert subkey value to bool: %s", vv[1])
}
m[vv[0]] = interface{}(b)
case "float", "float64", "num", "number", "numeric":
f, err := strconv.ParseFloat(vv[1], 64)
if err != nil {
return nil, fmt.Errorf("can't convert subkey value to float: %s", vv[1])
}
m[vv[0]] = interface{}(f)
default:
return nil, fmt.Errorf("unknown subkey conversion spec: %s", v)
}
default:
return nil, fmt.Errorf("unknown subkey spec: %s", v)
}
}
return m, nil
}
// ------------------------------- END of valuesFor ... ----------------------------
// ----------------------- locate where a key value is in the tree -------------------
//----------------------------- find all paths to a key --------------------------------
// Get all paths through Map, 'mv', (in dot-notation) that terminate with the specified key.
// Results can be used with ValuesForPath.
func (mv Map) PathsForKey(key string) []string {
m := map[string]interface{}(mv)
breadbasket := make(map[string]bool, 0)
breadcrumbs := ""
hasKeyPath(breadcrumbs, m, key, breadbasket)
if len(breadbasket) == 0 {
return nil
}
// unpack map keys to return
res := make([]string, len(breadbasket))
var i int
for k := range breadbasket {
res[i] = k
i++
}
return res
}
// Extract the shortest path from all possible paths - from PathsForKey() - in Map, 'mv'..
// Paths are strings using dot-notation.
func (mv Map) PathForKeyShortest(key string) string {
paths := mv.PathsForKey(key)
lp := len(paths)
if lp == 0 {
return ""
}
if lp == 1 {
return paths[0]
}
shortest := paths[0]
shortestLen := len(strings.Split(shortest, "."))
for i := 1; i < len(paths); i++ {
vlen := len(strings.Split(paths[i], "."))
if vlen < shortestLen {
shortest = paths[i]
shortestLen = vlen
}
}
return shortest
}
// hasKeyPath - if the map 'key' exists append it to KeyPath.path and increment KeyPath.depth
// This is really just a breadcrumber that saves all trails that hit the prescribed 'key'.
func hasKeyPath(crumbs string, iv interface{}, key string, basket map[string]bool) {
switch iv.(type) {
case map[string]interface{}:
vv := iv.(map[string]interface{})
if _, ok := vv[key]; ok {
// create a new breadcrumb, intialized with the one we have
var nbc string
if crumbs == "" {
nbc = key
} else {
nbc = crumbs + "." + key
}
basket[nbc] = true
}
// walk on down the path, key could occur again at deeper node
for k, v := range vv {
// create a new breadcrumb, intialized with the one we have
var nbc string
if crumbs == "" {
nbc = k
} else {
nbc = crumbs + "." + k
}
hasKeyPath(nbc, v, key, basket)
}
case []interface{}:
// crumb-trail doesn't change, pass it on
for _, v := range iv.([]interface{}) {
hasKeyPath(crumbs, v, key, basket)
}
}
}
// Returns the first found value for the path.
func (mv Map) ValueForPath(path string) (interface{}, error) {
vals, err := mv.ValuesForPath(path)
if err != nil {
return nil, err
}
if len(vals) == 0 {
return nil, errors.New("ValueForPath: path not found")
}
return vals[0], nil
}
// Returns the first found value for the path as a string.
func (mv Map) ValueForPathString(path string) (string, error) {
vals, err := mv.ValuesForPath(path)
if err != nil {
return "", err
}
if len(vals) == 0 {
return "", errors.New("ValueForPath: path not found")
}
val := vals[0]
switch str := val.(type) {
case string:
return str, nil
default:
return "", fmt.Errorf("ValueForPath: unsupported type: %T", str)
}
}
// Returns the first found value for the path as a string.
// If the path is not found then it returns an empty string.
func (mv Map) ValueOrEmptyForPathString(path string) string {
str, _ := mv.ValueForPathString(path)
return str
}

View File

@ -0,0 +1,47 @@
package mxj
import (
"fmt"
"testing"
)
func TestSetSubkeyFieldSeparator(t *testing.T) {
PrependAttrWithHyphen(true)
fmt.Println("----------- TestSetSubkeyFieldSeparator")
data := `
<doc>
<elem attr="1">value 1</elem>
<elem attr="2">value 2</elem>
<elem attr="3">value 3</elem>
</doc>`
m, err := NewMapXml([]byte(data))
if err != nil {
t.Fatal(err)
}
vals, err := m.ValuesForKey("elem", "-attr:2:text")
if err != nil {
t.Fatal(err)
}
if len(vals) != 1 {
t.Fatal(":len(vals);", len(vals), vals)
}
if vals[0].(map[string]interface{})["#text"].(string) != "value 2" {
t.Fatal(":expecting: value 2; got:", vals[0].(map[string]interface{})["#text"])
}
SetFieldSeparator("|")
defer SetFieldSeparator()
vals, err = m.ValuesForKey("elem", "-attr|2|text")
if err != nil {
t.Fatal(err)
}
if len(vals) != 1 {
t.Fatal("|len(vals);", len(vals), vals)
}
if vals[0].(map[string]interface{})["#text"].(string) != "value 2" {
t.Fatal("|expecting: value 2; got:", vals[0].(map[string]interface{})["#text"])
}
}

View File

@ -0,0 +1,446 @@
// keyvalues_test.go - test keyvalues.go methods
package mxj
import (
// "bytes"
"fmt"
// "io"
"testing"
)
func TestKVHeader(t *testing.T) {
fmt.Println("\n---------------- keyvalues_test.go ...")
}
var doc1 = []byte(`
<doc>
<books>
<book seq="1">
<author>William T. Gaddis</author>
<title>The Recognitions</title>
<review>One of the great seminal American novels of the 20th century.</review>
</book>
<book seq="2">
<author>Austin Tappan Wright</author>
<title>Islandia</title>
<review>An example of earlier 20th century American utopian fiction.</review>
</book>
<book seq="3">
<author>John Hawkes</author>
<title>The Beetle Leg</title>
<review>A lyrical novel about the construction of Ft. Peck Dam in Montana.</review>
</book>
<book seq="4">
<author>
<first_name>T.E.</first_name>
<last_name>Porter</last_name>
</author>
<title>King's Day</title>
<review>A magical novella.</review>
</book>
</books>
</doc>
`)
var doc2 = []byte(`
<doc>
<books>
<book seq="1">
<author>William T. Gaddis</author>
<title>The Recognitions</title>
<review>One of the great seminal American novels of the 20th century.</review>
</book>
</books>
<book>Something else.</book>
</doc>
`)
// the basic demo/test case - a small bibliography with mixed element types
func TestPathsForKey(t *testing.T) {
fmt.Println("PathsForKey, doc1 ...")
m, merr := NewMapXml(doc1)
if merr != nil {
t.Fatal("merr:", merr.Error())
}
fmt.Println("PathsForKey, doc1#author")
ss := m.PathsForKey("author")
fmt.Println("... ss:", ss)
fmt.Println("PathsForKey, doc1#books")
ss = m.PathsForKey("books")
fmt.Println("... ss:", ss)
fmt.Println("PathsForKey, doc2 ...")
m, merr = NewMapXml(doc2)
if merr != nil {
t.Fatal("merr:", merr.Error())
}
fmt.Println("PathForKey, doc2#book")
ss = m.PathsForKey("book")
fmt.Println("... ss:", ss)
fmt.Println("PathForKeyShortest, doc2#book")
s := m.PathForKeyShortest("book")
fmt.Println("... s :", s)
}
func TestValuesForKey(t *testing.T) {
fmt.Println("ValuesForKey ...")
m, merr := NewMapXml(doc1)
if merr != nil {
t.Fatal("merr:", merr.Error())
}
fmt.Println("ValuesForKey, doc1#author")
ss, sserr := m.ValuesForKey("author")
if sserr != nil {
t.Fatal("sserr:", sserr.Error())
}
for _, v := range ss {
fmt.Println("... ss.v:", v)
}
fmt.Println("ValuesForKey, doc1#book")
ss, sserr = m.ValuesForKey("book")
if sserr != nil {
t.Fatal("sserr:", sserr.Error())
}
for _, v := range ss {
fmt.Println("... ss.v:", v)
}
fmt.Println("ValuesForKey, doc1#book,-seq:3")
ss, sserr = m.ValuesForKey("book", "-seq:3")
if sserr != nil {
t.Fatal("sserr:", sserr.Error())
}
for _, v := range ss {
fmt.Println("... ss.v:", v)
}
fmt.Println("ValuesForKey, doc1#book, author:William T. Gaddis")
ss, sserr = m.ValuesForKey("book", "author:William T. Gaddis")
if sserr != nil {
t.Fatal("sserr:", sserr.Error())
}
for _, v := range ss {
fmt.Println("... ss.v:", v)
}
fmt.Println("ValuesForKey, doc1#author, -seq:1")
ss, sserr = m.ValuesForKey("author", "-seq:1")
if sserr != nil {
t.Fatal("sserr:", sserr.Error())
}
for _, v := range ss { // should be len(ss) == 0
fmt.Println("... ss.v:", v)
}
}
func TestValuesForPath(t *testing.T) {
fmt.Println("ValuesForPath ...")
m, merr := NewMapXml(doc1)
if merr != nil {
t.Fatal("merr:", merr.Error())
}
fmt.Println("ValuesForPath, doc.books.book.author")
ss, sserr := m.ValuesForPath("doc.books.book.author")
if sserr != nil {
t.Fatal("sserr:", sserr.Error())
}
for _, v := range ss {
fmt.Println("... ss.v:", v)
}
fmt.Println("ValuesForPath, doc.books.book")
ss, sserr = m.ValuesForPath("doc.books.book")
if sserr != nil {
t.Fatal("sserr:", sserr.Error())
}
for _, v := range ss {
fmt.Println("... ss.v:", v)
}
fmt.Println("ValuesForPath, doc.books.book -seq=3")
ss, sserr = m.ValuesForPath("doc.books.book", "-seq:3")
if sserr != nil {
t.Fatal("sserr:", sserr.Error())
}
for _, v := range ss {
fmt.Println("... ss.v:", v)
}
fmt.Println("ValuesForPath, doc.books.* -seq=3")
ss, sserr = m.ValuesForPath("doc.books.*", "-seq:3")
if sserr != nil {
t.Fatal("sserr:", sserr.Error())
}
for _, v := range ss {
fmt.Println("... ss.v:", v)
}
fmt.Println("ValuesForPath, doc.*.* -seq=3")
ss, sserr = m.ValuesForPath("doc.*.*", "-seq:3")
if sserr != nil {
t.Fatal("sserr:", sserr.Error())
}
for _, v := range ss {
fmt.Println("... ss.v:", v)
}
}
func TestValuesForNotKey(t *testing.T) {
fmt.Println("ValuesForNotKey ...")
m, merr := NewMapXml(doc1)
if merr != nil {
t.Fatal("merr:", merr.Error())
}
fmt.Println("ValuesForPath, doc.books.book !author:William T. Gaddis")
ss, sserr := m.ValuesForPath("doc.books.book", "!author:William T. Gaddis")
if sserr != nil {
t.Fatal("sserr:", sserr.Error())
}
for _, v := range ss {
fmt.Println("... ss.v:", v)
}
fmt.Println("ValuesForPath, doc.books.book !author:*")
ss, sserr = m.ValuesForPath("doc.books.book", "!author:*")
if sserr != nil {
t.Fatal("sserr:", sserr.Error())
}
for _, v := range ss { // expect len(ss) == 0
fmt.Println("... ss.v:", v)
}
fmt.Println("ValuesForPath, doc.books.book !unknown:*")
ss, sserr = m.ValuesForPath("doc.books.book", "!unknown:*")
if sserr != nil {
t.Fatal("sserr:", sserr.Error())
}
for _, v := range ss {
fmt.Println("... ss.v:", v)
}
}
func TestIAHeader(t *testing.T) {
fmt.Println("\n---------------- indexedarray_test.go ...")
}
var ak_data = []byte(`{ "section1":{"data" : [{"F1" : "F1 data","F2" : "F2 data"},{"F1" : "demo 123","F2" : "abc xyz"}]}}`)
var j_data = []byte(`{ "stuff":[ { "data":[ { "F":1 }, { "F":2 }, { "F":3 } ] }, { "data":[ 4, 5, 6 ] } ] }`)
var x_data = []byte(`
<doc>
<stuff>
<data seq="1.1">
<F>1</F>
</data>
<data seq="1.2">
<F>2</F>
</data>
<data seq="1.3">
<F>3</F>
</data>
</stuff>
<stuff>
<data seq="2.1">
<F>4</F>
</data>
<data seq="2.2">
<F>5</F>
</data>
<data seq="2.3">
<F>6</F>
</data>
</stuff>
</doc>`)
func TestValuesForIndexedArray(t *testing.T) {
j_main(t)
x_main(t)
ak_main(t)
}
func ak_main(t *testing.T) {
fmt.Println("\nak_data:", string(ak_data))
m, merr := NewMapJson(ak_data)
if merr != nil {
t.Fatal("merr:", merr.Error())
return
}
fmt.Println("m:", m)
v, verr := m.ValuesForPath("section1.data[0].F1")
if verr != nil {
t.Fatal("verr:", verr.Error())
}
fmt.Println("section1.data[0].F1:", v)
}
func j_main(t *testing.T) {
fmt.Println("j_data:", string(j_data))
m, merr := NewMapJson(j_data)
if merr != nil {
t.Fatal("merr:", merr.Error())
return
}
fmt.Println("m:", m)
v, verr := m.ValuesForPath("stuff[0]")
if verr != nil {
t.Fatal("verr:", verr.Error())
}
fmt.Println("stuff[0]:", v)
v, verr = m.ValuesForPath("stuff.data")
if verr != nil {
t.Fatal("verr:", verr.Error())
}
fmt.Println("stuff.data:", v)
v, verr = m.ValuesForPath("stuff[0].data")
if verr != nil {
t.Fatal("verr:", verr.Error())
}
fmt.Println("stuff[0].data:", v)
v, verr = m.ValuesForPath("stuff.data[0]")
if verr != nil {
t.Fatal("verr:", verr.Error())
}
fmt.Println("stuff.data[0]:", v)
v, verr = m.ValuesForPath("stuff.*[2]")
if verr != nil {
t.Fatal("verr:", verr.Error())
}
fmt.Println("stuff.*[2]:", v)
v, verr = m.ValuesForPath("stuff.data.F")
if verr != nil {
t.Fatal("verr:", verr.Error())
}
fmt.Println("stuff.data.F:", v)
v, verr = m.ValuesForPath("*.*.F")
if verr != nil {
t.Fatal("verr:", verr.Error())
}
fmt.Println("*.*.F:", v)
v, verr = m.ValuesForPath("stuff.data[0].F")
if verr != nil {
t.Fatal("verr:", verr.Error())
}
fmt.Println("stuff.data[0].F:", v)
v, verr = m.ValuesForPath("stuff.data[1].F")
if verr != nil {
t.Fatal("verr:", verr.Error())
}
fmt.Println("stuff.data[1].F:", v)
v, verr = m.ValuesForPath("stuff[0].data[2]")
if verr != nil {
t.Fatal("verr:", verr.Error())
}
fmt.Println("stuff[0].data[2]:", v)
v, verr = m.ValuesForPath("stuff[1].data[1]")
if verr != nil {
t.Fatal("verr:", verr.Error())
}
fmt.Println("stuff[1].data[1]:", v)
v, verr = m.ValuesForPath("stuff[1].data[1].F")
if verr != nil {
t.Fatal("verr:", verr.Error())
}
fmt.Println("stuff[1].data[1].F", v)
v, verr = m.ValuesForPath("stuff[1].data.F")
if verr != nil {
t.Fatal("verr:", verr.Error())
}
fmt.Println("stuff[1].data.F:", v)
}
func x_main(t *testing.T) {
fmt.Println("\nx_data:", string(x_data))
m, merr := NewMapXml(x_data)
if merr != nil {
t.Fatal("merr:", merr.Error())
return
}
fmt.Println("m:", m)
v, verr := m.ValuesForPath("doc.stuff[0]")
if verr != nil {
t.Fatal("verr:", verr.Error())
}
fmt.Println("doc.stuff[0]:", v)
v, verr = m.ValuesForPath("doc.stuff.data[0]")
if verr != nil {
t.Fatal("verr:", verr.Error())
}
fmt.Println("doc.stuff.data[0]:", v)
v, verr = m.ValuesForPath("doc.stuff.data[0]", "-seq:2.1")
if verr != nil {
t.Fatal("verr:", verr.Error())
}
fmt.Println("doc.stuff.data[0] -seq:2.1:", v)
v, verr = m.ValuesForPath("doc.stuff.data[0].F")
if verr != nil {
t.Fatal("verr:", verr.Error())
}
fmt.Println("doc.stuff.data[0].F:", v)
v, verr = m.ValuesForPath("doc.stuff[0].data[2]")
if verr != nil {
t.Fatal("verr:", verr.Error())
}
fmt.Println("doc.stuff[0].data[2]:", v)
v, verr = m.ValuesForPath("doc.stuff[1].data[1].F")
if verr != nil {
t.Fatal("verr:", verr.Error())
}
fmt.Println("doc.stuff[1].data[1].F:", v)
}
func TestValueForPath(t *testing.T) {
m := map[string]interface{}{
"Div": map[string]interface{}{
"Colour": "blue",
},
}
mv := Map(m)
v, err := mv.ValueForPath("Div.Colour")
if err != nil {
t.Fatal(err)
}
if str, ok := v.(string); !ok || str != "blue" {
t.Fatal("wrong value")
}
}
func TestValueForPathString(t *testing.T) {
m := map[string]interface{}{
"Div": map[string]interface{}{
"Colour": "blue",
},
}
mv := Map(m)
str, err := mv.ValueForPathString("Div.Colour")
if err != nil {
t.Fatal(err)
}
if str != "blue" {
t.Fatal("wrong value")
}
}

View File

@ -0,0 +1,112 @@
package mxj
// leafnode.go - return leaf nodes with paths and values for the Map
// inspired by: https://groups.google.com/forum/#!topic/golang-nuts/3JhuVKRuBbw
import (
"strconv"
"strings"
)
const (
NoAttributes = true // suppress LeafNode values that are attributes
)
// LeafNode - a terminal path value in a Map.
// For XML Map values it represents an attribute or simple element value - of type
// string unless Map was created using Cast flag. For JSON Map values it represents
// a string, numeric, boolean, or null value.
type LeafNode struct {
Path string // a dot-notation representation of the path with array subscripting
Value interface{} // the value at the path termination
}
// LeafNodes - returns an array of all LeafNode values for the Map.
// The option no_attr argument suppresses attribute values (keys with prepended hyphen, '-')
// as well as the "#text" key for the associated simple element value.
//
// PrependAttrWithHypen(false) will result in attributes having .attr-name as
// terminal node in 'path' while the path for the element value, itself, will be
// the base path w/o "#text".
//
// LeafUseDotNotation(true) causes list members to be identified using ".N" syntax
// rather than "[N]" syntax.
func (mv Map) LeafNodes(no_attr ...bool) []LeafNode {
var a bool
if len(no_attr) == 1 {
a = no_attr[0]
}
l := make([]LeafNode, 0)
getLeafNodes("", "", map[string]interface{}(mv), &l, a)
return l
}
func getLeafNodes(path, node string, mv interface{}, l *[]LeafNode, noattr bool) {
// if stripping attributes, then also strip "#text" key
if !noattr || node != "#text" {
if path != "" && node[:1] != "[" {
path += "."
}
path += node
}
switch mv.(type) {
case map[string]interface{}:
for k, v := range mv.(map[string]interface{}) {
// if noattr && k[:1] == "-" {
if noattr && len(attrPrefix) > 0 && strings.Index(k, attrPrefix) == 0 {
continue
}
getLeafNodes(path, k, v, l, noattr)
}
case []interface{}:
for i, v := range mv.([]interface{}) {
if useDotNotation {
getLeafNodes(path, strconv.Itoa(i), v, l, noattr)
} else {
getLeafNodes(path, "["+strconv.Itoa(i)+"]", v, l, noattr)
}
}
default:
// can't walk any further, so create leaf
n := LeafNode{path, mv}
*l = append(*l, n)
}
}
// LeafPaths - all paths that terminate in LeafNode values.
func (mv Map) LeafPaths(no_attr ...bool) []string {
ln := mv.LeafNodes()
ss := make([]string, len(ln))
for i := 0; i < len(ln); i++ {
ss[i] = ln[i].Path
}
return ss
}
// LeafValues - all terminal values in the Map.
func (mv Map) LeafValues(no_attr ...bool) []interface{} {
ln := mv.LeafNodes()
vv := make([]interface{}, len(ln))
for i := 0; i < len(ln); i++ {
vv[i] = ln[i].Value
}
return vv
}
// ====================== utilities ======================
// https://groups.google.com/forum/#!topic/golang-nuts/pj0C5IrZk4I
var useDotNotation bool
// LeafUseDotNotation sets a flag that list members in LeafNode paths
// should be identified using ".N" syntax rather than the default "[N]"
// syntax. Calling LeafUseDotNotation with no arguments toggles the
// flag on/off; otherwise, the argument sets the flag value 'true'/'false'.
func LeafUseDotNotation(b ...bool) {
if len(b) == 0 {
useDotNotation = !useDotNotation
return
}
useDotNotation = b[0]
}

View File

@ -0,0 +1,140 @@
package mxj
import (
"fmt"
"testing"
)
func TestLNHeader(t *testing.T) {
fmt.Println("\n---------------- leafnode_test.go ...")
}
func TestLeafNodes(t *testing.T) {
json1 := []byte(`{
"friends": [
{
"skills": [
44, 12
]
}
]
}`)
json2 := []byte(`{
"friends":
{
"skills": [
44, 12
]
}
}`)
m, _ := NewMapJson(json1)
ln := m.LeafNodes()
fmt.Println("\njson1-LeafNodes:")
for _, v := range ln {
fmt.Printf("%#v\n", v)
}
p := m.LeafPaths()
fmt.Println("\njson1-LeafPaths:")
for _, v := range p {
fmt.Printf("%#v\n", v)
}
m, _ = NewMapJson(json2)
ln = m.LeafNodes()
fmt.Println("\njson2-LeafNodes:")
for _, v := range ln {
fmt.Printf("%#v\n", v)
}
v := m.LeafValues()
fmt.Println("\njson1-LeafValues:")
for _, v := range v {
fmt.Printf("%#v\n", v)
}
json3 := []byte(`{ "a":"list", "of":["data", "of", 3, "types", true]}`)
m, _ = NewMapJson(json3)
ln = m.LeafNodes()
fmt.Println("\njson3-LeafNodes:")
for _, v := range ln {
fmt.Printf("%#v\n", v)
}
v = m.LeafValues()
fmt.Println("\njson3-LeafValues:")
for _, v := range v {
fmt.Printf("%#v\n", v)
}
p = m.LeafPaths()
fmt.Println("\njson3-LeafPaths:")
for _, v := range p {
fmt.Printf("%#v\n", v)
}
xmldata2 := []byte(`
<doc>
<item num="2" color="blue">Item 2 is blue</item>
<item num="3" color="green">
<arm side="left" length="3.5"/>
<arm side="right" length="3.6"/>
</item>
</doc>`)
m, err := NewMapXml(xmldata2)
if err != nil {
t.Fatal(err.Error())
}
fmt.Println("\nxml2data2-LeafValues:")
ln = m.LeafNodes()
for _, v := range ln {
fmt.Printf("%#v\n", v)
}
fmt.Println("\nxml2data2-LeafValues(NoAttributes):")
ln = m.LeafNodes(NoAttributes)
for _, v := range ln {
fmt.Printf("%#v\n", v)
}
// no-hyphen
PrependAttrWithHyphen(false)
m, err = NewMapXml(xmldata2)
if err != nil {
t.Fatal(err.Error())
}
fmt.Println("\nno-hyphen-xml2data2-LeafValues:")
ln = m.LeafNodes()
for _, v := range ln {
fmt.Printf("%#v\n", v)
}
fmt.Println("\nno-hyphen-xml2data2-LeafValues(NoAttributes):")
ln = m.LeafNodes(NoAttributes)
for _, v := range ln {
fmt.Printf("%#v\n", v)
}
// restore default
PrependAttrWithHyphen(true)
}
func TestLeafDotNotation(t *testing.T) {
xmldata2 := []byte(`
<doc>
<item num="2" color="blue">Item 2 is blue</item>
<item num="3" color="green">
<arm side="left" length="3.5"/>
<arm side="right" length="3.6"/>
</item>
</doc>`)
m, err := NewMapXml(xmldata2)
if err != nil {
t.Fatal(err.Error())
}
fmt.Println("\nDotNotation-LeafValues:")
LeafUseDotNotation()
defer LeafUseDotNotation()
ln := m.LeafNodes()
for _, v := range ln {
fmt.Printf("%#v\n", v)
}
}

View File

@ -0,0 +1,86 @@
// Copyright 2016 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
// misc.go - mimic functions (+others) called out in:
// https://groups.google.com/forum/#!topic/golang-nuts/jm_aGsJNbdQ
// Primarily these methods let you retrive XML structure information.
package mxj
import (
"fmt"
"sort"
"strings"
)
// Return the root element of the Map. If there is not a single key in Map,
// then an error is returned.
func (mv Map) Root() (string, error) {
mm := map[string]interface{}(mv)
if len(mm) != 1 {
return "", fmt.Errorf("Map does not have singleton root. Len: %d.", len(mm))
}
for k, _ := range mm {
return k, nil
}
return "", nil
}
// If the path is an element with sub-elements, return a list of the sub-element
// keys. (The list is alphabeticly sorted.) NOTE: Map keys that are prefixed with
// '-', a hyphen, are considered attributes; see m.Attributes(path).
func (mv Map) Elements(path string) ([]string, error) {
e, err := mv.ValueForPath(path)
if err != nil {
return nil, err
}
switch e.(type) {
case map[string]interface{}:
ee := e.(map[string]interface{})
elems := make([]string, len(ee))
var i int
for k, _ := range ee {
if len(attrPrefix) > 0 && strings.Index(k, attrPrefix) == 0 {
continue // skip attributes
}
elems[i] = k
i++
}
elems = elems[:i]
// alphabetic sort keeps things tidy
sort.Strings(elems)
return elems, nil
}
return nil, fmt.Errorf("no elements for path: %s", path)
}
// If the path is an element with attributes, return a list of the attribute
// keys. (The list is alphabeticly sorted.) NOTE: Map keys that are not prefixed with
// '-', a hyphen, are not treated as attributes; see m.Elements(path). Also, if the
// attribute prefix is "" - SetAttrPrefix("") or PrependAttrWithHyphen(false) - then
// there are no identifiable attributes.
func (mv Map) Attributes(path string) ([]string, error) {
a, err := mv.ValueForPath(path)
if err != nil {
return nil, err
}
switch a.(type) {
case map[string]interface{}:
aa := a.(map[string]interface{})
attrs := make([]string, len(aa))
var i int
for k, _ := range aa {
if len(attrPrefix) == 0 || strings.Index(k, attrPrefix) != 0 {
continue // skip non-attributes
}
attrs[i] = k[len(attrPrefix):]
i++
}
attrs = attrs[:i]
// alphabetic sort keeps things tidy
sort.Strings(attrs)
return attrs, nil
}
return nil, fmt.Errorf("no attributes for path: %s", path)
}

View File

@ -0,0 +1,199 @@
// misc_test.go
package mxj
import (
"fmt"
"testing"
)
var miscdata = []byte(`
<doc>
<elem1 name="elem1" seq="1">
<sub1 name="sub1" seq="1">sub_value_1</sub1>
<sub2 name="sub2" seq="2">sub_value_2</sub2>
</elem1>
<elem2 name="elem2" seq="2">element_2</elem2>
</doc>
`)
func TestMisc(t *testing.T) {
PrependAttrWithHyphen(true) // be safe
fmt.Println("\n------------------ misc_test.go ...")
}
func TestRoot(t *testing.T) {
m, err := NewMapXml(miscdata)
if err != nil {
t.Fatal(err)
}
r, err := m.Root()
if err != nil {
t.Fatal(err)
}
if r != "doc" {
t.Fatal("Root not doc:", r)
}
}
func TestElements(t *testing.T) {
m, err := NewMapXml(miscdata)
if err != nil {
t.Fatal(err)
}
e, err := m.Elements("doc")
if err != nil {
t.Fatal(err)
}
elist := []string{"elem1", "elem2"}
for i, ee := range e {
if ee != elist[i] {
t.Fatal("error in list, elem#:", i, "-", ee, ":", elist[i])
}
}
e, err = m.Elements("doc.elem1")
if err != nil {
t.Fatal(err)
}
elist = []string{"sub1", "sub2"}
for i, ee := range e {
if ee != elist[i] {
t.Fatal("error in list, elem#:", i, "-", ee, ":", elist[i])
}
}
}
func TestAttributes(t *testing.T) {
m, err := NewMapXml(miscdata)
if err != nil {
t.Fatal(err)
}
a, err := m.Attributes("doc.elem2")
if err != nil {
t.Fatal(err)
}
alist := []string{"name", "seq"}
for i, aa := range a {
if aa != alist[i] {
t.Fatal("error in list, elem#:", i, "-", aa, ":", alist[i])
}
}
a, err = m.Attributes("doc.elem1.sub2")
if err != nil {
t.Fatal(err)
}
alist = []string{"name", "seq"}
for i, aa := range a {
if aa != alist[i] {
t.Fatal("error in list, elem#:", i, "-", aa, ":", alist[i])
}
}
}
func TestElementsAttrPrefix(t *testing.T) {
SetAttrPrefix("__")
m, err := NewMapXml(miscdata)
if err != nil {
t.Fatal(err)
}
e, err := m.Elements("doc")
if err != nil {
t.Fatal(err)
}
elist := []string{"elem1", "elem2"}
for i, ee := range e {
if ee != elist[i] {
t.Fatal("error in list, elem#:", i, "-", ee, ":", elist[i])
}
}
e, err = m.Elements("doc.elem1")
if err != nil {
t.Fatal(err)
}
elist = []string{"sub1", "sub2"}
for i, ee := range e {
if ee != elist[i] {
t.Fatal("error in list, elem#:", i, "-", ee, ":", elist[i])
}
}
}
func TestAttributesAttrPrefix(t *testing.T) {
SetAttrPrefix("__")
m, err := NewMapXml(miscdata)
if err != nil {
t.Fatal(err)
}
a, err := m.Attributes("doc.elem2")
if err != nil {
t.Fatal(err)
}
alist := []string{"name", "seq"}
for i, aa := range a {
if aa != alist[i] {
t.Fatal("error in list, elem#:", i, "-", aa, ":", alist[i])
}
}
a, err = m.Attributes("doc.elem1.sub2")
if err != nil {
t.Fatal(err)
}
alist = []string{"name", "seq"}
for i, aa := range a {
if aa != alist[i] {
t.Fatal("error in list, elem#:", i, "-", aa, ":", alist[i])
}
}
}
func TestElementsNoAttrPrefix(t *testing.T) {
PrependAttrWithHyphen(false)
m, err := NewMapXml(miscdata)
if err != nil {
t.Fatal(err)
}
e, err := m.Elements("doc")
if err != nil {
t.Fatal(err)
}
if len(e) != 2 {
t.Fatal("didn't get 2 elements:", e)
}
e, err = m.Elements("doc.elem1")
if err != nil {
t.Fatal(err)
}
if len(e) != 4 {
t.Fatal("didn't get 4 elements:", e)
}
PrependAttrWithHyphen(true)
}
func TestAttributesNoAttrPrefix(t *testing.T) {
PrependAttrWithHyphen(false)
m, err := NewMapXml(miscdata)
if err != nil {
t.Fatal(err)
}
a, err := m.Attributes("doc.elem2")
if err != nil {
t.Fatal(err)
}
if len(a) > 0 {
t.Fatal("found attributes where there are none:", a)
}
a, err = m.Attributes("doc.elem1.sub2")
if err != nil {
t.Fatal(err)
}
if len(a) > 0 {
t.Fatal("found attributes where there are none:", a)
}
PrependAttrWithHyphen(true)
}

View File

@ -0,0 +1,128 @@
// mxj - A collection of map[string]interface{} and associated XML and JSON utilities.
// Copyright 2012-2014 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
package mxj
import (
"fmt"
"sort"
)
const (
Cast = true // for clarity - e.g., mxj.NewMapXml(doc, mxj.Cast)
SafeEncoding = true // ditto - e.g., mv.Json(mxj.SafeEncoding)
)
type Map map[string]interface{}
// Allocate a Map.
func New() Map {
m := make(map[string]interface{}, 0)
return m
}
// Cast a Map to map[string]interface{}
func (mv Map) Old() map[string]interface{} {
return mv
}
// Return a copy of mv as a newly allocated Map. If the Map only contains string,
// numeric, map[string]interface{}, and []interface{} values, then it can be thought
// of as a "deep copy." Copying a structure (or structure reference) value is subject
// to the noted restrictions.
// NOTE: If 'mv' includes structure values with, possibly, JSON encoding tags
// then only public fields of the structure are in the new Map - and with
// keys that conform to any encoding tag instructions. The structure itself will
// be represented as a map[string]interface{} value.
func (mv Map) Copy() (Map, error) {
// this is the poor-man's deep copy
// not efficient, but it works
j, jerr := mv.Json()
// must handle, we don't know how mv got built
if jerr != nil {
return nil, jerr
}
return NewMapJson(j)
}
// --------------- StringIndent ... from x2j.WriteMap -------------
// Pretty print a Map.
func (mv Map) StringIndent(offset ...int) string {
return writeMap(map[string]interface{}(mv), true, true, offset...)
}
// Pretty print a Map without the value type information - just key:value entries.
func (mv Map) StringIndentNoTypeInfo(offset ...int) string {
return writeMap(map[string]interface{}(mv), false, true, offset...)
}
// writeMap - dumps the map[string]interface{} for examination.
// 'typeInfo' causes value type to be printed.
// 'offset' is initial indentation count; typically: Write(m).
func writeMap(m interface{}, typeInfo, root bool, offset ...int) string {
var indent int
if len(offset) == 1 {
indent = offset[0]
}
var s string
switch m.(type) {
case []interface{}:
if typeInfo {
s += "[[]interface{}]"
}
for _, v := range m.([]interface{}) {
s += "\n"
for i := 0; i < indent; i++ {
s += " "
}
s += writeMap(v, typeInfo, false, indent+1)
}
case map[string]interface{}:
list := make([][2]string, len(m.(map[string]interface{})))
var n int
for k, v := range m.(map[string]interface{}) {
list[n][0] = k
list[n][1] = writeMap(v, typeInfo, false, indent+1)
n++
}
sort.Sort(mapList(list))
for _, v := range list {
if root {
root = false
} else {
s += "\n"
}
for i := 0; i < indent; i++ {
s += " "
}
s += v[0] + " : " + v[1]
}
default:
if typeInfo {
s += fmt.Sprintf("[%T] %+v", m, m)
} else {
s += fmt.Sprintf("%+v", m)
}
}
return s
}
// ======================== utility ===============
type mapList [][2]string
func (ml mapList) Len() int {
return len(ml)
}
func (ml mapList) Swap(i, j int) {
ml[i], ml[j] = ml[j], ml[i]
}
func (ml mapList) Less(i, j int) bool {
return ml[i][0] <= ml[j][0]
}

View File

@ -0,0 +1,46 @@
package mxj
import (
"fmt"
"testing"
)
func TestMxjHeader(t *testing.T) {
fmt.Println("\n---------------- mxj_test.go ...")
}
func TestMap(t *testing.T) {
m := New()
m["key"] = interface{}("value")
v := map[string]interface{}{"bool": true, "float": 3.14159, "string": "Now is the time"}
vv := []interface{}{3.1415962535, false, "for all good men"}
v["listkey"] = interface{}(vv)
m["newkey"] = interface{}(v)
fmt.Println("TestMap, m:")
fmt.Printf("%#v\n", m)
fmt.Println("TestMap, StringIndent -")
fmt.Println(m.StringIndent())
fmt.Println("TestMap, StringIndent NoTypeInfo -")
fmt.Println(m.StringIndentNoTypeInfo())
o := interface{}(m.Old())
switch o.(type) {
case map[string]interface{}:
// do nothing
default:
t.Fatal("invalid type for m.Old()")
}
m, _ = NewMapXml([]byte(`<doc><tag><sub_tag1>Hello</sub_tag1><sub_tag2>World</sub_tag2></tag></doc>`))
fmt.Println("TestMap, m_fromXML:")
fmt.Printf("%#v\n", m)
fmt.Println("TestMap, StringIndent -")
fmt.Println( m.StringIndent())
fmt.Println("TestMap, StringIndent NoTypeInfo -")
fmt.Println( m.StringIndentNoTypeInfo())
mm, _ := m.Copy()
fmt.Println("TestMap, m.Copy() -\n", mm)
}

View File

@ -0,0 +1,21 @@
package mxj
import (
"fmt"
"testing"
)
func TestNamespaceHeader(t *testing.T) {
fmt.Println("\n---------------- namespace_test.go ...")
}
func TestBeautifyXml(t *testing.T) {
fmt.Println("\n---------------- TestBeautifyXml ...")
const flatxml = `<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://example.com/ns"><soapenv:Header/><soapenv:Body><ns:request><ns:customer><ns:id>123</ns:id><ns:name type="NCHZ">John Brown</ns:name></ns:customer></ns:request></soapenv:Body></soapenv:Envelope>`
v, err := BeautifyXml([]byte(flatxml), "", " ")
if err != nil {
t.Fatal(err)
}
fmt.Println(flatxml)
fmt.Println(string(v))
}

View File

@ -0,0 +1,81 @@
// nan_test.go
package mxj
import (
"fmt"
"testing"
)
func TestNan(t *testing.T) {
fmt.Println("\n------------ TestNan ...")
data := []byte("<foo><bar>NAN</bar></foo>")
m, err := NewMapXml(data, true)
if err != nil {
t.Fatal("err:", err)
}
v, err := m.ValueForPath("foo.bar")
if err != nil {
t.Fatal("err:", err)
}
if _, ok := v.(string); !ok {
t.Fatal("v not string")
}
fmt.Println("foo.bar:", v)
}
func TestInf(t *testing.T) {
data := []byte("<foo><bar>INF</bar></foo>")
m, err := NewMapXml(data, true)
if err != nil {
t.Fatal("err:", err)
}
v, err := m.ValueForPath("foo.bar")
if err != nil {
t.Fatal("err:", err)
}
if _, ok := v.(string); !ok {
t.Fatal("v not string")
}
fmt.Println("foo.bar:", v)
}
func TestMinusInf(t *testing.T) {
data := []byte("<foo><bar>-INF</bar></foo>")
m, err := NewMapXml(data, true)
if err != nil {
t.Fatal("err:", err)
}
v, err := m.ValueForPath("foo.bar")
if err != nil {
t.Fatal("err:", err)
}
if _, ok := v.(string); !ok {
t.Fatal("v not string")
}
fmt.Println("foo.bar:", v)
}
func TestCastNanInf(t *testing.T) {
data := []byte("<foo><bar>NAN</bar></foo>")
CastNanInf(true)
m, err := NewMapXml(data, true)
if err != nil {
t.Fatal("err:", err)
}
v, err := m.ValueForPath("foo.bar")
if err != nil {
t.Fatal("err:", err)
}
if _, ok := v.(float64); !ok {
fmt.Printf("%#v\n", v)
t.Fatal("v not float64")
}
fmt.Println("foo.bar:", v)
}

View File

@ -0,0 +1,183 @@
// mxj - A collection of map[string]interface{} and associated XML and JSON utilities.
// Copyright 2012-2014 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
// remap.go - build a new Map from the current Map based on keyOld:keyNew mapppings
// keys can use dot-notation, keyOld can use wildcard, '*'
//
// Computational strategy -
// Using the key path - []string - traverse a new map[string]interface{} and
// insert the oldVal as the newVal when we arrive at the end of the path.
// If the type at the end is nil, then that is newVal
// If the type at the end is a singleton (string, float64, bool) an array is created.
// If the type at the end is an array, newVal is just appended.
// If the type at the end is a map, it is inserted if possible or the map value
// is converted into an array if necessary.
package mxj
import (
"errors"
"strings"
)
// (Map)NewMap - create a new Map from data in the current Map.
// 'keypairs' are key mappings "oldKey:newKey" and specify that the current value of 'oldKey'
// should be the value for 'newKey' in the returned Map.
// - 'oldKey' supports dot-notation as described for (Map)ValuesForPath()
// - 'newKey' supports dot-notation but with no wildcards, '*', or indexed arrays
// - "oldKey" is shorthand for the keypair value "oldKey:oldKey"
// - "oldKey:" and ":newKey" are invalid keypair values
// - if 'oldKey' does not exist in the current Map, it is not written to the new Map.
// "null" is not supported unless it is the current Map.
// - see newmap_test.go for several syntax examples
//
// NOTE: mv.NewMap() == mxj.New().
func (mv Map) NewMap(keypairs ...string) (Map, error) {
n := make(map[string]interface{}, 0)
if len(keypairs) == 0 {
return n, nil
}
// loop through the pairs
var oldKey, newKey string
var path []string
for _, v := range keypairs {
if len(v) == 0 {
continue // just skip over empty keypair arguments
}
// initialize oldKey, newKey and check
vv := strings.Split(v, ":")
if len(vv) > 2 {
return n, errors.New("oldKey:newKey keypair value not valid - " + v)
}
if len(vv) == 1 {
oldKey, newKey = vv[0], vv[0]
} else {
oldKey, newKey = vv[0], vv[1]
}
strings.TrimSpace(oldKey)
strings.TrimSpace(newKey)
if i := strings.Index(newKey, "*"); i > -1 {
return n, errors.New("newKey value cannot contain wildcard character - " + v)
}
if i := strings.Index(newKey, "["); i > -1 {
return n, errors.New("newKey value cannot contain indexed arrays - " + v)
}
if oldKey == "" || newKey == "" {
return n, errors.New("oldKey or newKey is not specified - " + v)
}
// get oldKey value
oldVal, err := mv.ValuesForPath(oldKey)
if err != nil {
return n, err
}
if len(oldVal) == 0 {
continue // oldKey has no value, may not exist in mv
}
// break down path
path = strings.Split(newKey, ".")
if path[len(path)-1] == "" { // ignore a trailing dot in newKey spec
path = path[:len(path)-1]
}
addNewVal(&n, path, oldVal)
}
return n, nil
}
// navigate 'n' to end of path and add val
func addNewVal(n *map[string]interface{}, path []string, val []interface{}) {
// newVal - either singleton or array
var newVal interface{}
if len(val) == 1 {
newVal = val[0] // is type interface{}
} else {
newVal = interface{}(val)
}
// walk to the position of interest, create it if necessary
m := (*n) // initialize map walker
var k string // key for m
lp := len(path) - 1 // when to stop looking
for i := 0; i < len(path); i++ {
k = path[i]
if i == lp {
break
}
var nm map[string]interface{} // holds position of next-map
switch m[k].(type) {
case nil: // need a map for next node in path, so go there
nm = make(map[string]interface{}, 0)
m[k] = interface{}(nm)
m = m[k].(map[string]interface{})
case map[string]interface{}:
// OK - got somewhere to walk to, go there
m = m[k].(map[string]interface{})
case []interface{}:
// add a map and nm points to new map unless there's already
// a map in the array, then nm points there
// The placement of the next value in the array is dependent
// on the sequence of members - could land on a map or a nil
// value first. TODO: how to test this.
a := make([]interface{}, 0)
var foundmap bool
for _, vv := range m[k].([]interface{}) {
switch vv.(type) {
case nil: // doesn't appear that this occurs, need a test case
if foundmap { // use the first one in array
a = append(a, vv)
continue
}
nm = make(map[string]interface{}, 0)
a = append(a, interface{}(nm))
foundmap = true
case map[string]interface{}:
if foundmap { // use the first one in array
a = append(a, vv)
continue
}
nm = vv.(map[string]interface{})
a = append(a, vv)
foundmap = true
default:
a = append(a, vv)
}
}
// no map found in array
if !foundmap {
nm = make(map[string]interface{}, 0)
a = append(a, interface{}(nm))
}
m[k] = interface{}(a) // must insert in map
m = nm
default: // it's a string, float, bool, etc.
aa := make([]interface{}, 0)
nm = make(map[string]interface{}, 0)
aa = append(aa, m[k], nm)
m[k] = interface{}(aa)
m = nm
}
}
// value is nil, array or a singleton of some kind
// initially m.(type) == map[string]interface{}
v := m[k]
switch v.(type) {
case nil: // initialized
m[k] = newVal
case []interface{}:
a := m[k].([]interface{})
a = append(a, newVal)
m[k] = interface{}(a)
default: // v exists:string, float64, bool, map[string]interface, etc.
a := make([]interface{}, 0)
a = append(a, v, newVal)
m[k] = interface{}(a)
}
}

View File

@ -0,0 +1,114 @@
package mxj
import (
"bytes"
"fmt"
"io"
"testing"
)
func TestNewMapHeader(t *testing.T) {
fmt.Println("\n---------------- newmap_test.go ...")
}
func TestNewMap(t *testing.T) {
j := []byte(`{ "A":"this", "B":"is", "C":"a", "D":"test" }`)
fmt.Println("j:", string(j))
m, _ := NewMapJson(j)
fmt.Printf("m: %#v\n", m)
fmt.Println("\n", `eval - m.NewMap("A:AA", "B:BB", "C:cc", "D:help")`)
n, err := m.NewMap("A:AA", "B:BB", "C:cc", "D:help")
if err != nil {
fmt.Println("err:", err.Error())
}
j, _ = n.Json()
fmt.Println("n.Json():", string(j))
x, _ := n.Xml()
fmt.Println("n.Xml():\n", string(x))
x, _ = n.XmlIndent("", " ")
fmt.Println("n.XmlIndent():\n", string(x))
fmt.Println("\n", `eval - m.NewMap("A:AA.A", "B:AA.B", "C:AA.B.cc", "D:hello.help")`)
n, err = m.NewMap("A:AA.A", "B:AA.B", "C:AA.B.cc", "D:hello.help")
if err != nil {
fmt.Println("err:", err.Error())
}
j, _ = n.Json()
fmt.Println("n.Json():", string(j))
x, _ = n.Xml()
fmt.Println("n.Xml():\n", string(x))
x, _ = n.XmlIndent("", " ")
fmt.Println("n.XmlIndent():\n", string(x))
var keypairs = []string{"A:xml.AA", "B:xml.AA.hello.again", "C:xml.AA", "D:xml.AA.hello.help"}
fmt.Println("\n", `eval - m.NewMap keypairs:`, keypairs)
n, err = m.NewMap(keypairs...)
if err != nil {
fmt.Println("err:", err.Error())
}
j, _ = n.Json()
fmt.Println("n.Json():", string(j))
x, _ = n.Xml()
fmt.Println("n.Xml():\n", string(x))
x, _ = n.XmlIndent("", " ")
fmt.Println("n.XmlIndent():\n", string(x))
}
// Need to normalize from an XML stream the values for "netid" and "idnet".
// Solution: make everything "netid"
// Demo how to re-label a key using mv.NewMap()
var msg1 = []byte(`
<?xml version="1.0" encoding="UTF-8"?>
<data>
<netid>
<disable>no</disable>
<text1>default:text</text1>
<word1>default:word</word1>
</netid>
</data>
`)
var msg2 = []byte(`
<?xml version="1.0" encoding="UTF-8"?>
<data>
<idnet>
<disable>yes</disable>
<text1>default:text</text1>
<word1>default:word</word1>
</idnet>
</data>
`)
func TestNetId(t *testing.T) {
// let's create a message stream
buf := new(bytes.Buffer)
// load a couple of messages into it
_, _ = buf.Write(msg1)
_, _ = buf.Write(msg2)
n := 0
for {
n++
// read the stream as Map values - quit on io.EOF
m, raw, merr := NewMapXmlReaderRaw(buf)
if merr != nil && merr != io.EOF {
// handle error - for demo we just print it and continue
fmt.Printf("msg: %d - merr: %s\n", n, merr.Error())
continue
} else if merr == io.EOF {
break
}
// the first keypair retains values if data correct
// the second keypair relabels "idnet" to "netid"
n, _ := m.NewMap("data.netid", "data.idnet:data.netid")
x, _ := n.XmlIndent("", " ")
fmt.Println("original value:", string(raw))
fmt.Println("new value:")
fmt.Println(string(x))
}
}

View File

@ -0,0 +1,178 @@
<h2>mxj - to/from maps, XML and JSON</h2>
Decode/encode XML to/from map[string]interface{} (or JSON) values, and extract/modify values from maps by key or key-path, including wildcards.
mxj supplants the legacy x2j and j2x packages. If you want the old syntax, use mxj/x2j and mxj/j2x packages.
<h4>Related Packages</h4>
https://github.com/clbanning/checkxml provides functions for validating XML data.
<h4>Refactor Decoder - 2015.11.15</h4>
For over a year I've wanted to refactor the XML-to-map[string]interface{} decoder to make it more performant. I recently took the time to do that, since we were using github.com/clbanning/mxj in a production system that could be deployed on a Raspberry Pi. Now the decoder is comparable to the stdlib JSON-to-map[string]interface{} decoder in terms of its additional processing overhead relative to decoding to a structure value. As shown by:
BenchmarkNewMapXml-4 100000 18043 ns/op
BenchmarkNewStructXml-4 100000 14892 ns/op
BenchmarkNewMapJson-4 300000 4633 ns/op
BenchmarkNewStructJson-4 300000 3427 ns/op
BenchmarkNewMapXmlBooks-4 20000 82850 ns/op
BenchmarkNewStructXmlBooks-4 20000 67822 ns/op
BenchmarkNewMapJsonBooks-4 100000 17222 ns/op
BenchmarkNewStructJsonBooks-4 100000 15309 ns/op
<h4>Notices</h4>
2018.04.18: mv.Xml/mv.XmlIndent encodes non-map[string]interface{} map values - map[string]string, map[int]uint, etc.
2018.03.29: mv.Gob/NewMapGob support gob encoding/decoding of Maps.
2018.03.26: Added mxj/x2j-wrapper sub-package for migrating from legacy x2j package.
2017.02.22: LeafNode paths can use ".N" syntax rather than "[N]" for list member indexing.
2017.02.10: SetFieldSeparator changes field separator for args in UpdateValuesForPath, ValuesFor... methods.
2017.02.06: Support XMPP stream processing - HandleXMPPStreamTag().
2016.11.07: Preserve name space prefix syntax in XmlSeq parser - NewMapXmlSeq(), etc.
2016.06.25: Support overriding default XML attribute prefix, "-", in Map keys - SetAttrPrefix().
2016.05.26: Support customization of xml.Decoder by exposing CustomDecoder variable.
2016.03.19: Escape invalid chars when encoding XML attribute and element values - XMLEscapeChars().
2016.03.02: By default decoding XML with float64 and bool value casting will not cast "NaN", "Inf", and "-Inf".
To cast them to float64, first set flag with CastNanInf(true).
2016.02.22: New mv.Root(), mv.Elements(), mv.Attributes methods let you examine XML document structure.
2016.02.16: Add CoerceKeysToLower() option to handle tags with mixed capitalization.
2016.02.12: Seek for first xml.StartElement token; only return error if io.EOF is reached first (handles BOM).
2015.12.02: XML decoding/encoding that preserves original structure of document. See NewMapXmlSeq()
and mv.XmlSeq() / mv.XmlSeqIndent().
2015-05-20: New: mv.StringIndentNoTypeInfo().
Also, alphabetically sort map[string]interface{} values by key to prettify output for mv.Xml(),
mv.XmlIndent(), mv.StringIndent(), mv.StringIndentNoTypeInfo().
2014-11-09: IncludeTagSeqNum() adds "_seq" key with XML doc positional information.
(NOTE: PreserveXmlList() is similar and will be here soon.)
2014-09-18: inspired by NYTimes fork, added PrependAttrWithHyphen() to allow stripping hyphen from attribute tag.
2014-08-02: AnyXml() and AnyXmlIndent() will try to marshal arbitrary values to XML.
2014-04-28: ValuesForPath() and NewMap() now accept path with indexed array references.
<h4>Basic Unmarshal XML to map[string]interface{}</h4>
<pre>type Map map[string]interface{}</pre>
Create a `Map` value, 'm', from any `map[string]interface{}` value, 'v':
<pre>mv := Map(v)</pre>
Unmarshal / marshal XML as a `Map` value, 'm':
<pre>mv, err := NewMapXml(xmlValue) // unmarshal
xmlValue, err := mv.Xml() // marshal</pre>
Unmarshal XML from an `io.Reader` as a `Map` value, 'm':
<pre>mv, err := NewMapReader(xmlReader) // repeated calls, as with an os.File Reader, will process stream
mv, raw, err := NewMapReaderRaw(xmlReader) // 'raw' is the raw XML that was decoded</pre>
Marshal `Map` value, 'mv', to an XML Writer (`io.Writer`):
<pre>err := mv.XmlWriter(xmlWriter)
raw, err := mv.XmlWriterRaw(xmlWriter) // 'raw' is the raw XML that was written on xmlWriter</pre>
Also, for prettified output:
<pre>xmlValue, err := mv.XmlIndent(prefix, indent, ...)
err := mv.XmlIndentWriter(xmlWriter, prefix, indent, ...)
raw, err := mv.XmlIndentWriterRaw(xmlWriter, prefix, indent, ...)</pre>
Bulk process XML with error handling (note: handlers must return a boolean value):
<pre>err := HandleXmlReader(xmlReader, mapHandler(Map), errHandler(error))
err := HandleXmlReaderRaw(xmlReader, mapHandler(Map, []byte), errHandler(error, []byte))</pre>
Converting XML to JSON: see Examples for `NewMapXml` and `HandleXmlReader`.
There are comparable functions and methods for JSON processing.
Arbitrary structure values can be decoded to / encoded from `Map` values:
<pre>mv, err := NewMapStruct(structVal)
err := mv.Struct(structPointer)</pre>
<h4>Extract / modify Map values</h4>
To work with XML tag values, JSON or Map key values or structure field values, decode the XML, JSON
or structure to a `Map` value, 'm', or cast a `map[string]interface{}` value to a `Map` value, 'm', then:
<pre>paths := m.PathsForKey(key)
path := mv.PathForKeyShortest(key)
values, err := mv.ValuesForKey(key, subkeys)
values, err := mv.ValuesForPath(path, subkeys)
count, err := mv.UpdateValuesForPath(newVal, path, subkeys)</pre>
Get everything at once, irrespective of path depth:
<pre>leafnodes := mv.LeafNodes()
leafvalues := mv.LeafValues()</pre>
A new `Map` with whatever keys are desired can be created from the current `Map` and then encoded in XML
or JSON. (Note: keys can use dot-notation.)
<pre>newMap, err := mv.NewMap("oldKey_1:newKey_1", "oldKey_2:newKey_2", ..., "oldKey_N:newKey_N")
newXml, err := newMap.Xml() // for example
newJson, err := newMap.Json() // ditto</pre>
<h4>Usage</h4>
The package is fairly well [self-documented with examples](http://godoc.org/github.com/clbanning/mxj).
Also, the subdirectory "examples" contains a wide range of examples, several taken from golang-nuts discussions.
<h4>XML parsing conventions</h4>
Using NewMapXml()
- Attributes are parsed to `map[string]interface{}` values by prefixing a hyphen, `-`,
to the attribute label. (Unless overridden by `PrependAttrWithHyphen(false)` or
`SetAttrPrefix()`.)
- If the element is a simple element and has attributes, the element value
is given the key `#text` for its `map[string]interface{}` representation. (See
the 'atomFeedString.xml' test data, below.)
- XML comments, directives, and process instructions are ignored.
- If CoerceKeysToLower() has been called, then the resultant keys will be lower case.
Using NewMapXmlSeq()
- Attributes are parsed to `map["#attr"]map[<attr_label>]map[string]interface{}`values
where the `<attr_label>` value has "#text" and "#seq" keys - the "#text" key holds the
value for `<attr_label>`.
- All elements, except for the root, have a "#seq" key.
- Comments, directives, and process instructions are unmarshalled into the Map using the
keys "#comment", "#directive", and "#procinst", respectively. (See documentation for more
specifics.)
- Name space syntax is preserved:
- `<ns:key>something</ns.key>` parses to `map["ns:key"]interface{}{"something"}`
- `xmlns:ns="http://myns.com/ns"` parses to `map["xmlns:ns"]interface{}{"http://myns.com/ns"}`
Both
- By default, "Nan", "Inf", and "-Inf" values are not cast to float64. If you want them
to be cast, set a flag to cast them using CastNanInf(true).
<h4>XML encoding conventions</h4>
- 'nil' `Map` values, which may represent 'null' JSON values, are encoded as `<tag/>`.
NOTE: the operation is not symmetric as `<tag/>` elements are decoded as `tag:""` `Map` values,
which, then, encode in JSON as `"tag":""` values.
- ALSO: there is no guarantee that the encoded XML doc will be the same as the decoded one. (Go
randomizes the walk through map[string]interface{} values.) If you plan to re-encode the
Map value to XML and want the same sequencing of elements look at NewMapXmlSeq() and
m.XmlSeq() - these try to preserve the element sequencing but with added complexity when
working with the Map representation.
<h4>Running "go test"</h4>
Because there are no guarantees on the sequence map elements are retrieved, the tests have been
written for visual verification in most cases. One advantage is that you can easily use the
output from running "go test" as examples of calling the various functions and methods.
<h4>Motivation</h4>
I make extensive use of JSON for messaging and typically unmarshal the messages into
`map[string]interface{}` values. This is easily done using `json.Unmarshal` from the
standard Go libraries. Unfortunately, many legacy solutions use structured
XML messages; in those environments the applications would have to be refactored to
interoperate with my components.
The better solution is to just provide an alternative HTTP handler that receives
XML messages and parses it into a `map[string]interface{}` value and then reuse
all the JSON-based code. The Go `xml.Unmarshal()` function does not provide the same
option of unmarshaling XML messages into `map[string]interface{}` values. So I wrote
a couple of small functions to fill this gap and released them as the x2j package.
Over the next year and a half additional features were added, and the companion j2x
package was released to address XML encoding of arbitrary JSON and `map[string]interface{}`
values. As part of a refactoring of our production system and looking at how we had been
using the x2j and j2x packages we found that we rarely performed direct XML-to-JSON or
JSON-to_XML conversion and that working with the XML or JSON as `map[string]interface{}`
values was the primary value. Thus, everything was refactored into the mxj package.

View File

@ -0,0 +1,37 @@
package mxj
import "strings"
// Removes the path.
func (mv Map) Remove(path string) error {
m := map[string]interface{}(mv)
return remove(m, path)
}
func remove(m interface{}, path string) error {
val, err := prevValueByPath(m, path)
if err != nil {
return err
}
lastKey := lastKey(path)
delete(val, lastKey)
return nil
}
// returns the last key of the path.
// lastKey("a.b.c") would had returned "c"
func lastKey(path string) string {
keys := strings.Split(path, ".")
key := keys[len(keys)-1]
return key
}
// returns the path without the last key
// parentPath("a.b.c") whould had returned "a.b"
func parentPath(path string) string {
keys := strings.Split(path, ".")
parentPath := strings.Join(keys[0:len(keys)-1], ".")
return parentPath
}

View File

@ -0,0 +1,21 @@
package mxj
import (
"testing"
)
func TestRemove(t *testing.T) {
m := map[string]interface{}{
"Div": map[string]interface{}{
"Colour": "blue",
},
}
mv := Map(m)
err := mv.Remove("Div.Colour")
if err != nil {
t.Fatal(err)
}
if mv.Exists("Div.Colour") {
t.Fatal("removed key still remain")
}
}

View File

@ -0,0 +1,54 @@
package mxj
import (
"errors"
"strings"
)
// RenameKey renames a key in a Map.
// It works only for nested maps. It doesn't work for cases when it buried in a list.
func (mv Map) RenameKey(path string, newName string) error {
if !mv.Exists(path) {
return errors.New("RenameKey: path not found: " + path)
}
if mv.Exists(parentPath(path) + "." + newName) {
return errors.New("RenameKey: key already exists: " + newName)
}
m := map[string]interface{}(mv)
return renameKey(m, path, newName)
}
func renameKey(m interface{}, path string, newName string) error {
val, err := prevValueByPath(m, path)
if err != nil {
return err
}
oldName := lastKey(path)
val[newName] = val[oldName]
delete(val, oldName)
return nil
}
// returns a value which contains a last key in the path
// For example: prevValueByPath("a.b.c", {a{b{c: 3}}}) returns {c: 3}
func prevValueByPath(m interface{}, path string) (map[string]interface{}, error) {
keys := strings.Split(path, ".")
switch mValue := m.(type) {
case map[string]interface{}:
for key, value := range mValue {
if key == keys[0] {
if len(keys) == 1 {
return mValue, nil
} else {
// keep looking for the full path to the key
return prevValueByPath(value, strings.Join(keys[1:], "."))
}
}
}
}
return nil, errors.New("prevValueByPath: didn't find path " + path)
}

View File

@ -0,0 +1,40 @@
package mxj
import (
"testing"
)
func TestRenameKey(t *testing.T) {
m := map[string]interface{}{
"Div": map[string]interface{}{
"Colour": "blue",
"Width": "100%",
},
}
mv := Map(m)
err := mv.RenameKey("Div.Colour", "Color")
if err != nil {
t.Fatal(err)
}
values, err := mv.ValuesForPath("Div.Color")
if len(values) != 1 {
t.Fatal("didn't add the new key")
}
if values[0] != "blue" {
t.Fatal("value is changed")
}
values, err = mv.ValuesForPath("Div.Colour")
if len(values) > 0 {
t.Fatal("didn't removed the old key")
}
err = mv.RenameKey("not.existing.path", "newname")
if err == nil {
t.Fatal("should raise an error on a non existing path")
}
err = mv.RenameKey("Div.Color", "Width")
if err == nil {
t.Fatal("should raise an error if the newName already exists")
}
}

View File

@ -0,0 +1,52 @@
// seqnum.go
package mxj
import (
"fmt"
"testing"
)
var seqdata1 = []byte(`
<Obj c="la" x="dee" h="da">
<IntObj id="3"/>
<IntObj1 id="1"/>
<IntObj id="2"/>
</Obj>`)
var seqdata2 = []byte(`
<Obj c="la" x="dee" h="da">
<IntObj id="3"/>
<NewObj>
<id>1</id>
<StringObj>hello</StringObj>
<BoolObj>true</BoolObj>
</NewObj>
<IntObj id="2"/>
</Obj>`)
func TestSeqNumHeader(t *testing.T) {
fmt.Println("\n---------------- seqnum_test.go ...")
}
func TestSeqNum(t *testing.T) {
IncludeTagSeqNum(true)
m, err := NewMapXml(seqdata1, Cast)
if err != nil {
t.Fatal(err)
}
fmt.Printf("m1: %#v\n", m)
j, _ := m.JsonIndent("", " ")
fmt.Println(string(j))
m, err = NewMapXml(seqdata2, Cast)
if err != nil {
t.Fatal(err)
}
fmt.Printf("m2: %#v\n", m)
j, _ = m.JsonIndent("", " ")
fmt.Println(string(j))
IncludeTagSeqNum(false)
}

View File

@ -0,0 +1,26 @@
package mxj
import (
"strings"
)
// Sets the value for the path
func (mv Map) SetValueForPath(value interface{}, path string) error {
pathAry := strings.Split(path, ".")
parentPathAry := pathAry[0 : len(pathAry)-1]
parentPath := strings.Join(parentPathAry, ".")
val, err := mv.ValueForPath(parentPath)
if err != nil {
return err
}
if val == nil {
return nil // we just ignore the request if there's no val
}
key := pathAry[len(pathAry)-1]
cVal := val.(map[string]interface{})
cVal[key] = value
return nil
}

View File

@ -0,0 +1,43 @@
package mxj
import (
"testing"
)
func TestSetValueForPath(t *testing.T) {
m := map[string]interface{}{
"Div": map[string]interface{}{
"Colour": "blue",
"Font": map[string]interface{}{
"Family": "sans",
},
},
}
mv := Map(m)
// testing setting a new key
err := mv.SetValueForPath("big", "Div.Font.Size")
if err != nil {
t.Fatal(err)
}
val, err := mv.ValueForPathString("Div.Font.Size")
if err != nil {
t.Fatal(err)
}
if val != "big" {
t.Fatal("key's value hasn't changed")
}
// testing setting a new value to en existing key
err = mv.SetValueForPath("red", "Div.Colour")
if err != nil {
t.Fatal(err)
}
val, err = mv.ValueForPathString("Div.Colour")
if err != nil {
t.Fatal(err)
}
if val != "red" {
t.Fatal("existig key's value hasn't changed")
}
}

View File

@ -0,0 +1,20 @@
package mxj
// Per: https://github.com/clbanning/mxj/issues/37#issuecomment-278651862
var fieldSep string = ":"
// SetFieldSeparator changes the default field separator, ":", for the
// newVal argument in mv.UpdateValuesForPath and the optional 'subkey' arguments
// in mv.ValuesForKey and mv.ValuesForPath.
//
// E.g., if the newVal value is "http://blah/blah", setting the field separator
// to "|" will allow the newVal specification, "<key>|http://blah/blah" to parse
// properly. If called with no argument or an empty string value, the field
// separator is set to the default, ":".
func SetFieldSeparator(s ...string) {
if len(s) == 0 || s[0] == "" {
fieldSep = ":" // the default
return
}
fieldSep = s[0]
}

View File

@ -0,0 +1,69 @@
package mxj
import (
"fmt"
"testing"
)
func TestStakeCase(t *testing.T) {
PrependAttrWithHyphen(true)
fmt.Println("\n----------- TestSnakeCase")
CoerceKeysToSnakeCase()
defer CoerceKeysToSnakeCase()
data1 := `<xml-rpc><element-one attr-1="an attribute">something</element-one></xml-rpc>`
data2 := `<xml_rpc><element_one attr_1="an attribute">something</element_one></xml_rpc>`
m, err := NewMapXml([]byte(data1))
if err != nil {
t.Fatal(err)
}
x, err := m.Xml()
if err != nil {
t.Fatal(err)
}
if string(x) != data2 {
t.Fatal(string(x), "!=", data2)
}
// Use-case from: https://github.com/clbanning/mxj/pull/33#issuecomment-273724506
data1 = `<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:junos="http://xml.juniper.net/junos/11.2R4/junos" message-id="97741fa3-99e8-46ba-b103-bab6b459d884">
<software-information>
<host-name>srx100</host-name>
<product-model>srx100b</product-model>
<product-name>srx100b</product-name>
<jsr/>
<package-information>
<name>junos</name>
<comment>JUNOS Software Release [11.2R4.3]</comment>
</package-information>
</software-information>
</rpc-reply>`
data2 = `<rpc_reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:junos="http://xml.juniper.net/junos/11.2R4/junos" message_id="97741fa3-99e8-46ba-b103-bab6b459d884">
<software_information>
<host_name>srx100</host_name>
<product_model>srx100b</product_model>
<product_name>srx100b</product_name>
<jsr/>
<package_information>
<name>junos</name>
<comment>JUNOS Software Release [11.2R4.3]</comment>
</package_information>
</software_information>
</rpc_reply>`
m, err = NewMapXmlSeq([]byte(data1))
if err != nil {
t.Fatal(err)
}
x, err = m.XmlSeqIndent("", "")
if err != nil {
t.Fatal(err)
}
if string(x) != data2 {
t.Fatal(string(x), "!=", data2)
}
}

View File

@ -0,0 +1,29 @@
<msg mtype="alert" mpriority="1">
<text>help me!</text>
<song title="A Long Time" author="Mayer Hawthorne">
<verses>
<verse name="verse 1" no="1">
<line no="1">Henry was a renegade</line>
<line no="2">Didn't like to play it safe</line>
<line no="3">One component at a time</line>
<line no="4">There's got to be a better way</line>
<line no="5">Oh, people came from miles around</line>
<line no="6">Searching for a steady job</line>
<line no="7">Welcome to the Motor Town</line>
<line no="8">Booming like an atom bomb</line>
</verse>
<verse name="verse 2" no="2">
<line no="1">Oh, Henry was the end of the story</line>
<line no="2">Then everything went wrong</line>
<line no="3">And we'll return it to its former glory</line>
<line no="4">But it just takes so long</line>
</verse>
</verses>
<chorus>
<line no="1">It's going to take a long time</line>
<line no="2">It's going to take it, but we'll make it one day</line>
<line no="3">It's going to take a long time</line>
<line no="4">It's going to take it, but we'll make it one day</line>
</chorus>
</song>
</msg>

View File

@ -0,0 +1,30 @@
// Copyright 2016 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
// strict.go actually addresses setting xml.Decoder attribute
// values. This'll let you parse non-standard XML.
package mxj
import (
"encoding/xml"
)
// CustomDecoder can be used to specify xml.Decoder attribute
// values, e.g., Strict:false, to be used. By default CustomDecoder
// is nil. If CustomeDecoder != nil, then mxj.XmlCharsetReader variable is
// ignored and must be set as part of the CustomDecoder value, if needed.
// Usage:
// mxj.CustomDecoder = &xml.Decoder{Strict:false}
var CustomDecoder *xml.Decoder
// useCustomDecoder copy over public attributes from customDecoder
func useCustomDecoder(d *xml.Decoder) {
d.Strict = CustomDecoder.Strict
d.AutoClose = CustomDecoder.AutoClose
d.Entity = CustomDecoder.Entity
d.CharsetReader = CustomDecoder.CharsetReader
d.DefaultSpace = CustomDecoder.DefaultSpace
}

View File

@ -0,0 +1,48 @@
package mxj
import (
"encoding/xml"
"fmt"
"testing"
)
func TestStrictModeXml(t *testing.T) {
fmt.Println("----------------- TestStrictModeXml ...")
data := []byte(`<document> <name>Bill & Hallett</name> <salute>Duc &amp; 123xx</salute> <goes_by/> <lang>E</lang> </document>`)
CustomDecoder = &xml.Decoder{Strict:false}
m, err := NewMapXml(data)
if err != nil {
t.Fatal(err)
}
fmt.Println("m:",m)
}
func TestStrictModeXmlSeq(t *testing.T) {
fmt.Println("----------------- TestStrictModeXmlSeq ...")
data := []byte(`<document> <name>Bill & Hallett</name> <salute>Duc &amp; 123xx</salute> <goes_by/> <lang>E</lang> </document>`)
CustomDecoder = &xml.Decoder{Strict:false}
m, err := NewMapXmlSeq(data)
if err != nil {
t.Fatal(err)
}
fmt.Println("m:",m)
}
func TestStrictModeFail(t *testing.T) {
fmt.Println("----------------- TestStrictFail ...")
data := []byte(`<document> <name>Bill & Hallett</name> <salute>Duc &amp; 123xx</salute> <goes_by/> <lang>E</lang> </document>`)
CustomDecoder = nil
_, err := NewMapXml(data)
if err == nil {
t.Fatal("error not caught: NewMapXml")
}
_, err = NewMapXmlSeq(data)
if err == nil {
t.Fatal("error not caught: NewMapXmlSeq")
}
fmt.Println("OK")
}

View File

@ -0,0 +1,54 @@
// Copyright 2012-2017 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
package mxj
import (
"encoding/json"
"errors"
"reflect"
// "gitee.com/johng/gf/third/github.com/fatih/structs"
)
// Create a new Map value from a structure. Error returned if argument is not a structure.
// Only public structure fields are decoded in the Map value. See github.com/fatih/structs#Map
// for handling of "structs" tags.
// DEPRECATED - import github.com/fatih/structs and cast result of structs.Map to mxj.Map.
// import "gitee.com/johng/gf/third/github.com/fatih/structs"
// ...
// sm, err := structs.Map(<some struct>)
// if err != nil {
// // handle error
// }
// m := mxj.Map(sm)
// Alernatively uncomment the old source and import in struct.go.
func NewMapStruct(structVal interface{}) (Map, error) {
return nil, errors.New("deprecated - see package documentation")
/*
if !structs.IsStruct(structVal) {
return nil, errors.New("NewMapStruct() error: argument is not type Struct")
}
return structs.Map(structVal), nil
*/
}
// Marshal a map[string]interface{} into a structure referenced by 'structPtr'. Error returned
// if argument is not a pointer or if json.Unmarshal returns an error.
// json.Unmarshal structure encoding rules are followed to encode public structure fields.
func (mv Map) Struct(structPtr interface{}) error {
// should check that we're getting a pointer.
if reflect.ValueOf(structPtr).Kind() != reflect.Ptr {
return errors.New("mv.Struct() error: argument is not type Ptr")
}
m := map[string]interface{}(mv)
j, err := json.Marshal(m)
if err != nil {
return err
}
return json.Unmarshal(j, structPtr)
}

View File

@ -0,0 +1,86 @@
package mxj
import (
"fmt"
"testing"
)
func TestStructHeader(t *testing.T) {
fmt.Println("\n---------------- struct_test.go ...")
}
/*
func TestNewMapStruct(t *testing.T) {
type str struct {
IntVal int `json:"int"`
StrVal string `json:"str"`
FloatVal float64 `json:"float"`
BoolVal bool `json:"bool"`
private string
}
s := str{IntVal: 4, StrVal: "now's the time", FloatVal: 3.14159, BoolVal: true, private: "It's my party"}
m, merr := NewMapStruct(s)
if merr != nil {
t.Fatal("merr:", merr.Error())
}
fmt.Printf("NewMapStruct, s: %#v\n", s)
fmt.Printf("NewMapStruct, m: %#v\n", m)
m, merr = NewMapStruct(s)
if merr != nil {
t.Fatal("merr:", merr.Error())
}
fmt.Printf("NewMapStruct, s: %#v\n", s)
fmt.Printf("NewMapStruct, m: %#v\n", m)
}
func TestNewMapStructError(t *testing.T) {
var s string
_, merr := NewMapStruct(s)
if merr == nil {
t.Fatal("NewMapStructError, merr is nil")
}
fmt.Println("NewMapStructError, merr:", merr.Error())
}
*/
func TestStruct(t *testing.T) {
type str struct {
IntVal int `json:"int"`
StrVal string `json:"str"`
FloatVal float64 `json:"float"`
BoolVal bool `json:"bool"`
private string
}
var s str
m := Map{"int": 4, "str": "now's the time", "float": 3.14159, "bool": true, "private": "Somewhere over the rainbow"}
mverr := m.Struct(&s)
if mverr != nil {
t.Fatal("mverr:", mverr.Error())
}
fmt.Printf("Struct, m: %#v\n", m)
fmt.Printf("Struct, s: %#v\n", s)
}
func TestStructError(t *testing.T) {
type str struct {
IntVal int `json:"int"`
StrVal string `json:"str"`
FloatVal float64 `json:"float"`
BoolVal bool `json:"bool"`
}
var s str
mv := Map{"int": 4, "str": "now's the time", "float": 3.14159, "bool": true}
mverr := mv.Struct(s)
if mverr == nil {
t.Fatal("StructError, no error returned")
}
fmt.Println("StructError, mverr:", mverr.Error())
}

View File

@ -0,0 +1,256 @@
// Copyright 2012-2014, 2017 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
// updatevalues.go - modify a value based on path and possibly sub-keys
// TODO(clb): handle simple elements with attributes and NewMapXmlSeq Map values.
package mxj
import (
"fmt"
"strconv"
"strings"
)
// Update value based on path and possible sub-key values.
// A count of the number of values changed and any error are returned.
// If the count == 0, then no path (and subkeys) matched.
// 'newVal' can be a Map or map[string]interface{} value with a single 'key' that is the key to be modified
// or a string value "key:value[:type]" where type is "bool" or "num" to cast the value.
// 'path' is dot-notation list of keys to traverse; last key in path can be newVal key
// NOTE: 'path' spec does not currently support indexed array references.
// 'subkeys' are "key:value[:type]" entries that must match for path node
// The subkey can be wildcarded - "key:*" - to require that it's there with some value.
// If a subkey is preceeded with the '!' character, the key:value[:type] entry is treated as an
// exclusion critera - e.g., "!author:William T. Gaddis".
//
// NOTES:
// 1. Simple elements with attributes need a path terminated as ".#text" to modify the actual value.
// 2. Values in Maps created using NewMapXmlSeq are map[string]interface{} values with a "#text" key.
// 3. If values in 'newVal' or 'subkeys' args contain ":", use SetFieldSeparator to an unused symbol,
// perhaps "|".
func (mv Map) UpdateValuesForPath(newVal interface{}, path string, subkeys ...string) (int, error) {
m := map[string]interface{}(mv)
// extract the subkeys
var subKeyMap map[string]interface{}
if len(subkeys) > 0 {
var err error
subKeyMap, err = getSubKeyMap(subkeys...)
if err != nil {
return 0, err
}
}
// extract key and value from newVal
var key string
var val interface{}
switch newVal.(type) {
case map[string]interface{}, Map:
switch newVal.(type) { // "fallthrough is not permitted in type switch" (Spec)
case Map:
newVal = newVal.(Map).Old()
}
if len(newVal.(map[string]interface{})) != 1 {
return 0, fmt.Errorf("newVal map can only have len == 1 - %+v", newVal)
}
for key, val = range newVal.(map[string]interface{}) {
}
case string: // split it as a key:value pair
ss := strings.Split(newVal.(string), fieldSep)
n := len(ss)
if n < 2 || n > 3 {
return 0, fmt.Errorf("unknown newVal spec - %+v", newVal)
}
key = ss[0]
if n == 2 {
val = interface{}(ss[1])
} else if n == 3 {
switch ss[2] {
case "bool", "boolean":
nv, err := strconv.ParseBool(ss[1])
if err != nil {
return 0, fmt.Errorf("can't convert newVal to bool - %+v", newVal)
}
val = interface{}(nv)
case "num", "numeric", "float", "int":
nv, err := strconv.ParseFloat(ss[1], 64)
if err != nil {
return 0, fmt.Errorf("can't convert newVal to float64 - %+v", newVal)
}
val = interface{}(nv)
default:
return 0, fmt.Errorf("unknown type for newVal value - %+v", newVal)
}
}
default:
return 0, fmt.Errorf("invalid newVal type - %+v", newVal)
}
// parse path
keys := strings.Split(path, ".")
var count int
updateValuesForKeyPath(key, val, m, keys, subKeyMap, &count)
return count, nil
}
// navigate the path
func updateValuesForKeyPath(key string, value interface{}, m interface{}, keys []string, subkeys map[string]interface{}, cnt *int) {
// ----- at end node: looking at possible node to get 'key' ----
if len(keys) == 1 {
updateValue(key, value, m, keys[0], subkeys, cnt)
return
}
// ----- here we are navigating the path thru the penultimate node --------
// key of interest is keys[0] - the next in the path
switch keys[0] {
case "*": // wildcard - scan all values
switch m.(type) {
case map[string]interface{}:
for _, v := range m.(map[string]interface{}) {
updateValuesForKeyPath(key, value, v, keys[1:], subkeys, cnt)
}
case []interface{}:
for _, v := range m.([]interface{}) {
switch v.(type) {
// flatten out a list of maps - keys are processed
case map[string]interface{}:
for _, vv := range v.(map[string]interface{}) {
updateValuesForKeyPath(key, value, vv, keys[1:], subkeys, cnt)
}
default:
updateValuesForKeyPath(key, value, v, keys[1:], subkeys, cnt)
}
}
}
default: // key - must be map[string]interface{}
switch m.(type) {
case map[string]interface{}:
if v, ok := m.(map[string]interface{})[keys[0]]; ok {
updateValuesForKeyPath(key, value, v, keys[1:], subkeys, cnt)
}
case []interface{}: // may be buried in list
for _, v := range m.([]interface{}) {
switch v.(type) {
case map[string]interface{}:
if vv, ok := v.(map[string]interface{})[keys[0]]; ok {
updateValuesForKeyPath(key, value, vv, keys[1:], subkeys, cnt)
}
}
}
}
}
}
// change value if key and subkeys are present
func updateValue(key string, value interface{}, m interface{}, keys0 string, subkeys map[string]interface{}, cnt *int) {
// there are two possible options for the value of 'keys0': map[string]interface, []interface{}
// and 'key' is a key in the map or is a key in a map in a list.
switch m.(type) {
case map[string]interface{}: // gotta have the last key
if keys0 == "*" {
for k := range m.(map[string]interface{}) {
updateValue(key, value, m, k, subkeys, cnt)
}
return
}
endVal, _ := m.(map[string]interface{})[keys0]
// if newV key is the end of path, replace the value for path-end
// may be []interface{} - means replace just an entry w/ subkeys
// otherwise replace the keys0 value if subkeys are there
// NOTE: this will replace the subkeys, also
if key == keys0 {
switch endVal.(type) {
case map[string]interface{}:
if hasSubKeys(m, subkeys) {
(m.(map[string]interface{}))[keys0] = value
(*cnt)++
}
case []interface{}:
// without subkeys can't select list member to modify
// so key:value spec is it ...
if hasSubKeys(m, subkeys) {
(m.(map[string]interface{}))[keys0] = value
(*cnt)++
break
}
nv := make([]interface{}, 0)
var valmodified bool
for _, v := range endVal.([]interface{}) {
// check entry subkeys
if hasSubKeys(v, subkeys) {
// replace v with value
nv = append(nv, value)
valmodified = true
(*cnt)++
continue
}
nv = append(nv, v)
}
if valmodified {
(m.(map[string]interface{}))[keys0] = interface{}(nv)
}
default: // anything else is a strict replacement
if hasSubKeys(m, subkeys) {
(m.(map[string]interface{}))[keys0] = value
(*cnt)++
}
}
return
}
// so value is for an element of endVal
// if endVal is a map then 'key' must be there w/ subkeys
// if endVal is a list then 'key' must be in a list member w/ subkeys
switch endVal.(type) {
case map[string]interface{}:
if !hasSubKeys(endVal, subkeys) {
return
}
if _, ok := (endVal.(map[string]interface{}))[key]; ok {
(endVal.(map[string]interface{}))[key] = value
(*cnt)++
}
case []interface{}: // keys0 points to a list, check subkeys
for _, v := range endVal.([]interface{}) {
// got to be a map so we can replace value for 'key'
vv, vok := v.(map[string]interface{})
if !vok {
continue
}
if _, ok := vv[key]; !ok {
continue
}
if !hasSubKeys(vv, subkeys) {
continue
}
vv[key] = value
(*cnt)++
}
}
case []interface{}: // key may be in a list member
// don't need to handle keys0 == "*"; we're looking at everything, anyway.
for _, v := range m.([]interface{}) {
// only map values - we're looking for 'key'
mm, ok := v.(map[string]interface{})
if !ok {
continue
}
if _, ok := mm[key]; !ok {
continue
}
if !hasSubKeys(mm, subkeys) {
continue
}
mm[key] = value
(*cnt)++
}
}
// return
}

View File

@ -0,0 +1,187 @@
// modifyvalues_test.go - test keyvalues.go methods
package mxj
import (
"fmt"
"testing"
)
func TestUVHeader(t *testing.T) {
fmt.Println("\n---------------- updatevalues_test.go ...")
}
func TestUpdateValuesForPath_Author(t *testing.T) {
m, merr := NewMapXml(doc1)
if merr != nil {
t.Fatal("merr:", merr.Error())
}
fmt.Println("m:", m)
ss, _ := m.ValuesForPath("doc.books.book.author")
for _, v := range ss {
fmt.Println("v:", v)
}
fmt.Println("m.UpdateValuesForPath(\"author:NoName\", \"doc.books.book.author\")")
n, err := m.UpdateValuesForPath("author:NoName", "doc.books.book.author")
if err != nil {
t.Fatal("err:", err.Error())
}
fmt.Println(n, "updates")
ss, _ = m.ValuesForPath("doc.books.book.author")
for _, v := range ss {
fmt.Println("v:", v)
}
fmt.Println("m.UpdateValuesForPath(\"author:William Gadddis\", \"doc.books.book.author\", \"title:The Recognitions\")")
n, err = m.UpdateValuesForPath("author:William Gadddis", "doc.books.book.author", "title:The Recognitions")
o, _ := m.UpdateValuesForPath("author:Austin Tappen Wright", "doc.books.book", "title:Islandia")
p, _ := m.UpdateValuesForPath("author:John Hawkes", "doc.books.book", "title:The Beetle Leg")
q, _ := m.UpdateValuesForPath("author:T. E. Porter", "doc.books.book", "title:King's Day")
if err != nil {
t.Fatal("err:", err.Error())
}
fmt.Println(n+o+p+q, "updates")
ss, _ = m.ValuesForPath("doc.books.book.author")
for _, v := range ss {
fmt.Println("v:", v)
}
fmt.Println("m.UpdateValuesForPath(\"author:William T. Gaddis\", \"doc.books.book.*\", \"title:The Recognitions\")")
n, _ = m.UpdateValuesForPath("author:William T. Gaddis", "doc.books.book.*", "title:The Recognitions")
fmt.Println(n, "updates")
ss, _ = m.ValuesForPath("doc.books.book.author")
for _, v := range ss {
fmt.Println("v:", v)
}
fmt.Println("m.UpdateValuesForPath(\"title:The Cannibal\", \"doc.books.book.title\", \"author:John Hawkes\")")
n, _ = m.UpdateValuesForPath("title:The Cannibal", "doc.books.book.title", "author:John Hawkes")
o, _ = m.UpdateValuesForPath("review:A novel on his experiences in WWII.", "doc.books.book.review", "title:The Cannibal")
fmt.Println(n+o, "updates")
ss, _ = m.ValuesForPath("doc.books.book")
for _, v := range ss {
fmt.Println("v:", v)
}
fmt.Println("m.UpdateValuesForPath(\"books:\", \"doc.books\")")
n, _ = m.UpdateValuesForPath("books:", "doc.books")
fmt.Println(n, "updates")
fmt.Println("m:", m)
fmt.Println("m.UpdateValuesForPath(mm, \"*\")")
mm, _ := NewMapXml(doc1)
n, err = m.UpdateValuesForPath(mm, "*")
if err != nil {
t.Fatal("err:", err.Error())
}
fmt.Println(n, "updates")
fmt.Println("m:", m)
// ---------------------- newDoc
var newDoc = []byte(`<tag color="green" shape="square">simple element</tag>`)
m, merr = NewMapXml(newDoc)
if merr != nil {
t.Fatal("merr:", merr.Error())
}
fmt.Println("\nnewDoc:", string(newDoc))
fmt.Println("m:", m)
fmt.Println("m.UpdateValuesForPath(\"#text:maybe not so simple element\", \"tag\")")
n, _ = m.UpdateValuesForPath("#text:maybe not so simple element", "tag")
fmt.Println("n:", n, "m:", m)
fmt.Println("m.UpdateValuesForPath(\"#text:simple element again\", \"*\")")
n, _ = m.UpdateValuesForPath("#text:simple element again", "*")
fmt.Println("n:", n, "m:", m)
/*
fmt.Println("UpdateValuesForPath, doc.books.book, title:The Recognitions : NoBook")
n, err = m.UpdateValuesForPath("NoBook", "doc.books.book", "title:The Recognitions")
if err != nil {
t.Fatal("err:", err.Error())
}
fmt.Println(n, "updates")
ss, _ = m.ValuesForPath("doc.books.book")
for _, v := range ss {
fmt.Println("v:", v)
}
fmt.Println("UpdateValuesForPath, doc.books.book.title -seq=3: The Blood Oranges")
n, err = m.UpdateValuesForPath("The Blood Oranges", "doc.books.book.title", "-seq:3")
if err != nil {
t.Fatal("err:", err.Error())
}
fmt.Println(n, "updates")
ss, _ = m.ValuesForPath("doc.books.book.title")
for _, v := range ss {
fmt.Println("v:", v)
}
*/
}
var authorDoc = []byte(`
<biblio>
<author>
<name>William Gaddis</name>
<books>
<book>
<title>The Recognitions</title>
<date>1955</date>
<review>A novel that changed the face of American literature.</review>
</book>
<book>
<title>JR</title>
<date>1975</date>
<review>Winner of National Book Award for Fiction.</review>
</book>
</books>
</author>
<author>
<name>John Hawkes</name>
<books>
<book>
<title>The Cannibal</title>
<date>1949</date>
<review>A novel on his experiences in WWII.</review>
</book>
<book>
<title>The Beetle Leg</title>
<date>1951</date>
<review>A lyrical novel about the construction of Ft. Peck Dam in Montana.</review>
</book>
<book>
<title>The Blood Oranges</title>
<date>1970</date>
<review>Where everyone wants to vacation.</review>
</book>
</books>
</author>
</biblio>`)
func TestAuthorDoc(t *testing.T) {
m, merr := NewMapXml(authorDoc)
if merr != nil {
t.Fatal("merr:", merr.Error())
}
fmt.Println(m.StringIndent())
fmt.Println("m.UpdateValuesForPath(\"review:National Book Award winner.\", \"*.*.*.*\", \"title:JR\")")
n, _ := m.UpdateValuesForPath("review:National Book Award winner.", "*.*.*.*", "title:JR")
fmt.Println(n, "updates")
ss, _ := m.ValuesForPath("biblio.author", "name:William Gaddis")
for _, v := range ss {
fmt.Println("v:", v)
}
fmt.Println("m.UpdateValuesForPath(newVal, path, oldVal)")
path := m.PathForKeyShortest("date")
v, _ := m.ValuesForPath(path)
var counter int
for _, vv := range v {
oldVal := "date:" + vv.(string)
newVal := "date:" + vv.(string) + ":num"
n, _ = m.UpdateValuesForPath(newVal, path, oldVal)
counter += n
}
fmt.Println(counter, "updates")
fmt.Println(m.StringIndent())
}

View File

@ -0,0 +1,28 @@
Copyright (c) 2012-2016 Charles Banning <clbanning@gmail.com>.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,109 @@
x2j.go - Unmarshal arbitrary XML docs to map[string]interface{} or JSON and extract values (using wildcards, if necessary).
NOTICE:
26mar18: This is provided to aid transitioning applications using the "x2j" package to the "mxj" package.
USAGE
The package is fairly well self-documented. (http://godoc.org/github.com/clbanning/x2j)
The one really useful function is:
- Unmarshal(doc []byte, v interface{}) error
where v is a pointer to a variable of type 'map[string]interface{}', 'string', or
any other type supported by xml.Unmarshal().
To retrieve a value for specific tag use:
- DocValue(doc, path string, attrs ...string) (interface{},error)
- MapValue(m map[string]interface{}, path string, attr map[string]interface{}, recast ...bool) (interface{}, error)
The 'path' argument is a period-separated tag hierarchy - also known as dot-notation.
It is the program's responsibility to cast the returned value to the proper type; possible
types are the normal JSON unmarshaling types: string, float64, bool, []interface, map[string]interface{}.
To retrieve all values associated with a tag occurring anywhere in the XML document use:
- ValuesForTag(doc, tag string) ([]interface{}, error)
- ValuesForKey(m map[string]interface{}, key string) []interface{}
Demos: http://play.golang.org/p/m8zP-cpk0O
http://play.golang.org/p/cIteTS1iSg
http://play.golang.org/p/vd8pMiI21b
Returned values should be one of map[string]interface, []interface{}, or string.
All the values assocated with a tag-path that may include one or more wildcard characters -
'*' - can also be retrieved using:
- ValuesFromTagPath(doc, path string, getAttrs ...bool) ([]interface{}, error)
- ValuesFromKeyPath(map[string]interface{}, path string, getAttrs ...bool) []interface{}
Demos: http://play.golang.org/p/kUQnZ8VuhS
http://play.golang.org/p/l1aMHYtz7G
NOTE: care should be taken when using "*" at the end of a path - i.e., "books.book.*". See
the x2jpath_test.go case on how the wildcard returns all key values and collapses list values;
the same message structure can load a []interface{} or a map[string]interface{} (or an interface{})
value for a tag.
See the test cases in "x2jpath_test.go" and programs in "example" subdirectory for more.
XML PARSING CONVENTIONS
- Attributes are parsed to map[string]interface{} values by prefixing a hyphen, '-',
to the attribute label. [See https://godoc.org/github.com/clbanning/mxj#SetAttrPrefix for options.]
- If the element is a simple element and has attributes, the element value
is given the key '#text' for its map[string]interface{} representation. (See
the 'atomFeedString.xml' test data, below.)
BULK PROCESSING OF MESSAGE FILES
Sometime messages may be logged into files for transmission via FTP (e.g.) and subsequent
processing. You can use the bulk XML message processor to convert files of XML messages into
map[string]interface{} values with custom processing and error handler functions. See
the notes and test code for:
- XmlMsgsFromFile(fname string, phandler func(map[string]interface{}) bool, ehandler func(error) bool,recast ...bool) error
[See https://godoc.org/github.com/clbanning/mxj#HandleXmlReader for general version.]
IMPLEMENTATION NOTES
Nothing fancy here, just brute force.
- Use xml.Decoder to parse the XML doc and build a tree.
- Walk the tree and load values into a map[string]interface{} variable, 'm', as
appropriate.
- Use json.Marshaler to convert 'm' to JSON.
As for testing:
- Copy an XML doc into 'x2j_test.xml'.
- Run "go test" and you'll get a full dump.
("pathTestString.xml" and "atomFeedString.xml" are test data from "read_test.go"
in the encoding/xml directory of the standard package library.)
MOTIVATION
I make extensive use of JSON for messaging and typically unmarshal the messages into
map[string]interface{} variables. This is easily done using json.Unmarshal from the
standard Go libraries. Unfortunately, many legacy solutions use structured
XML messages; in those environments the applications would have to be refitted to
interoperate with my components.
The better solution is to just provide an alternative HTTP handler that receives
XML doc messages and parses it into a map[string]interface{} variable and then reuse
all the JSON-based code. The Go xml.Unmarshal() function does not provide the same
option of unmarshaling XML messages into map[string]interface{} variables. So I wrote
a couple of small functions to fill this gap.
Of course, once the XML doc was unmarshal'd into a map[string]interface{} variable it
was just a matter of calling json.Marshal() to provide it as a JSON string. Hence 'x2j'
rather than just 'x2m'.
USES
- putting a XML API on our message hub middleware (http://jsonhub.net)
- loading XML data into NoSQL database, such as, mongoDB

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us" updated="2009-10-04T01:35:58+00:00"><title>Code Review - My issues</title><link href="http://codereview.appspot.com/" rel="alternate"></link><link href="http://codereview.appspot.com/rss/mine/rsc" rel="self"></link><id>http://codereview.appspot.com/</id><author><name>rietveld&lt;&gt;</name></author><entry><title>rietveld: an attempt at pubsubhubbub
</title><link href="http://codereview.appspot.com/126085" rel="alternate"></link><updated>2009-10-04T01:35:58+00:00</updated><author><name>email-address-removed</name></author><id>urn:md5:134d9179c41f806be79b3a5f7877d19a</id><summary type="html">
An attempt at adding pubsubhubbub support to Rietveld.
http://code.google.com/p/pubsubhubbub
http://code.google.com/p/rietveld/issues/detail?id=155
The server side of the protocol is trivial:
1. add a &amp;lt;link rel=&amp;quot;hub&amp;quot; href=&amp;quot;hub-server&amp;quot;&amp;gt; tag to all
feeds that will be pubsubhubbubbed.
2. every time one of those feeds changes, tell the hub
with a simple POST request.
I have tested this by adding debug prints to a local hub
server and checking that the server got the right publish
requests.
I can&amp;#39;t quite get the server to work, but I think the bug
is not in my code. I think that the server expects to be
able to grab the feed and see the feed&amp;#39;s actual URL in
the link rel=&amp;quot;self&amp;quot;, but the default value for that drops
the :port from the URL, and I cannot for the life of me
figure out how to get the Atom generator deep inside
django not to do that, or even where it is doing that,
or even what code is running to generate the Atom feed.
(I thought I knew but I added some assert False statements
and it kept running!)
Ignoring that particular problem, I would appreciate
feedback on the right way to get the two values at
the top of feeds.py marked NOTE(rsc).
</summary></entry><entry><title>rietveld: correct tab handling
</title><link href="http://codereview.appspot.com/124106" rel="alternate"></link><updated>2009-10-03T23:02:17+00:00</updated><author><name>email-address-removed</name></author><id>urn:md5:0a2a4f19bb815101f0ba2904aed7c35a</id><summary type="html">
This fixes the buggy tab rendering that can be seen at
http://codereview.appspot.com/116075/diff/1/2
The fundamental problem was that the tab code was
not being told what column the text began in, so it
didn&amp;#39;t know where to put the tab stops. Another problem
was that some of the code assumed that string byte
offsets were the same as column offsets, which is only
true if there are no tabs.
In the process of fixing this, I cleaned up the arguments
to Fold and ExpandTabs and renamed them Break and
_ExpandTabs so that I could be sure that I found all the
call sites. I also wanted to verify that ExpandTabs was
not being used from outside intra_region_diff.py.
</summary></entry></feed> `

View File

@ -0,0 +1,56 @@
package x2j
import (
"encoding/json"
"fmt"
"testing"
)
func TestGoofy(t *testing.T) {
var doc = `<xml><tag one="1" pi="3.1415962535" bool="true"/><tagJR key="value"/></xml>`
type goofy struct {
S string
Sp *string
}
g := new(goofy)
g.S = "Now is the time for"
tmp := "all good men to come to"
g.Sp = &tmp
m, err := DocToMap(doc)
if err != nil {
fmt.Println("err:",err.Error())
return
}
m["goofyVal"] = interface{}(g)
m["byteVal"] = interface{}([]byte(`the aid of their country`))
m["nilVal"] = interface{}(nil)
fmt.Println("\nTestGoofy ... MapToDoc:",m)
var v []byte
v,err = json.Marshal(m)
if err != nil {
fmt.Println("err:",err.Error())
}
fmt.Println("v:",string(v))
type goofier struct {
G *goofy
B []byte
N *string
}
gg := new(goofier)
gg.G = g
gg.B = []byte(`the tree of freedom must periodically be`)
gg.N = nil
m["goofierVal"] = interface{}(gg)
fmt.Println("\nTestGoofier ... MapToDoc:",m)
v,err = json.Marshal(m)
if err != nil {
fmt.Println("err:",err.Error())
}
fmt.Println("v:",string(v))
}

View File

@ -0,0 +1,20 @@
<Result>
<Before>1</Before>
<Items>
<Item1>
<Value>A</Value>
</Item1>
<Item2>
<Value>B</Value>
</Item2>
<Item1>
<Value>C</Value>
<Value>D</Value>
</Item1>
<_>
<Value>E</Value>
</_>
</Items>
<After>2</After>
</Result>

View File

@ -0,0 +1,86 @@
// Copyright 2012-2018 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
// io.Reader --> map[string]interface{} or JSON string
// nothing magic - just implements generic Go case
package x2j
import (
"encoding/json"
"io"
"gitee.com/johng/gf/third/github.com/clbanning/mxj"
)
// ToMap() - parse a XML io.Reader to a map[string]interface{}
func ToMap(rdr io.Reader, recast ...bool) (map[string]interface{}, error) {
var r bool
if len(recast) == 1 {
r = recast[0]
}
return mxj.NewMapXmlReader(rdr, r)
}
// ToJson() - parse a XML io.Reader to a JSON string
func ToJson(rdr io.Reader, recast ...bool) (string, error) {
var r bool
if len(recast) == 1 {
r = recast[0]
}
m, merr := mxj.NewMapXmlReader(rdr, r)
if m == nil || merr != nil {
return "", merr
}
b, berr := json.Marshal(m)
if berr != nil {
return "", berr
}
return string(b), nil
}
// ToJsonIndent - the pretty form of ReaderToJson
func ToJsonIndent(rdr io.Reader, recast ...bool) (string, error) {
var r bool
if len(recast) == 1 {
r = recast[0]
}
m, merr := mxj.NewMapXmlReader(rdr, r)
if m == nil || merr != nil {
return "", merr
}
b, berr := json.MarshalIndent(m, "", " ")
if berr != nil {
return "", berr
}
// NOTE: don't have to worry about safe JSON marshaling with json.Marshal, since '<' and '>" are reservedin XML.
return string(b), nil
}
// ReaderValuesFromTagPath - io.Reader version of ValuesFromTagPath()
func ReaderValuesFromTagPath(rdr io.Reader, path string, getAttrs ...bool) ([]interface{}, error) {
var a bool
if len(getAttrs) == 1 {
a = getAttrs[0]
}
m, err := mxj.NewMapXmlReader(rdr)
if err != nil {
return nil, err
}
return ValuesFromKeyPath(m, path, a), nil
}
// ReaderValuesForTag - io.Reader version of ValuesForTag()
func ReaderValuesForTag(rdr io.Reader, tag string) ([]interface{}, error) {
m, err := mxj.NewMapXmlReader(rdr)
if err != nil {
return nil, err
}
return ValuesForKey(m, tag), nil
}

View File

@ -0,0 +1,76 @@
package x2j
import (
"bytes"
"fmt"
"testing"
)
var doc = `<entry><vars><foo>bar</foo><foo2><hello>world</hello></foo2></vars></entry>`
func TestToMap(t *testing.T) {
fmt.Println("\nToMap - Read doc:",doc)
rdr := bytes.NewBufferString(doc)
m,err := ToMap(rdr)
if err != nil {
fmt.Println("err:",err.Error())
}
fmt.Println(WriteMap(m))
}
func TestToJson(t *testing.T) {
fmt.Println("\nToJson - Read doc:",doc)
rdr := bytes.NewBufferString(doc)
s,err := ToJson(rdr)
if err != nil {
fmt.Println("err:",err.Error())
}
fmt.Println("json:",s)
}
func TestToJsonIndent(t *testing.T) {
fmt.Println("\nToJsonIndent - Read doc:",doc)
rdr := bytes.NewBufferString(doc)
s,err := ToJsonIndent(rdr)
if err != nil {
fmt.Println("err:",err.Error())
}
fmt.Println("json:",s)
}
func TestBulkParser(t *testing.T) {
s := doc + `<this><is>an</err>`+ doc
fmt.Println("\nBulkParser (with error) - Read doc:",s)
rdr := bytes.NewBufferString(s)
err := XmlMsgsFromReader(rdr,phandler,ehandler)
if err != nil {
fmt.Println("reader terminated:",err.Error())
}
}
func phandler(m map[string]interface{}) bool {
fmt.Println("phandler m:",m)
return true
}
func ehandler(err error) bool {
fmt.Println("ehandler err:",err.Error())
return true
}
func TestBulkParserToJson(t *testing.T) {
s := doc + `<this><is>an</err>`+ doc
fmt.Println("\nBulkParser (with error) - Read doc:",s)
rdr := bytes.NewBufferString(s)
err := XmlMsgsFromReaderAsJson(rdr,phandlerj,ehandler)
if err != nil {
fmt.Println("reader terminated:",err.Error())
}
}
func phandlerj(s string) bool {
fmt.Println("phandlerj s:",s)
return true
}

View File

@ -0,0 +1,29 @@
<msg mtype="alert" mpriority="1">
<text>help me!</text>
<song title="A Long Time" author="Mayer Hawthorne">
<verses>
<verse name="verse 1" no="1">
<line no="1">Henry was a renegade</line>
<line no="2">Didn't like to play it safe</line>
<line no="3">One component at a time</line>
<line no="4">There's got to be a better way</line>
<line no="5">Oh, people came from miles around</line>
<line no="6">Searching for a steady job</line>
<line no="7">Welcome to the Motor Town</line>
<line no="8">Booming like an atom bomb</line>
</verse>
<verse name="verse 2" no="2">
<line no="1">Oh, Henry was the end of the story</line>
<line no="2">Then everything went wrong</line>
<line no="3">And we'll return it to its former glory</line>
<line no="4">But it just takes so long</line>
</verse>
</verses>
<chorus>
<line no="1">It's going to take a long time</line>
<line no="2">It's going to take it, but we'll make it one day</line>
<line no="3">It's going to take a long time</line>
<line no="4">It's going to take it, but we'll make it one day</line>
</chorus>
</song>
</msg>

View File

@ -0,0 +1,455 @@
// Unmarshal arbitrary XML docs to map[string]interface{} or JSON and extract values (using wildcards, if necessary).
// Copyright 2012-2018 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
/*
Unmarshal dynamic / arbitrary XML docs and extract values (using wildcards, if necessary).
THIS IS ONLY PROVIDED TO FACILIATE MIGRATING TO "mxj" PACKAGE FROM "x2j" PACKAGE.
NOTICE: 03mar18, package mostly replicates github.com/clbanning/x2j using github.com/clbanning/mxj
(Note: there is no concept of Node or Tree; only direct decoding to map[string]interface{}.)
One useful function is:
- Unmarshal(doc []byte, v interface{}) error
where v is a pointer to a variable of type 'map[string]interface{}', 'string', or
any other type supported by xml.Unmarshal().
To retrieve a value for specific tag use:
- DocValue(doc, path string, attrs ...string) (interface{},error)
- MapValue(m map[string]interface{}, path string, attr map[string]interface{}, recast ...bool) (interface{}, error)
The 'path' argument is a period-separated tag hierarchy - also known as dot-notation.
It is the program's responsibility to cast the returned value to the proper type; possible
types are the normal JSON unmarshaling types: string, float64, bool, []interface, map[string]interface{}.
To retrieve all values associated with a tag occurring anywhere in the XML document use:
- ValuesForTag(doc, tag string) ([]interface{}, error)
- ValuesForKey(m map[string]interface{}, key string) []interface{}
Demos: http://play.golang.org/p/m8zP-cpk0O
http://play.golang.org/p/cIteTS1iSg
http://play.golang.org/p/vd8pMiI21b
Returned values should be one of map[string]interface, []interface{}, or string.
All the values assocated with a tag-path that may include one or more wildcard characters -
'*' - can also be retrieved using:
- ValuesFromTagPath(doc, path string, getAttrs ...bool) ([]interface{}, error)
- ValuesFromKeyPath(map[string]interface{}, path string, getAttrs ...bool) []interface{}
Demos: http://play.golang.org/p/kUQnZ8VuhS
http://play.golang.org/p/l1aMHYtz7G
NOTE: care should be taken when using "*" at the end of a path - i.e., "books.book.*". See
the x2jpath_test.go case on how the wildcard returns all key values and collapses list values;
the same message structure can load a []interface{} or a map[string]interface{} (or an interface{})
value for a tag.
See the test cases in "x2jpath_test.go" and programs in "example" subdirectory for more.
XML PARSING CONVENTIONS
- Attributes are parsed to map[string]interface{} values by prefixing a hyphen, '-',
to the attribute label.
- If the element is a simple element and has attributes, the element value
is given the key '#text' for its map[string]interface{} representation. (See
the 'atomFeedString.xml' test data, below.)
io.Reader HANDLING
ToMap(), ToJson(), and ToJsonIndent() provide parsing of messages from an io.Reader.
If you want to handle a message stream, look at XmlMsgsFromReader().
NON-UTF8 CHARACTER SETS
Use the X2jCharsetReader variable to assign io.Reader for alternative character sets.
*/
package x2j
import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"io"
"strconv"
"strings"
"gitee.com/johng/gf/third/github.com/clbanning/mxj"
)
// If X2jCharsetReader != nil, it will be used to decode the doc or stream if required
// import charset "code.google.com/p/go-charset/charset"
// ...
// x2j.X2jCharsetReader = charset.NewReader
// s, err := x2j.DocToJson(doc)
var X2jCharsetReader func(charset string, input io.Reader)(io.Reader, error)
// DocToJson - return an XML doc as a JSON string.
// If the optional argument 'recast' is 'true', then values will be converted to boolean or float64 if possible.
func DocToJson(doc string, recast ...bool) (string, error) {
var r bool
if len(recast) == 1 {
r = recast[0]
}
m, merr := mxj.NewMapXml([]byte(doc), r)
if m == nil || merr != nil {
return "", merr
}
b, berr := m.Json()
if berr != nil {
return "", berr
}
// NOTE: don't have to worry about safe JSON marshaling with json.Marshal, since '<' and '>" are reservedin XML.
return string(b), nil
}
// DocToJsonIndent - return an XML doc as a prettified JSON string.
// If the optional argument 'recast' is 'true', then values will be converted to boolean or float64 if possible.
// Note: recasting is only applied to element values, not attribute values.
func DocToJsonIndent(doc string, recast ...bool) (string, error) {
var r bool
if len(recast) == 1 {
r = recast[0]
}
m, merr := mxj.NewMapXml([]byte(doc), r)
if m == nil || merr != nil {
return "", merr
}
b, berr := m.JsonIndent("", " ")
if berr != nil {
return "", berr
}
// NOTE: don't have to worry about safe JSON marshaling with json.Marshal, since '<' and '>" are reservedin XML.
return string(b), nil
}
// DocToMap - convert an XML doc into a map[string]interface{}.
// (This is analogous to unmarshalling a JSON string to map[string]interface{} using json.Unmarshal().)
// If the optional argument 'recast' is 'true', then values will be converted to boolean or float64 if possible.
// Note: recasting is only applied to element values, not attribute values.
func DocToMap(doc string, recast ...bool) (map[string]interface{}, error) {
var r bool
if len(recast) == 1 {
r = recast[0]
}
return mxj.NewMapXml([]byte(doc), r)
}
// WriteMap - dumps the map[string]interface{} for examination.
// 'offset' is initial indentation count; typically: WriteMap(m).
// NOTE: with XML all element types are 'string'.
// But code written as generic for use with maps[string]interface{} values from json.Unmarshal().
// Or it can handle a DocToMap(doc,true) result where values have been recast'd.
func WriteMap(m interface{}, offset ...int) string {
var indent int
if len(offset) == 1 {
indent = offset[0]
}
var s string
switch m.(type) {
case nil:
return "[nil] nil"
case string:
return "[string] " + m.(string)
case float64:
return "[float64] " + strconv.FormatFloat(m.(float64), 'e', 2, 64)
case bool:
return "[bool] " + strconv.FormatBool(m.(bool))
case []interface{}:
s += "[[]interface{}]"
for i, v := range m.([]interface{}) {
s += "\n"
for i := 0; i < indent; i++ {
s += " "
}
s += "[item: " + strconv.FormatInt(int64(i), 10) + "]"
switch v.(type) {
case string, float64, bool:
s += "\n"
default:
// noop
}
for i := 0; i < indent; i++ {
s += " "
}
s += WriteMap(v, indent+1)
}
case map[string]interface{}:
for k, v := range m.(map[string]interface{}) {
s += "\n"
for i := 0; i < indent; i++ {
s += " "
}
// s += "[map[string]interface{}] "+k+" :"+WriteMap(v,indent+1)
s += k + " :" + WriteMap(v, indent+1)
}
default:
// shouldn't ever be here ...
s += fmt.Sprintf("unknown type for: %v", m)
}
return s
}
// ------------------------ value extraction from XML doc --------------------------
// DocValue - return a value for a specific tag
// 'doc' is a valid XML message.
// 'path' is a hierarchy of XML tags, e.g., "doc.name".
// 'attrs' is an OPTIONAL list of "name:value" pairs for attributes.
// Note: 'recast' is not enabled here. Use DocToMap(), NewAttributeMap(), and MapValue() calls for that.
func DocValue(doc, path string, attrs ...string) (interface{}, error) {
m, err := mxj.NewMapXml([]byte(doc), false)
if err != nil {
return nil, err
}
a, err := NewAttributeMap(attrs...)
if err != nil {
return nil, err
}
v, verr := MapValue(m, path, a)
if verr != nil {
return nil, verr
}
return v, nil
}
// MapValue - retrieves value based on walking the map, 'm'.
// 'm' is the map value of interest.
// 'path' is a period-separated hierarchy of keys in the map.
// 'attr' is a map of attribute "name:value" pairs from NewAttributeMap(). May be 'nil'.
// If the path can't be traversed, an error is returned.
// Note: the optional argument 'r' can be used to coerce attribute values, 'attr', if done so for 'm'.
func MapValue(m map[string]interface{}, path string, attr map[string]interface{}, r ...bool) (interface{}, error) {
// attribute values may have been recasted during map construction; default is 'false'.
if len(r) == 1 && r[0] == true {
for k, v := range attr {
attr[k] = recast(v.(string), true)
}
}
// parse the path
keys := strings.Split(path, ".")
// initialize return value to 'm' so a path of "" will work correctly
var v interface{} = m
var ok bool
var okey string
var isMap bool = true
if keys[0] == "" && len(attr) == 0 {
return v, nil
}
for _, key := range keys {
if !isMap {
return nil, errors.New("no keys beyond: " + okey)
}
if v, ok = m[key]; !ok {
return nil, errors.New("no key in map: " + key)
} else {
switch v.(type) {
case map[string]interface{}:
m = v.(map[string]interface{})
isMap = true
default:
isMap = false
}
}
// save 'key' for error reporting
okey = key
}
// match attributes; value is "#text" or nil
if attr == nil {
return v, nil
}
return hasAttributes(v, attr)
}
// recast - try to cast string values to bool or float64
func recast(s string, r bool) interface{} {
if r {
// handle numeric strings ahead of boolean
if f, err := strconv.ParseFloat(s, 64); err == nil {
return interface{}(f)
}
// ParseBool treats "1"==true & "0"==false
if b, err := strconv.ParseBool(s); err == nil {
return interface{}(b)
}
}
return interface{}(s)
}
// hasAttributes() - interface{} equality works for string, float64, bool
func hasAttributes(v interface{}, a map[string]interface{}) (interface{}, error) {
switch v.(type) {
case []interface{}:
// run through all entries looking one with matching attributes
for _, vv := range v.([]interface{}) {
if vvv, vvverr := hasAttributes(vv, a); vvverr == nil {
return vvv, nil
}
}
return nil, errors.New("no list member with matching attributes")
case map[string]interface{}:
// do all attribute name:value pairs match?
nv := v.(map[string]interface{})
for key, val := range a {
if vv, ok := nv[key]; !ok {
return nil, errors.New("no attribute with name: " + key[1:])
} else if val != vv {
return nil, errors.New("no attribute key:value pair: " + fmt.Sprintf("%s:%v", key[1:], val))
}
}
// they all match; so return value associated with "#text" key.
if vv, ok := nv["#text"]; ok {
return vv, nil
} else {
// this happens when another element is value of tag rather than just a string value
return nv, nil
}
}
return nil, errors.New("no match for attributes")
}
// NewAttributeMap() - generate map of attributes=value entries as map["-"+string]string.
// 'kv' arguments are "name:value" pairs that appear as attributes, name="value".
// If len(kv) == 0, the return is (nil, nil).
func NewAttributeMap(kv ...string) (map[string]interface{}, error) {
if len(kv) == 0 {
return nil, nil
}
m := make(map[string]interface{}, 0)
for _, v := range kv {
vv := strings.Split(v, ":")
if len(vv) != 2 {
return nil, errors.New("attribute not \"name:value\" pair: " + v)
}
// attributes are stored as keys prepended with hyphen
m["-"+vv[0]] = interface{}(vv[1])
}
return m, nil
}
//------------------------- get values for key ----------------------------
// ValuesForTag - return all values in doc associated with 'tag'.
// Returns nil if the 'tag' does not occur in the doc.
// If there is an error encounted while parsing doc, that is returned.
// If you want values 'recast' use DocToMap() and ValuesForKey().
func ValuesForTag(doc, tag string) ([]interface{}, error) {
m, err := mxj.NewMapXml([]byte(doc))
if err != nil {
return nil, err
}
return ValuesForKey(m, tag), nil
}
// ValuesForKey - return all values in map associated with 'key'
// Returns nil if the 'key' does not occur in the map
func ValuesForKey(m map[string]interface{}, key string) []interface{} {
ret := make([]interface{}, 0)
hasKey(m, key, &ret)
if len(ret) > 0 {
return ret
}
return nil
}
// hasKey - if the map 'key' exists append it to array
// if it doesn't do nothing except scan array and map values
func hasKey(iv interface{}, key string, ret *[]interface{}) {
switch iv.(type) {
case map[string]interface{}:
vv := iv.(map[string]interface{})
if v, ok := vv[key]; ok {
*ret = append(*ret, v)
}
for _, v := range iv.(map[string]interface{}) {
hasKey(v, key, ret)
}
case []interface{}:
for _, v := range iv.([]interface{}) {
hasKey(v, key, ret)
}
}
}
// ======== 2013.07.01 - x2j.Unmarshal, wraps xml.Unmarshal ==============
// Unmarshal - wraps xml.Unmarshal with handling of map[string]interface{}
// and string type variables.
// Usage: x2j.Unmarshal(doc,&m) where m of type map[string]interface{}
// x2j.Unmarshal(doc,&s) where s of type string (Overrides xml.Unmarshal().)
// x2j.Unmarshal(doc,&struct) - passed to xml.Unmarshal()
// x2j.Unmarshal(doc,&slice) - passed to xml.Unmarshal()
func Unmarshal(doc []byte, v interface{}) error {
switch v.(type) {
case *map[string]interface{}:
m, err := mxj.NewMapXml(doc)
vv := *v.(*map[string]interface{})
for k, v := range m {
vv[k] = v
}
return err
case *string:
s, err := ByteDocToJson(doc)
*(v.(*string)) = s
return err
default:
b := bytes.NewBuffer(doc)
p := xml.NewDecoder(b)
p.CharsetReader = X2jCharsetReader
return p.Decode(v)
// return xml.Unmarshal(doc, v)
}
return nil
}
// ByteDocToJson - return an XML doc as a JSON string.
// If the optional argument 'recast' is 'true', then values will be converted to boolean or float64 if possible.
func ByteDocToJson(doc []byte, recast ...bool) (string, error) {
var r bool
if len(recast) == 1 {
r = recast[0]
}
m, merr := mxj.NewMapXml(doc, r)
if m == nil || merr != nil {
return "", merr
}
b, berr := m.Json()
if berr != nil {
return "", berr
}
// NOTE: don't have to worry about safe JSON marshaling with json.Marshal, since '<' and '>" are reservedin XML.
return string(b), nil
}
// ByteDocToMap - convert an XML doc into a map[string]interface{}.
// (This is analogous to unmarshalling a JSON string to map[string]interface{} using json.Unmarshal().)
// If the optional argument 'recast' is 'true', then values will be converted to boolean or float64 if possible.
// Note: recasting is only applied to element values, not attribute values.
func ByteDocToMap(doc []byte, recast ...bool) (map[string]interface{}, error) {
var r bool
if len(recast) == 1 {
r = recast[0]
}
return mxj.NewMapXml(doc, r)
}

View File

@ -0,0 +1,129 @@
// Copyright 2012-2018 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
// x2j_bulk.go: Process files with multiple XML messages.
// Extends x2m_bulk.go to work with JSON strings rather than map[string]interface{}.
package x2j
import (
"bytes"
"io"
"os"
"regexp"
"gitee.com/johng/gf/third/github.com/clbanning/mxj"
)
// XmlMsgsFromFileAsJson()
// 'fname' is name of file
// 'phandler' is the JSON string processing handler. Return of 'false' stops further processing.
// 'ehandler' is the parsing error handler. Return of 'false' stops further processing and returns error.
// Note: phandler() and ehandler() calls are blocking, so reading and processing of messages is serialized.
// This means that you can stop reading the file on error or after processing a particular message.
// To have reading and handling run concurrently, pass arguments to a go routine in handler and return true.
func XmlMsgsFromFileAsJson(fname string, phandler func(string)(bool), ehandler func(error)(bool), recast ...bool) error {
var r bool
if len(recast) == 1 {
r = recast[0]
}
fi, fierr := os.Stat(fname)
if fierr != nil {
return fierr
}
fh, fherr := os.Open(fname)
if fherr != nil {
return fherr
}
defer fh.Close()
buf := make([]byte,fi.Size())
_, rerr := fh.Read(buf)
if rerr != nil {
return rerr
}
doc := string(buf)
// xml.Decoder doesn't properly handle whitespace in some doc
// see songTextString.xml test case ...
reg,_ := regexp.Compile("[ \t\n\r]*<")
doc = reg.ReplaceAllString(doc,"<")
b := bytes.NewBufferString(doc)
for {
s, serr := XmlBufferToJson(b,r)
if serr != nil && serr != io.EOF {
if ok := ehandler(serr); !ok {
// caused reader termination
return serr
}
}
if s != "" {
if ok := phandler(s); !ok {
break
}
}
if serr == io.EOF {
break
}
}
return nil
}
// XmlBufferToJson - process XML message from a bytes.Buffer
// 'b' is the buffer
// Optional argument 'recast' coerces values to float64 or bool where possible.
func XmlBufferToJson(b *bytes.Buffer,recast ...bool) (string,error) {
var r bool
if len(recast) == 1 {
r = recast[0]
}
m, err := mxj.NewMapXmlReader(b, r)
// n,err := XmlBufferToTree(b)
if err != nil {
return "", err
}
// m := make(map[string]interface{})
// m[n.key] = n.treeToMap(r)
j, jerr := m.Json()
return string(j), jerr
}
// ============================= io.Reader version for stream processing ======================
// XmlMsgsFromReaderAsJson() - io.Reader version of XmlMsgsFromFileAsJson
// 'rdr' is an io.Reader for an XML message (stream)
// 'phandler' is the JSON string processing handler. Return of 'false' stops further processing.
// 'ehandler' is the parsing error handler. Return of 'false' stops further processing and returns error.
// Note: phandler() and ehandler() calls are blocking, so reading and processing of messages is serialized.
// This means that you can stop reading the file on error or after processing a particular message.
// To have reading and handling run concurrently, pass arguments to a go routine in handler and return true.
func XmlMsgsFromReaderAsJson(rdr io.Reader, phandler func(string)(bool), ehandler func(error)(bool), recast ...bool) error {
var r bool
if len(recast) == 1 {
r = recast[0]
}
for {
s, serr := ToJson(rdr,r)
if serr != nil && serr != io.EOF {
if ok := ehandler(serr); !ok {
// caused reader termination
return serr
}
}
if s != "" {
if ok := phandler(s); !ok {
break
}
}
if serr == io.EOF {
break
}
}
return nil
}

View File

@ -0,0 +1,148 @@
// x2j_findPath - utility functions to retrieve path to node in dot-notation
// Copyright 2012-2018 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
package x2j
import (
"strings"
"gitee.com/johng/gf/third/github.com/clbanning/mxj"
)
//----------------------------- find all paths to a key --------------------------------
// Want eventually to extract shortest path and call GetValuesAtKeyPath()
// This will get all the possible paths. These can be scanned for len(path) and sequence.
// Get all paths through the doc (in dot-notation) that terminate with the specified tag.
// Results can be used with ValuesAtTagPath() and ValuesFromTagPath().
func PathsForTag(doc string, key string) ([]string, error) {
m, err := mxj.NewMapXml([]byte(doc))
if err != nil {
return nil, err
}
ss := PathsForKey(m, key)
return ss, nil
}
// Extract the shortest path from all possible paths - from PathsForTag().
// Paths are strings using dot-notation.
func PathForTagShortest(doc string, key string) (string, error) {
m, err := mxj.NewMapXml([]byte(doc))
if err != nil {
return "", err
}
s := PathForKeyShortest(m, key)
return s, nil
}
// Get all paths through the doc (in dot-notation) that terminate with the specified tag.
// Results can be used with ValuesAtTagPath() and ValuesFromTagPath().
func BytePathsForTag(doc []byte, key string) ([]string, error) {
m, err := mxj.NewMapXml(doc)
if err != nil {
return nil, err
}
ss := PathsForKey(m, key)
return ss, nil
}
// Extract the shortest path from all possible paths - from PathsForTag().
// Paths are strings using dot-notation.
func BytePathForTagShortest(doc []byte, key string) (string, error) {
m, err := ByteDocToMap(doc)
if err != nil {
return "", err
}
s := PathForKeyShortest(m, key)
return s, nil
}
// Get all paths through the map (in dot-notation) that terminate with the specified key.
// Results can be used with ValuesAtKeyPath() and ValuesFromKeyPath().
func PathsForKey(m map[string]interface{}, key string) []string {
breadbasket := make(map[string]bool,0)
breadcrumb := ""
hasKeyPath(breadcrumb, m, key, &breadbasket)
if len(breadbasket) == 0 {
return nil
}
// unpack map keys to return
res := make([]string,len(breadbasket))
var i int
for k,_ := range breadbasket {
res[i] = k
i++
}
return res
}
// Extract the shortest path from all possible paths - from PathsForKey().
// Paths are strings using dot-notation.
func PathForKeyShortest(m map[string]interface{}, key string) string {
paths := PathsForKey(m,key)
lp := len(paths)
if lp == 0 {
return ""
}
if lp == 1 {
return paths[0]
}
shortest := paths[0]
shortestLen := len(strings.Split(shortest,"."))
for i := 1 ; i < len(paths) ; i++ {
vlen := len(strings.Split(paths[i],"."))
if vlen < shortestLen {
shortest = paths[i]
shortestLen = vlen
}
}
return shortest
}
// hasKeyPath - if the map 'key' exists append it to KeyPath.path and increment KeyPath.depth
// This is really just a breadcrumber that saves all trails that hit the prescribed 'key'.
func hasKeyPath(crumb string, iv interface{}, key string, basket *map[string]bool) {
switch iv.(type) {
case map[string]interface{}:
vv := iv.(map[string]interface{})
if _, ok := vv[key]; ok {
if crumb == "" {
crumb = key
} else {
crumb += "." + key
}
// *basket = append(*basket, crumb)
(*basket)[crumb] = true
}
// walk on down the path, key could occur again at deeper node
for k, v := range vv {
// create a new breadcrumb, add the one we're at to the crumb-trail
var nbc string
if crumb == "" {
nbc = k
} else {
nbc = crumb + "." + k
}
hasKeyPath(nbc, v, key, basket)
}
case []interface{}:
// crumb-trail doesn't change, pass it on
for _, v := range iv.([]interface{}) {
hasKeyPath(crumb, v, key, basket)
}
}
}

View File

@ -0,0 +1,390 @@
package x2j
import (
"encoding/json"
"fmt"
"os"
"testing"
)
func TestX2j(t *testing.T) {
fmt.Println("\n================================ x2j_test.go ...")
fmt.Println("\n=================== TestX2j ...")
fi, fierr := os.Stat("x2j_test.xml")
if fierr != nil {
fmt.Println("fierr:",fierr.Error())
return
}
fh, fherr := os.Open("x2j_test.xml")
if fherr != nil {
fmt.Println("fherr:",fherr.Error())
return
}
defer fh.Close()
buf := make([]byte,fi.Size())
_, nerr := fh.Read(buf)
if nerr != nil {
fmt.Println("nerr:",nerr.Error())
return
}
doc := string(buf)
fmt.Println("\nXML doc:\n",doc)
// test DocToMap() with recast
mm, mmerr := DocToMap(doc,true)
if mmerr != nil {
println("mmerr:",mmerr.Error())
return
}
println("\nDocToMap(), recast==true:\n",WriteMap(mm))
// test DocToJsonIndent() with recast
s,serr := DocToJsonIndent(doc,true)
if serr != nil {
fmt.Println("serr:",serr.Error())
}
fmt.Println("\nDocToJsonIndent, recast==true:\n",s)
}
func TestGetValue(t *testing.T) {
fmt.Println("\n=================== TestGetValue ...")
// test MapValue()
doc := `<entry><vars><foo>bar</foo><foo2><hello>world</hello></foo2></vars></entry>`
fmt.Println("\nRead doc:",doc)
fmt.Println("Looking for value: entry.vars")
mm,mmerr := DocToMap(doc)
if mmerr != nil {
fmt.Println("merr:",mmerr.Error())
}
v,verr := MapValue(mm,"entry.vars",nil)
if verr != nil {
fmt.Println("verr:",verr.Error())
} else {
j, jerr := json.MarshalIndent(v,""," ")
if jerr != nil {
fmt.Println("jerr:",jerr.Error())
} else {
fmt.Println(string(j))
}
}
fmt.Println("Looking for value: entry.vars.foo2.hello")
v,verr = MapValue(mm,"entry.vars.foo2.hello",nil)
if verr != nil {
fmt.Println("verr:",verr.Error())
} else {
fmt.Println(v.(string))
}
fmt.Println("Looking with error in path: entry.var")
v,verr = MapValue(mm,"entry.var",nil)
fmt.Println("verr:",verr.Error())
// test DocValue()
fmt.Println("DocValue() for tag path entry.vars")
v,verr = DocValue(doc,"entry.vars")
if verr != nil {
fmt.Println("verr:",verr.Error())
}
j,_ := json.MarshalIndent(v,""," ")
fmt.Println(string(j))
}
func TestGetValueWithAttr(t *testing.T) {
fmt.Println("\n=================== TestGetValueWithAttr ...")
doc := `<entry><vars>
<foo item="1">bar</foo>
<foo item="2">
<hello item="3">world</hello>
<hello item="4">universe</hello>
</foo></vars></entry>`
fmt.Println("\nRead doc:",doc)
fmt.Println("Looking for value: entry.vars")
mm,mmerr := DocToMap(doc)
if mmerr != nil {
fmt.Println("merr:",mmerr.Error())
}
v,verr := MapValue(mm,"entry.vars",nil)
if verr != nil {
fmt.Println("verr:",verr.Error())
} else {
j, jerr := json.MarshalIndent(v,""," ")
if jerr != nil {
fmt.Println("jerr:",jerr.Error())
} else {
fmt.Println(string(j))
}
}
fmt.Println("\nMapValue(): Looking for value: entry.vars.foo item=2")
a,aerr := NewAttributeMap("item:2")
if aerr != nil {
fmt.Println("aerr:",aerr.Error())
}
v,verr = MapValue(mm,"entry.vars.foo",a)
if verr != nil {
fmt.Println("verr:",verr.Error())
} else {
j, jerr := json.MarshalIndent(v,""," ")
if jerr != nil {
fmt.Println("jerr:",jerr.Error())
} else {
fmt.Println(string(j))
}
}
fmt.Println("\nMapValue(): Looking for hello item:4")
a,_ = NewAttributeMap("item:4")
v,verr = MapValue(mm,"hello",a)
if verr != nil {
fmt.Println("verr:",verr.Error())
} else {
j, jerr := json.MarshalIndent(v,""," ")
if jerr != nil {
fmt.Println("jerr:",jerr.Error())
} else {
fmt.Println(string(j))
}
}
fmt.Println("\nDocValue(): Looking for entry.vars.foo.hello item:4")
v,verr = DocValue(doc,"entry.vars.foo.hello","item:4")
if verr != nil {
fmt.Println("verr:",verr.Error())
} else {
j, jerr := json.MarshalIndent(v,""," ")
if jerr != nil {
fmt.Println("jerr:",jerr.Error())
} else {
fmt.Println(string(j))
}
}
fmt.Println("\nDocValue(): Looking for empty nil")
v,verr = DocValue(doc,"")
if verr != nil {
fmt.Println("verr:",verr.Error())
} else {
j, jerr := json.MarshalIndent(v,""," ")
if jerr != nil {
fmt.Println("jerr:",jerr.Error())
} else {
fmt.Println(string(j))
}
}
// test 'recast' switch
fmt.Println("\ntesting recast switch...")
mm,mmerr = DocToMap(doc,true)
if mmerr != nil {
fmt.Println("merr:",mmerr.Error())
}
fmt.Println("MapValue(): Looking for value: entry.vars.foo item=2")
a,aerr = NewAttributeMap("item:2")
if aerr != nil {
fmt.Println("aerr:",aerr.Error())
}
v,verr = MapValue(mm,"entry.vars.foo",a,true)
if verr != nil {
fmt.Println("verr:",verr.Error())
} else {
j, jerr := json.MarshalIndent(v,""," ")
if jerr != nil {
fmt.Println("jerr:",jerr.Error())
} else {
fmt.Println(string(j))
}
}
}
func TestStuff_1(t *testing.T) {
fmt.Println("\n=================== TestStuff_1 ...")
doc := `<doc>
<tag item="1">val2</tag>
<tag item="2">val2</tag>
<tag item="2" instance="2">val3</tag>
</doc>`
fmt.Println(doc)
m,merr := DocToMap(doc)
if merr != nil {
fmt.Println("merr:",merr.Error())
} else {
fmt.Println(WriteMap(m))
}
fmt.Println("\nDocValue(): tag")
v,verr := DocValue(doc,"doc.tag")
if verr != nil {
fmt.Println("verr:",verr.Error())
} else {
j, jerr := json.MarshalIndent(v,""," ")
if jerr != nil {
fmt.Println("jerr:",jerr.Error())
} else {
fmt.Println(string(j))
}
}
fmt.Println("\nDocValue(): item:2 instance:2")
v,verr = DocValue(doc,"doc.tag","item:2","instance:2")
if verr != nil {
fmt.Println("verr:",verr.Error())
} else {
j, jerr := json.MarshalIndent(v,""," ")
if jerr != nil {
fmt.Println("jerr:",jerr.Error())
} else {
fmt.Println(string(j))
}
}
}
func TestStuff_2(t *testing.T) {
fmt.Println("\n=================== TestStuff_2 ...")
doc := `
<tag item="1">val2</tag>
<tag item="2">val2</tag>
<tag item="2" instance="2">val3</tag>`
fmt.Println(doc)
m,merr := DocToMap(doc)
if merr != nil {
fmt.Println("merr:",merr.Error())
} else {
fmt.Println(WriteMap(m))
}
fmt.Println("\nDocValue(): tag")
v,verr := DocValue(doc,"tag")
if verr != nil {
fmt.Println("verr:",verr.Error())
} else {
j, jerr := json.MarshalIndent(v,""," ")
if jerr != nil {
fmt.Println("jerr:",jerr.Error())
} else {
fmt.Println(string(j))
}
}
fmt.Println("\nDocValue(): item:2 instance:2")
v,verr = DocValue(doc,"tag","item:2","instance:2")
if verr != nil {
fmt.Println("verr:",verr.Error())
} else {
j, jerr := json.MarshalIndent(v,""," ")
if jerr != nil {
fmt.Println("jerr:",jerr.Error())
} else {
fmt.Println(string(j))
}
}
}
func procMap(m map[string]interface{}) bool {
fmt.Println("procMap:",WriteMap(m))
return true
}
func procMapToJson(m map[string]interface{}) bool {
b,_ := json.MarshalIndent(m,""," ")
fmt.Println("procMap:",string(b))
return true
}
func procErr(err error) bool {
fmt.Println("procError err:",err.Error())
return true
}
func TestBulk(t *testing.T) {
fmt.Println("\n=================== TestBulkBuffer ...")
fmt.Println("\nBulk Message Processing Tests")
// if err := XmlMsgsFromFile("x2m_bulk.xml",procMap,procErr); err != nil {
if err := XmlMsgsFromFile("x2m_bulk.xml",procMapToJson,procErr); err != nil {
fmt.Println("XmlMsgsFromFile err:",err.Error())
}
}
func TestTagAndKey(t *testing.T) {
fmt.Println("\n=================== TestTagAndKey ...")
var doc string
doc = `<doc>
<sections>
<section>one</section>
<section>
<parts>
<part>two.one</part>
<part>two.two</part>
</parts>
</section>
</sections>
<partitions>
<parts>
<sections>
<section>one</section>
<section>two</section>
</sections>
</parts>
</partitions>
</doc>`
fmt.Println("\nTestTagAndKey()\n",doc)
v,verr := ValuesForTag(doc,"parts")
if verr != nil {
fmt.Println("verr:",verr.Error())
}
fmt.Println("tag: parts :: len:",len(v),"v:",v)
v, _ = ValuesForTag(doc,"not_a_tag")
if v == nil {
fmt.Println("no 'not_a_tag' tag")
} else {
fmt.Println("key: not_a_tag :: len:",len(v),"v:",v)
}
m,merr := DocToMap(doc)
if merr != nil {
fmt.Println("merr:",merr.Error())
}
v = ValuesForKey(m,"section")
fmt.Println("key: section :: len:",len(v),"v:",v)
v = ValuesForKey(m,"not_a_key")
if v == nil {
fmt.Println("no 'not_a_key' key")
} else {
fmt.Println("key: not_a-key :: len:",len(v),"v:",v)
}
}
// ---------------- x2j_fast.go ----------------
/*
func Test_F_DocToMap(t *testing.T) {
var doc string = `<doc>
<sections>
<section>one</section>
<section>
<parts>
<part>two.one</part>
<part>two.two</part>
</parts>
</section>
</sections>
<partitions>
<parts>
<sections>
<section>one</section>
<section>two</section>
</sections>
</parts>
</partitions>
</doc>`
fmt.Println("\nF_DocToMap()")
fmt.Println(doc)
m,err := F_DocToMap(doc)
if err != nil {
fmt.Println("err:",err.Error())
}
fmt.Println(WriteMap(m),"\n")
}
*/

View File

@ -0,0 +1,29 @@
<msg mtype="alert" mpriority="1">
<text>help me!</text>
<song title="A Long Time" author="Mayer Hawthorne">
<verses>
<verse name="verse 1" no="1">
<line no="1">Henry was a renegade</line>
<line no="2">Didn't like to play it safe</line>
<line no="3">One component at a time</line>
<line no="4">There's got to be a better way</line>
<line no="5">Oh, people came from miles around</line>
<line no="6">Searching for a steady job</line>
<line no="7">Welcome to the Motor Town</line>
<line no="8">Booming like an atom bomb</line>
</verse>
<verse name="verse 2" no="2">
<line no="1">Oh, Henry was the end of the story</line>
<line no="2">Then everything went wrong</line>
<line no="3">And we'll return it to its former glory</line>
<line no="4">But it just takes so long</line>
</verse>
</verses>
<chorus>
<line no="1">It's going to take a long time</line>
<line no="2">It's going to take it, but we'll make it one day</line>
<line no="3">It's going to take a long time</line>
<line no="4">It's going to take it, but we'll make it one day</line>
</chorus>
</song>
</msg>

View File

@ -0,0 +1,90 @@
// Copyright 2012-2018 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
// x2j_valuesAt.go: Extract values from an arbitrary XML doc that are at same level as "key".
// Tag path can include wildcard characters.
package x2j
import (
"strings"
"gitee.com/johng/gf/third/github.com/clbanning/mxj"
)
// ------------------- sweep up everything for some point in the node tree ---------------------
// ValuesAtTagPath - deliver all values at the same level of the document as the specified key.
// See ValuesAtKeyPath().
// If there are no values for the path 'nil' is returned.
// A return value of (nil, nil) means that there were no values and no errors parsing the doc.
// 'doc' is the XML document
// 'path' is a dot-separated path of tag nodes
// 'getAttrs' can be set 'true' to return attribute values for "*"-terminated path
// If a node is '*', then everything beyond is scanned for values.
// E.g., "doc.books' might return a single value 'book' of type []interface{}, but
// "doc.books.*" could return all the 'book' entries as []map[string]interface{}.
// "doc.books.*.author" might return all the 'author' tag values as []string - or
// "doc.books.*.author.lastname" might be required, depending on he schema.
func ValuesAtTagPath(doc, path string, getAttrs ...bool) ([]interface{}, error) {
var a bool
if len(getAttrs) == 1 {
a = getAttrs[0]
}
m, err := mxj.NewMapXml([]byte(doc))
if err != nil {
return nil, err
}
v := ValuesAtKeyPath(m, path, a)
return v, nil
}
// ValuesAtKeyPath - deliver all values at the same depth in a map[string]interface{} value
// If v := ValuesAtKeyPath(m,"x.y.z")
// then there exists a _,vv := range v
// such that v.(map[string]interface{})[z] == ValuesFromKeyPath(m,"x.y.z")
// If there are no values for the path 'nil' is returned.
// 'm' is the map to be walked
// 'path' is a dot-separated path of key values
// 'getAttrs' can be set 'true' to return attribute values for "*"-terminated path
// If a node is '*', then everything beyond is walked.
// E.g., see ValuesFromTagPath documentation.
func ValuesAtKeyPath(m map[string]interface{}, path string, getAttrs ...bool) []interface{} {
var a bool
if len(getAttrs) == 1 {
a = getAttrs[0]
}
keys := strings.Split(path, ".")
lenKeys := len(keys)
ret := make([]interface{}, 0)
if lenKeys > 1 {
// use function in x2j_valuesFrom.go
valuesFromKeyPath(&ret, m, keys[:lenKeys-1], a)
if len(ret) == 0 {
return nil
}
} else {
ret = append(ret,interface{}(m))
}
// scan the value set and see if key occurs
key := keys[lenKeys-1]
// wildcard is special
if key == "*" {
return ret
}
for _, v := range ret {
switch v.(type) {
case map[string]interface{}:
if _, ok := v.(map[string]interface{})[key]; ok {
return ret
}
}
}
// no instance of key in penultimate value set
return nil
}

View File

@ -0,0 +1,127 @@
// Copyright 2012-2018 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
// x2j_valuesFrom.go: Extract values from an arbitrary XML doc. Tag path can include wildcard characters.
package x2j
import (
"strings"
"gitee.com/johng/gf/third/github.com/clbanning/mxj"
)
// ------------------- sweep up everything for some point in the node tree ---------------------
// ValuesFromTagPath - deliver all values for a path node from a XML doc
// If there are no values for the path 'nil' is returned.
// A return value of (nil, nil) means that there were no values and no errors parsing the doc.
// 'doc' is the XML document
// 'path' is a dot-separated path of tag nodes
// 'getAttrs' can be set 'true' to return attribute values for "*"-terminated path
// If a node is '*', then everything beyond is scanned for values.
// E.g., "doc.books' might return a single value 'book' of type []interface{}, but
// "doc.books.*" could return all the 'book' entries as []map[string]interface{}.
// "doc.books.*.author" might return all the 'author' tag values as []string - or
// "doc.books.*.author.lastname" might be required, depending on he schema.
func ValuesFromTagPath(doc, path string, getAttrs ...bool) ([]interface{}, error) {
var a bool
if len(getAttrs) == 1 {
a = getAttrs[0]
}
m, err := mxj.NewMapXml([]byte(doc))
if err != nil {
return nil, err
}
v := ValuesFromKeyPath(m, path, a)
return v, nil
}
// ValuesFromKeyPath - deliver all values for a path node from a map[string]interface{}
// If there are no values for the path 'nil' is returned.
// 'm' is the map to be walked
// 'path' is a dot-separated path of key values
// 'getAttrs' can be set 'true' to return attribute values for "*"-terminated path
// If a node is '*', then everything beyond is walked.
// E.g., see ValuesFromTagPath documentation.
func ValuesFromKeyPath(m map[string]interface{}, path string, getAttrs ...bool) []interface{} {
var a bool
if len(getAttrs) == 1 {
a = getAttrs[0]
}
keys := strings.Split(path, ".")
ret := make([]interface{}, 0)
valuesFromKeyPath(&ret, m, keys, a)
if len(ret) == 0 {
return nil
}
return ret
}
func valuesFromKeyPath(ret *[]interface{}, m interface{}, keys []string, getAttrs bool) {
lenKeys := len(keys)
// load 'm' values into 'ret'
// expand any lists
if lenKeys == 0 {
switch m.(type) {
case map[string]interface{}:
*ret = append(*ret, m)
case []interface{}:
for _, v := range m.([]interface{}) {
*ret = append(*ret, v)
}
default:
*ret = append(*ret, m)
}
return
}
// key of interest
key := keys[0]
switch key {
case "*": // wildcard - scan all values
switch m.(type) {
case map[string]interface{}:
for k, v := range m.(map[string]interface{}) {
if string(k[:1]) == "-" && !getAttrs { // skip attributes?
continue
}
valuesFromKeyPath(ret, v, keys[1:], getAttrs)
}
case []interface{}:
for _, v := range m.([]interface{}) {
switch v.(type) {
// flatten out a list of maps - keys are processed
case map[string]interface{}:
for kk, vv := range v.(map[string]interface{}) {
if string(kk[:1]) == "-" && !getAttrs { // skip attributes?
continue
}
valuesFromKeyPath(ret, vv, keys[1:], getAttrs)
}
default:
valuesFromKeyPath(ret, v, keys[1:], getAttrs)
}
}
}
default: // key - must be map[string]interface{}
switch m.(type) {
case map[string]interface{}:
if v, ok := m.(map[string]interface{})[key]; ok {
valuesFromKeyPath(ret, v, keys[1:], getAttrs)
}
case []interface{}: // may be buried in list
for _, v := range m.([]interface{}) {
switch v.(type) {
case map[string]interface{}:
if vv, ok := v.(map[string]interface{})[key]; ok {
valuesFromKeyPath(ret, vv, keys[1:], getAttrs)
}
}
}
}
}
}

View File

@ -0,0 +1,167 @@
package x2j
import (
"fmt"
"testing"
)
var doc1 = `
<doc>
<books>
<book seq="1">
<author>William H. Gaddis</author>
<title>The Recognitions</title>
<review>One of the great seminal American novels of the 20th century.</review>
</book>
<book seq="2">
<author>Austin Tappan Wright</author>
<title>Islandia</title>
<review>An example of earlier 20th century American utopian fiction.</review>
</book>
<book seq="3">
<author>John Hawkes</author>
<title>The Beetle Leg</title>
<review>A lyrical novel about the construction of Ft. Peck Dam in Montana.</review>
</book>
<book seq="4">
<author>
<first_name>T.E.</first_name>
<last_name>Porter</last_name>
</author>
<title>King's Day</title>
<review>A magical novella.</review>
</book>
</books>
</doc>
`
var doc2 = `
<doc>
<books>
<author>
<name>William H. Gaddis</name>
<book seq="1">
<title>The Recognitions</title>
<review>One of the great seminal American novels of the 20th century.</review>
</book>
<book>
<title>JR</title>
<review>Won the National Book Award</review>
</book>
</author>
<author>
<name>John Hawkes</name>
<books>
<book>
<title>The Beetle Leg</title>
</book>
<book>
<title>The Blood Oranges</title>
</book>
</books>
</author>
</books>
</doc>
`
var msg1 = `
<msg>
<pub>test</pub>
<text>This is a long cold winter</text>
</msg>`
var msg2 = `
<msgs>
<msg>
<pub>test</pub>
<text>This is a long cold winter</text>
</msg>
<msg>
<pub>test2</pub>
<text>I hope we have a cool summer, though</text>
</msg>
</msgs>`
func TestValuesAtKeyPath(t *testing.T) {
fmt.Println("\n============================ x2jat_test.go")
fmt.Println("\n=============== TestValuesAtKeyPath ...")
fmt.Println("\nValuesAtKeyPath ... doc1#author")
m, _ := DocToMap(doc1)
ss := PathsForKey(m,"author")
fmt.Println("ss:", ss)
for _,v := range ss {
vv := ValuesAtKeyPath(m,v,true)
fmt.Println("vv:", vv)
}
fmt.Println("\nValuesAtKeyPath ... doc1#first_name")
// m, _ := DocToMap(doc1)
ss = PathsForKey(m,"first_name")
fmt.Println("ss:", ss)
for _,v := range ss {
vv := ValuesAtKeyPath(m,v,true)
fmt.Println("vv:", vv)
}
fmt.Println("\nGetKeyPaths...doc2#book")
m, _ = DocToMap(doc2)
ss = PathsForKey(m,"book")
fmt.Println("ss:", ss)
for _,v := range ss {
vv := ValuesAtKeyPath(m,v,true)
fmt.Println("vv:", vv)
}
s := PathForKeyShortest(m,"book")
vv := ValuesAtKeyPath(m,s)
fmt.Println("vv,shortest_path:",vv)
fmt.Println("\nValuesAtKeyPath ... msg1#pub")
m, _ = DocToMap(msg1)
ss = PathsForKey(m,"pub")
fmt.Println("ss:", ss)
for _,v := range ss {
vv := ValuesAtKeyPath(m,v,true)
fmt.Println("vv:", vv)
}
fmt.Println("\nValuesAtKeyPath ... msg2#pub")
m, _ = DocToMap(msg2)
ss = PathsForKey(m,"pub")
fmt.Println("ss:", ss)
for _,v := range ss {
vv := ValuesAtKeyPath(m,v,true)
fmt.Println("vv:", vv)
}
}
func TestValuesAtTagPath(t *testing.T) {
fmt.Println("\n=============== TestValuesAtTagPath ...")
fmt.Println("\nValuesAtTagPath ... doc1#author")
m, _ := DocToMap(doc1)
ss := PathsForKey(m,"author")
fmt.Println("ss:", ss)
for _,v := range ss {
vv,_ := ValuesAtTagPath(doc1,v,true)
fmt.Println("vv:", vv)
}
fmt.Println("\nValuesAtTagPath ... doc1#first_name")
// m, _ := DocToMap(doc1)
ss = PathsForKey(m,"first_name")
fmt.Println("ss:", ss)
for _,v := range ss {
vv,_ := ValuesAtTagPath(doc1,v,true)
fmt.Println("vv:", vv)
}
fmt.Println("\nValuesAtTagPath...doc2#book")
m, _ = DocToMap(doc2)
ss = PathsForKey(m,"book")
fmt.Println("ss:", ss)
for _,v := range ss {
vv,_ := ValuesAtTagPath(doc2,v,true)
fmt.Println("vv:", vv)
}
s := PathForKeyShortest(m,"book")
vv,_ := ValuesAtTagPath(doc2,s)
fmt.Println("vv,shortest_path:",vv)
}

View File

@ -0,0 +1,109 @@
package x2j
import (
"fmt"
"testing"
)
var doc01 = `
<doc>
<books>
<book seq="1">
<author>William H. Gaddis</author>
<title>The Recognitions</title>
<review>One of the great seminal American novels of the 20th century.</review>
</book>
<book seq="2">
<author>Austin Tappan Wright</author>
<title>Islandia</title>
<review>An example of earlier 20th century American utopian fiction.</review>
</book>
<book seq="3">
<author>John Hawkes</author>
<title>The Beetle Leg</title>
<review>A lyrical novel about the construction of Ft. Peck Dam in Montana.</review>
</book>
<book seq="4">
<author>
<first_name>T.E.</first_name>
<last_name>Porter</last_name>
</author>
<title>King's Day</title>
<review>A magical novella.</review>
</book>
</books>
</doc>
`
var doc02 = `
<doc>
<books>
<author>
<name>William H. Gaddis</name>
<book seq="1">
<title>The Recognitions</title>
<review>One of the great seminal American novels of the 20th century.</review>
</book>
<book>
<title>JR</title>
<review>Won the National Book Award</review>
</book>
</author>
<author>
<name>John Hawkes</name>
<books>
<book>
<title>The Beetle Leg</title>
</book>
<book>
<title>The Blood Oranges</title>
</book>
</books>
</author>
</books>
</doc>
`
// the basic demo/test case - a small bibliography with mixed element types
func TestPathsForKey(t *testing.T) {
fmt.Println("\n================================ x2jfindPath_test.go")
fmt.Println("\n=============== TestPathsForKey ...")
fmt.Println("\nPathsForKey... doc01#author")
m, _ := DocToMap(doc01)
ss := PathsForKey(m, "author")
fmt.Println("ss:", ss)
fmt.Println("\nPathsForKey... doc01#books")
// m, _ := DocToMap(doc01)
ss = PathsForKey(m, "books")
fmt.Println("ss:", ss)
fmt.Println("\nPathsForKey...doc02#book")
m, _ = DocToMap(doc02)
ss = PathsForKey(m, "book")
fmt.Println("ss:", ss)
fmt.Println("\nPathForKeyShortest...doc02#book")
m, _ = DocToMap(doc02)
s := PathForKeyShortest(m, "book")
fmt.Println("s:", s)
}
// the basic demo/test case - a small bibliography with mixed element types
func TestPathsForTag(t *testing.T) {
fmt.Println("\n=============== TestPathsForTag ...")
fmt.Println("\nPathsForTag... doc01#author")
ss, _ := PathsForTag(doc01, "author")
fmt.Println("ss:", ss)
fmt.Println("\nPathsForTag... doc01#books")
ss, _ = PathsForTag(doc01, "books")
fmt.Println("ss:", ss)
fmt.Println("\nPathsForTag...doc02#book")
ss, _ = PathsForTag(doc02, "book")
fmt.Println("ss:", ss)
fmt.Println("\nPathForTagShortest...doc02#book")
s, _ := PathForTagShortest(doc02, "book")
fmt.Println("s:", s)
}

View File

@ -0,0 +1,169 @@
package x2j
import (
"fmt"
"testing"
)
// the basic demo/test case - a small bibliography with mixed element types
func TestValuesFromTagPath(t *testing.T) {
var doc = `
<doc>
<books>
<book seq="1">
<author>William H. Gaddis</author>
<title>The Recognitions</title>
<review>One of the great seminal American novels of the 20th century.</review>
</book>
<book seq="2">
<author>Austin Tappan Wright</author>
<title>Islandia</title>
<review>An example of earlier 20th century American utopian fiction.</review>
</book>
<book seq="3">
<author>John Hawkes</author>
<title>The Beetle Leg</title>
<review>A lyrical novel about the construction of Ft. Peck Dam in Montana.</review>
</book>
<book seq="4">
<author>
<first_name>T.E.</first_name>
<last_name>Porter</last_name>
</author>
<title>King's Day</title>
<review>A magical novella.</review>
</book>
</books>
</doc>
`
var doc2 = `
<doc>
<books>
<book seq="1">
<author>William H. Gaddis</author>
<title>The Recognitions</title>
<review>One of the great seminal American novels of the 20th century.</review>
</book>
</books>
</doc>
`
fmt.Println("\nTestValuesFromTagPath()\n",doc)
m,_ := DocToMap(doc)
fmt.Println("map:",WriteMap(m))
v,_ := ValuesFromTagPath(doc,"doc.books")
fmt.Println("path == doc.books: len(v):",len(v))
for key,val := range v {
fmt.Println(key,":",val)
}
v,_ = ValuesFromTagPath(doc,"doc.books.*")
fmt.Println("path == doc.books.*: len(v):",len(v))
for key,val := range v {
fmt.Println(key,":",val)
}
v,_ = ValuesFromTagPath(doc,"doc.books.book")
fmt.Println("path == doc.books.book: len(v):",len(v))
for key,val := range v {
fmt.Println(key,":",val)
}
v,_ = ValuesFromTagPath(doc2,"doc.books.book")
fmt.Println("doc == doc2 / path == doc.books.book: len(v):",len(v))
for key,val := range v {
fmt.Println(key,":",val)
}
v,_ = ValuesFromTagPath(doc,"doc.books.book.*")
fmt.Println("path == doc.books.book.*: len(v):",len(v))
for key,val := range v {
fmt.Println(key,":",val)
}
v,_ = ValuesFromTagPath(doc2,"doc.books.book.*")
fmt.Println("doc == doc2 / path == doc.books.book.*: len(v):",len(v))
for key,val := range v {
fmt.Println(key,":",val)
}
v,_ = ValuesFromTagPath(doc,"doc.books.*.author")
fmt.Println("path == doc.books.*.author: len(v):",len(v))
for key,val := range v {
fmt.Println(key,":",val)
}
v,_ = ValuesFromTagPath(doc,"doc.*.*.author")
fmt.Println("path == doc.*.*.author: len(v):",len(v))
for key,val := range v {
fmt.Println(key,":",val)
}
v,_ = ValuesFromTagPath(doc,"doc.*.*.title")
fmt.Println("path == doc.*.*.title: len(v):",len(v))
for key,val := range v {
fmt.Println(key,":",val)
}
v,_ = ValuesFromTagPath(doc,"doc.*.*.*")
fmt.Println("path == doc.*.*.*: len(v):",len(v))
for key,val := range v {
fmt.Println(key,":",val)
}
v,_ = ValuesFromTagPath(doc,"doc.*.*.*.*")
fmt.Println("path == doc.*.*.*.*: len(v):",len(v))
for key,val := range v {
fmt.Println(key,":",val)
}
}
// demo how to compensate for irregular tag labels in data
// "netid" vs. "idnet"
func TestValuesFromTagPath2(t *testing.T) {
var doc1 = `
<?xml version="1.0" encoding="UTF-8"?>
<data>
<netid>
<disable>no</disable>
<text1>default:text</text1>
<word1>default:word</word1>
</netid>
</data>
`
var doc2 = `
<?xml version="1.0" encoding="UTF-8"?>
<data>
<idnet>
<disable>yes</disable>
<text1>default:text</text1>
<word1>default:word</word1>
</idnet>
</data>
`
var docs = []string{doc1,doc2}
for n,doc := range docs {
fmt.Println("\nTestValuesFromTagPath2(), iteration:",n,"\n",doc)
m,_ := DocToMap(doc)
fmt.Println("map:",WriteMap(m))
v,_ := ValuesFromTagPath(doc,"data.*")
fmt.Println("\npath == data.*: len(v):",len(v))
for key,val := range v {
fmt.Println(key,":",val)
}
for key,val := range v[0].(map[string]interface{}) {
fmt.Println("\t",key,":",val)
}
v,_ = ValuesFromTagPath(doc,"data.*.*")
fmt.Println("\npath == data.*.*: len(v):",len(v))
for key,val := range v {
fmt.Println(key,":",val)
}
}
}

View File

@ -0,0 +1,57 @@
package x2j
import (
"fmt"
"testing"
"encoding/xml"
)
func TestUnmarshal(t *testing.T) {
var doc = []byte(`<doc> <name>Mayer Hawthorne</name> <song> <title>A Long Time</title> <verse no="1"> <line no="1">Henry was a renegade</line> <line no="2">Didn't like to play it safe</line> </verse> </song> </doc>`)
fmt.Println("\nUnmarshal test ... *map[string]interface{}, *string")
m := make(map[string]interface{},0)
err := Unmarshal(doc,&m)
if err != nil {
fmt.Println("err:",err.Error())
}
fmt.Println("m:",m)
var s string
err = Unmarshal(doc,&s)
if err != nil {
fmt.Println("err:",err.Error())
}
fmt.Println("s:",s)
}
func TestStructValue(t *testing.T) {
var doc = []byte(`<info><name>clbanning</name><address>unknown</address></info>`)
type Info struct {
XMLName xml.Name `xml:"info"`
Name string `xml:"name"`
Address string `xml:"address"`
}
var myInfo Info
fmt.Println("\nUnmarshal test ... struct:",string(doc))
err := Unmarshal(doc,&myInfo)
if err != nil {
fmt.Println("err:",err.Error())
} else {
fmt.Printf("myInfo: %+v\n",myInfo)
}
}
func TestMapValue(t *testing.T) {
var doc = `<doc> <name>Mayer Hawthorne</name> <song> <title>A Long Time</title> <verse no="1"> <line no="1">Henry was a renegade</line> <line no="2">Didn't like to play it safe</line> </verse> </song> </doc>`
fmt.Println("\nTestMapValue of doc.song.verse w/ len(attrs) == 0.")
fmt.Println("doc:",doc)
v,err := DocValue(doc,"doc.song.verse")
if err != nil {
fmt.Println("err:",err.Error())
}
fmt.Println("result:",v)
}

View File

@ -0,0 +1,118 @@
// Copyright 2012-2018 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
// x2m_bulk.go: Process files with multiple XML messages.
package x2j
import (
"bytes"
"io"
"os"
"regexp"
"gitee.com/johng/gf/third/github.com/clbanning/mxj"
)
// XmlMsgsFromFile()
// 'fname' is name of file
// 'phandler' is the map processing handler. Return of 'false' stops further processing.
// 'ehandler' is the parsing error handler. Return of 'false' stops further processing and returns error.
// Note: phandler() and ehandler() calls are blocking, so reading and processing of messages is serialized.
// This means that you can stop reading the file on error or after processing a particular message.
// To have reading and handling run concurrently, pass arguments to a go routine in handler and return true.
func XmlMsgsFromFile(fname string, phandler func(map[string]interface{})(bool), ehandler func(error)(bool), recast ...bool) error {
var r bool
if len(recast) == 1 {
r = recast[0]
}
fi, fierr := os.Stat(fname)
if fierr != nil {
return fierr
}
fh, fherr := os.Open(fname)
if fherr != nil {
return fherr
}
defer fh.Close()
buf := make([]byte,fi.Size())
_, rerr := fh.Read(buf)
if rerr != nil {
return rerr
}
doc := string(buf)
// xml.Decoder doesn't properly handle whitespace in some doc
// see songTextString.xml test case ...
reg,_ := regexp.Compile("[ \t\n\r]*<")
doc = reg.ReplaceAllString(doc,"<")
b := bytes.NewBufferString(doc)
for {
m, merr := mxj.NewMapXmlReader(b,r)
if merr != nil && merr != io.EOF {
if ok := ehandler(merr); !ok {
// caused reader termination
return merr
}
}
if m != nil {
if ok := phandler(m); !ok {
break
}
}
if merr == io.EOF {
break
}
}
return nil
}
// XmlBufferToMap - process XML message from a bytes.Buffer
// 'b' is the buffer
// Optional argument 'recast' coerces map values to float64 or bool where possible.
func XmlBufferToMap(b *bytes.Buffer,recast ...bool) (map[string]interface{},error) {
var r bool
if len(recast) == 1 {
r = recast[0]
}
return mxj.NewMapXmlReader(b, r)
}
// ============================= io.Reader version for stream processing ======================
// XmlMsgsFromReader() - io.Reader version of XmlMsgsFromFile
// 'rdr' is an io.Reader for an XML message (stream)
// 'phandler' is the map processing handler. Return of 'false' stops further processing.
// 'ehandler' is the parsing error handler. Return of 'false' stops further processing and returns error.
// Note: phandler() and ehandler() calls are blocking, so reading and processing of messages is serialized.
// This means that you can stop reading the file on error or after processing a particular message.
// To have reading and handling run concurrently, pass arguments to a go routine in handler and return true.
func XmlMsgsFromReader(rdr io.Reader, phandler func(map[string]interface{})(bool), ehandler func(error)(bool), recast ...bool) error {
var r bool
if len(recast) == 1 {
r = recast[0]
}
for {
m, merr := ToMap(rdr,r)
if merr != nil && merr != io.EOF {
if ok := ehandler(merr); !ok {
// caused reader termination
return merr
}
}
if m != nil {
if ok := phandler(m); !ok {
break
}
}
if merr == io.EOF {
break
}
}
return nil
}

View File

@ -0,0 +1,67 @@
<msg mtype="alert" mpriority="1">
<text>help me!</text>
<song title="A Long Time" author="Mayer Hawthorne">
<verses>
<verse name="verse 1" no="1">
<line no="1">Henry was a renegade</line>
<line no="2">Didn't like to play it safe</line>
<line no="3">One component at a time</line>
<line no="4">There's got to be a better way</line>
<line no="5">Oh, people came from miles around</line>
<line no="6">Searching for a steady job</line>
<line no="7">Welcome to the Motor Town</line>
<line no="8">Booming like an atom bomb</line>
</verse>
<verse name="verse 2" no="2">
<line no="1">Oh, Henry was the end of the story</line>
<line no="2">Then everything went wrong</line>
<line no="3">And we'll return it to its former glory</line>
<line no="4">But it just takes so long</line>
</verse>
</verses>
<chorus>
<line no="1">It's going to take a long time</line>
<line no="2">It's going to take it, but we'll make it one day</line>
<line no="3">It's going to take a long time</line>
<line no="4">It's going to take it, but we'll make it one day</line>
</chorus>
</song>
</msg>
<msg mtype="alert" mpriority="1">
<text>help me!</text>
<song title="A Long Time" author="Mayer Hawthorne">
<verses>
<verse name="verse 1" no="1">
<line no="1">Henry was a renegade</line>
<line no="2">Didn't like to play it safe</line>
<line no="3">One component at a time</line>
<line no="4">There's got to be a better way</line>
<line no="5">Oh, people came from miles around</line>
<line no="6">Searching for a steady job</line>
<line no="7">Welcome to the Motor Town</line>
<line no="8">Booming like an atom bomb</line>
</verse>
</verses>
</song>
</msg>
<msg mtype="alert" mpriority="1">
<text>help me!</text>
<song title="A Long Time" author="Mayer Hawthorne">
<chorus>
<line no="1">It's going to take a long time</line>
<line no="2">It's going to take it, but we'll make it one day</line>
<line no="3">It's going to take a long time</line>
<line no="4">It's going to take it, but we'll make it one day</line>
</chorus>
</song>
</msg>
<msg mtype="alert" mpriority="1">
<text>help me!</text>
<song title="A Long Time" author="Mayer Hawthorne">
<chorus>
<line no="1">It's going to take a long time</line>
<line no="2">It's going to take it, but we'll make it one day</line>
<line no="3">It's going to take a long time</line>
<line no="4">It's going to take it, but we'll make it one day</line>
</song>
</msg>

View File

@ -0,0 +1,13 @@
// Copyright 2012-2018 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
package x2j
var castNanInf bool
// Cast "Nan", "Inf", "-Inf" XML values to 'float64'.
// By default, these values will be decoded as 'string'.
func CastNanInf(b bool) {
castNanInf = b
}

View File

@ -0,0 +1,184 @@
// Copyright 2012-2014 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
// x2j - For (mostly) backwards compatibility with legacy x2j package.
// Wrappers for end-to-end XML to JSON transformation and value manipulation.
package x2j
import (
. "gitee.com/johng/gf/third/github.com/clbanning/mxj"
"io"
)
// FromXml() --> map[string]interface{}
func XmlToMap(xmlVal []byte) (map[string]interface{}, error) {
m, err := NewMapXml(xmlVal)
if err != nil {
return nil, err
}
return map[string]interface{}(m), nil
}
// map[string]interface{} --> ToXml()
func MapToXml(m map[string]interface{}) ([]byte, error) {
return Map(m).Xml()
}
// FromXml() --> ToJson().
func XmlToJson(xmlVal []byte, safeEncoding ...bool) ([]byte, error) {
m, err := NewMapXml(xmlVal)
if err != nil {
return nil, err
}
return m.Json(safeEncoding...)
}
// FromXml() --> ToJsonWriterRaw().
func XmlToJsonWriter(xmlVal []byte, jsonWriter io.Writer, safeEncoding ...bool) ([]byte, error) {
m, err := NewMapXml(xmlVal)
if err != nil {
return nil, err
}
return m.JsonWriterRaw(jsonWriter, safeEncoding...)
}
// FromXmlReaderRaw() --> ToJson().
func XmlReaderToJson(xmlReader io.Reader, safeEncoding ...bool) ([]byte, []byte, error) {
m, xraw, err := NewMapXmlReaderRaw(xmlReader)
if err != nil {
return xraw, nil, err
}
j, jerr := m.Json(safeEncoding...)
return xraw, j, jerr
}
// FromXmlReader() --> ToJsonWriter(). Handy for bulk transformation of documents.
func XmlReaderToJsonWriter(xmlReader io.Reader, jsonWriter io.Writer, safeEncoding ...bool) ([]byte, []byte, error) {
m, xraw, err := NewMapXmlReaderRaw(xmlReader)
if err != nil {
return xraw, nil, err
}
jraw, jerr := m.JsonWriterRaw(jsonWriter, safeEncoding...)
return xraw, jraw, jerr
}
// XML wrappers for Map methods implementing tag path and value functions.
// Wrap PathsForKey for XML.
func XmlPathsForTag(xmlVal []byte, tag string) ([]string, error) {
m, err := NewMapXml(xmlVal)
if err != nil {
return nil, err
}
paths := m.PathsForKey(tag)
return paths, nil
}
// Wrap PathForKeyShortest for XML.
func XmlPathForTagShortest(xmlVal []byte, tag string) (string, error) {
m, err := NewMapXml(xmlVal)
if err != nil {
return "", err
}
path := m.PathForKeyShortest(tag)
return path, nil
}
// Wrap ValuesForKey for XML.
// 'attrs' are key:value pairs for attributes, where key is attribute label prepended with a hypen, '-'.
func XmlValuesForTag(xmlVal []byte, tag string, attrs ...string) ([]interface{}, error) {
m, err := NewMapXml(xmlVal)
if err != nil {
return nil, err
}
return m.ValuesForKey(tag, attrs...)
}
// Wrap ValuesForPath for XML.
// 'attrs' are key:value pairs for attributes, where key is attribute label prepended with a hypen, '-'.
func XmlValuesForPath(xmlVal []byte, path string, attrs ...string) ([]interface{}, error) {
m, err := NewMapXml(xmlVal)
if err != nil {
return nil, err
}
return m.ValuesForPath(path, attrs...)
}
// Wrap UpdateValuesForPath for XML
// 'xmlVal' is XML value
// 'newTagValue' is the value to replace an existing value at the end of 'path'
// 'path' is the dot-notation path with the tag whose value is to be replaced at the end
// (can include wildcard character, '*')
// 'subkeys' are key:value pairs of tag:values that must match for the tag
func XmlUpdateValsForPath(xmlVal []byte, newTagValue interface{}, path string, subkeys ...string) ([]byte, error) {
m, err := NewMapXml(xmlVal)
if err != nil {
return nil, err
}
_, err = m.UpdateValuesForPath(newTagValue, path, subkeys...)
if err != nil {
return nil, err
}
return m.Xml()
}
// Wrap NewMap for XML and return as XML
// 'xmlVal' is an XML value
// 'tagpairs' are "oldTag:newTag" values that conform to 'keypairs' in (Map)NewMap.
func XmlNewXml(xmlVal []byte, tagpairs ...string) ([]byte, error) {
m, err := NewMapXml(xmlVal)
if err != nil {
return nil, err
}
n, err := m.NewMap(tagpairs...)
if err != nil {
return nil, err
}
return n.Xml()
}
// Wrap NewMap for XML and return as JSON
// 'xmlVal' is an XML value
// 'tagpairs' are "oldTag:newTag" values that conform to 'keypairs' in (Map)NewMap.
func XmlNewJson(xmlVal []byte, tagpairs ...string) ([]byte, error) {
m, err := NewMapXml(xmlVal)
if err != nil {
return nil, err
}
n, err := m.NewMap(tagpairs...)
if err != nil {
return nil, err
}
return n.Json()
}
// Wrap LeafNodes for XML.
// 'xmlVal' is an XML value
func XmlLeafNodes(xmlVal []byte) ([]LeafNode, error) {
m, err := NewMapXml(xmlVal)
if err != nil {
return nil, err
}
return m.LeafNodes(), nil
}
// Wrap LeafValues for XML.
// 'xmlVal' is an XML value
func XmlLeafValues(xmlVal []byte) ([]interface{}, error) {
m, err := NewMapXml(xmlVal)
if err != nil {
return nil, err
}
return m.LeafValues(), nil
}
// Wrap LeafPath for XML.
// 'xmlVal' is an XML value
func XmlLeafPath(xmlVal []byte) ([]string, error) {
m, err := NewMapXml(xmlVal)
if err != nil {
return nil, err
}
return m.LeafPaths(), nil
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,322 @@
package mxj
import (
"encoding/json"
"encoding/xml"
"fmt"
"io"
"os"
"testing"
)
func TestXml2Header(t *testing.T) {
fmt.Println("\n---------------- xml2_test.go ...")
}
func TestNewMapXml4(t *testing.T) {
x := []byte(`<doc>
<books>
<book seq="1">
<author>William T. Gaddis</author>
<title>The Recognitions</title>
<review>One of the great seminal American novels of the 20th century.</review>
</book>
<book seq="2">
<author>Austin Tappan Wright</author>
<title>Islandia</title>
<review>An example of earlier 20th century American utopian fiction.</review>
</book>
<book seq="3">
<author>John Hawkes</author>
<title>The Beetle Leg</title>
<review>A lyrical novel about the construction of Ft. Peck Dam in Montana.</review>
</book>
<book seq="4">
<author>
<first_name>T.E.</first_name>
<last_name>Porter</last_name>
</author>
<title>King's Day</title>
<review>A magical novella.</review>
</book>
</books>
</doc>`)
m, err := NewMapXml(x)
if err != nil && err != io.EOF {
t.Fatal("err:", err.Error())
}
fmt.Println("NewMapXml4, x:\n", string(x))
fmt.Println("NewMapXml4, m:\n", m)
fmt.Println("NewMapXml4, s:\n", m.StringIndent())
b, err := m.XmlIndent("", " ")
if err != nil {
t.Fatal("err:", err)
}
fmt.Println("NewMapXml4, b:\n", string(b))
}
func TestNewMapXml5(t *testing.T) {
fh, err := os.Open("songtext.xml")
if err != nil {
t.Fatal("err:", err.Error())
}
defer fh.Close()
m, raw, err := NewMapXmlReaderRaw(fh)
if err != nil && err != io.EOF {
t.Fatal("err:", err.Error())
}
fmt.Println("NewMapXml5, raw:\n", string(raw))
fmt.Println("NewMapXml5, m:\n", m)
fmt.Println("NewMapXml5, s:\n", m.StringIndent())
b, err := m.Xml()
if err != nil {
t.Fatal("err:", err)
}
fmt.Println("NewMapXml5, b:\n", string(b))
b, err = m.XmlIndent("", " ")
if err != nil {
t.Fatal("err:", err)
}
fmt.Println("NewMapXml5, b:\n", string(b))
}
func TestNewMapXml6(t *testing.T) {
fh, err := os.Open("atomFeedString.xml")
if err != nil {
t.Fatal("err:", err.Error())
}
defer fh.Close()
m, raw, err := NewMapXmlReaderRaw(fh)
if err != nil && err != io.EOF {
t.Fatal("err:", err.Error())
}
fmt.Println("NewMapXml6, raw:\n", string(raw))
fmt.Println("NewMapXml6, m:\n", m)
fmt.Println("NewMapXml6, s:\n", m.StringIndent())
b, err := m.Xml()
if err != nil {
t.Fatal("err:", err)
}
fmt.Println("NewMapXml6, b:\n", string(b))
b, err = m.XmlIndent("", " ")
if err != nil {
t.Fatal("err:", err)
}
fmt.Println("NewMapXml6, b:\n", string(b))
}
// ===================================== benchmarks ============================
var smallxml = []byte(`
<doc>
<words>
<word1>this</word1>
<word2>is</word2>
<word3>the</word3>
<word4>end</word4>
</words>
</doc>
`)
var smalljson = []byte(`{"doc":{"words":{"word1":"this","word2":"is","word3":"the","word4":"end"}}}`)
type words struct {
Word1 string `xml:"word1"`
Word2 string `xml:"word2"`
Word3 string `xml:"word3"`
Word4 string `xml:"word4"`
}
type xmldoc struct {
Words words `xml:"words"`
}
type jsondoc struct {
Doc xmldoc
}
func BenchmarkNewMapXml(b *testing.B) {
// var m Map
var err error
for i := 0; i < b.N; i++ {
if _, err = NewMapXml(smallxml); err != nil {
b.Fatal("err:", err)
}
}
// fmt.Println("m Map:", m)
}
func BenchmarkNewStructXml(b *testing.B) {
var s *xmldoc
var err error
for i := 0; i < b.N; i++ {
s = new(xmldoc)
if err = xml.Unmarshal(smallxml, s); err != nil {
b.Fatal("err:", err)
}
}
// fmt.Println("s xmldoc:", *s)
}
func BenchmarkNewMapJson(b *testing.B) {
var m map[string]interface{}
var err error
for i := 0; i < b.N; i++ {
m = make(map[string]interface{})
if err = json.Unmarshal(smalljson, &m); err != nil {
b.Fatal("err:", err)
}
}
// fmt.Println("m map:", m)
}
func BenchmarkNewStructJson(b *testing.B) {
var s *jsondoc
var err error
for i := 0; i < b.N; i++ {
s = new(jsondoc)
if err = json.Unmarshal(smalljson, s); err != nil {
b.Fatal("err:", err)
}
}
// fmt.Println("s jsondoc:", *s)
}
// ================== something with a little more content ... ===================
var xmlbooks = []byte(`
<doc>
<books>
<book seq="1">
<author>William T. Gaddis</author>
<title>The Recognitions</title>
<review>One of the great seminal American novels of the 20th century.</review>
</book>
<book seq="2">
<author>Austin Tappan Wright</author>
<title>Islandia</title>
<review>An example of earlier 20th century American utopian fiction.</review>
</book>
<book seq="3">
<author>John Hawkes</author>
<title>The Beetle Leg</title>
<review>A lyrical novel set during the construction of Ft. Peck Dam in Montana.</review>
</book>
<book seq="4">
<author>
<first_name>T.E.</first_name>
<last_name>Porter</last_name>
</author>
<title>King's Day</title>
<review>A magical novella.</review>
</book>
</books>
</doc>
`)
var jsonbooks = []byte(`
{"doc":
{"books":
{"book":[
{ "author":"William T. Gaddis",
"title":"The Recognitions",
"review":"One of the great seminal American novels of the 20th century."
},
{ "author":"Austin Tappan Wright",
"title":"Islandia",
"review":"An example of earlier 20th century American utopian fiction."
},
{ "author":"John Hawkes",
"title":"The Beetle Leg",
"review":"A lyrical novel set during the construction of Ft. Peck Dam in Montana."
},
{ "author":{"first_name":"T.E.", "last_name":"Porter"},
"title":"King's Day",
"review":"A magical novella."
}]
}
}
}
`)
type book struct {
Author string `xml:"author"`
Title string `xml:"title"`
Review string `xml:"review"`
}
type books struct {
Book []book `xml:"book"`
}
type doc struct {
Books books `xml:"books"`
}
type jsonbook struct {
Author json.RawMessage
Title string
Review string
}
type jsonbooks2 struct {
Book []jsonbook
}
type jsondoc1 struct {
Books jsonbooks2
}
type jsondoc2 struct {
Doc jsondoc1
}
func BenchmarkNewMapXmlBooks(b *testing.B) {
// var m Map
var err error
for i := 0; i < b.N; i++ {
if _, err = NewMapXml(xmlbooks); err != nil {
b.Fatal("err:", err)
}
}
// fmt.Println("m Map:", m)
}
func BenchmarkNewStructXmlBooks(b *testing.B) {
var s *doc
var err error
for i := 0; i < b.N; i++ {
s = new(doc)
if err = xml.Unmarshal(xmlbooks, s); err != nil {
b.Fatal("err:", err)
}
}
// fmt.Println("s doc:", *s)
}
func BenchmarkNewMapJsonBooks(b *testing.B) {
var m map[string]interface{}
var err error
for i := 0; i < b.N; i++ {
m = make(map[string]interface{})
if err = json.Unmarshal(jsonbooks, &m); err != nil {
b.Fatal("err:", err)
}
}
// fmt.Println("m map:", m)
}
func BenchmarkNewStructJsonBooks(b *testing.B) {
var s *jsondoc2
var err error
for i := 0; i < b.N; i++ {
s = new(jsondoc2)
if err = json.Unmarshal(jsonbooks, s); err != nil {
b.Fatal("err:", err)
}
}
// fmt.Println("s jsondoc2:", *s)
}

View File

@ -0,0 +1,47 @@
// xml3_test.go - patch tests
package mxj
import (
"fmt"
"testing"
)
func TestXml3(t *testing.T) {
fmt.Println("\n------------ xml3_test.go")
}
// for: https://github.com/clbanning/mxj/pull/26
func TestOnlyAttributes(t *testing.T) {
fmt.Println("========== TestOnlyAttributes")
dom, err := NewMapXml([]byte(`
<memballoon model="virtio">
<address type="pci" domain="0x0000" bus="0x00" slot="0x05" function="0x0"/>
<empty/>
</memballoon>)`))
if err != nil {
t.Fatal(err)
}
xml, err := dom.XmlIndent("", " ")
if err != nil {
t.Fatal(err)
}
fmt.Println(string(xml))
}
func TestOnlyAttributesSeq(t *testing.T) {
fmt.Println("========== TestOnlyAttributesSeq")
dom, err := NewMapXmlSeq([]byte(`
<memballoon model="virtio">
<address type="pci" domain="0x0000" bus="0x00" slot="0x05" function="0x0"/>
<empty/>
</memballoon>)`))
if err != nil {
t.Fatal(err)
}
xml, err := dom.XmlSeqIndent("", " ")
if err != nil {
t.Fatal(err)
}
fmt.Println(string(xml))
}

View File

@ -0,0 +1,241 @@
package mxj
import (
"bytes"
"fmt"
"io"
"reflect"
"testing"
)
func TestXmlHeader(t *testing.T) {
fmt.Println("\n---------------- xml_test.go ...")
}
func TestNewMapXml(t *testing.T) {
x := []byte(`<root2><newtag newattr="some_attr_value">something more</newtag><list listattr="val"><item>1</item><item>2</item></list></root2>`)
mv, merr := NewMapXml(x)
if merr != nil {
t.Fatal("merr:", merr.Error())
}
want := Map{"root2":
map[string]interface{}{
"newtag":
map[string]interface{}{"-newattr": "some_attr_value", "#text":"something more"},
"list":
map[string]interface{}{"-listattr":"val", "item":[]interface{}{"1", "2"}},
}}
if !reflect.DeepEqual(mv, want) {
fmt.Println("NewMapXml, x :", string(x))
fmt.Printf("NewMapXml, mv : %#v\n", mv)
fmt.Printf("NewMapXml, want: %#v\n", want)
t.Fatal("not DeepEqual")
}
}
func TestAttrHyphenFalse(t *testing.T) {
PrependAttrWithHyphen(false)
defer PrependAttrWithHyphen(true)
x := []byte(`<root2><newtag newattr="some_attr_value">something more</newtag><list listattr="val"><item>1</item><item>2</item></list></root2>`)
mv, merr := NewMapXml(x)
if merr != nil {
t.Fatal("merr:", merr.Error())
}
want := Map{"root2":
map[string]interface{}{
"newtag":
map[string]interface{}{"newattr": "some_attr_value", "#text":"something more"},
"list":
map[string]interface{}{"listattr":"val", "item":[]interface{}{"1", "2"}},
}}
if !reflect.DeepEqual(mv, want) {
fmt.Println("AttrHyphenFalse, x :", string(x))
fmt.Printf("AttrHyphenFalse, mv : %#v\n", mv)
fmt.Printf("AttrHyphenFalse, want: %#v\n", want)
t.Fatal("not DeepEqual")
}
}
func TestNewMapXmlError(t *testing.T) {
x := []byte(`<root2><newtag>something more</newtag><list><item>1</item><item>2</item></list>`)
m, merr := NewMapJson(x)
if merr == nil {
t.Fatal("NewMapXmlError, m:", m)
}
want := `invalid character '<' looking for beginning of value`
if merr != nil && merr.Error() != want {
fmt.Println("NewMapXmlError, x :", string(x))
fmt.Println("NewMapXmlError, merr:", merr.Error())
fmt.Println("NewMapXmlError, want:", want)
}
}
func TestNewMapXmlReader(t *testing.T) {
fmt.Println("\n==================== TestNewMapXmlReader ...")
x := []byte(`<root><this>is a test</this></root><root2><newtag>something more</newtag><list><item>1</item><item>2</item></list></root2>`)
r := bytes.NewReader(x)
for {
m, raw, err := NewMapXmlReaderRaw(r)
if err != nil && err != io.EOF {
t.Fatal("err:", err.Error())
}
if err == io.EOF && len(m) == 0 {
break
}
fmt.Println("NewMapXmlReader, raw:", string(raw))
fmt.Println("NewMapXmlReader, m :", m)
}
}
// --------------------- Xml() and XmlWriter() test cases -------------------
func TestXml_1(t *testing.T) {
mv := Map{"tag1": "some data", "tag2": "more data", "boolean": true, "float": 3.14159625, "null": nil}
x, err := mv.Xml()
if err != nil {
t.Fatal("err:", err.Error())
}
fmt.Println("Xml_1, mv:", mv)
fmt.Println("Xml_1, x :", string(x))
}
func TestXml_2(t *testing.T) {
a := []interface{}{"string", true, 36.4}
mv := Map{"array": a}
x, err := mv.Xml()
if err != nil {
t.Fatal("err:", err.Error())
}
fmt.Println("Xml_2, mv:", mv)
fmt.Println("Xml_2, x :", string(x))
}
func TestXml_3(t *testing.T) {
a := []interface{}{"string", true, 36.4}
mv := Map{"array": []interface{}{a, "string2"}}
x, err := mv.Xml()
if err != nil {
t.Fatal("err:", err.Error())
}
fmt.Println("Xml_3, mv:", mv)
fmt.Println("Xml_3, x :", string(x))
}
func TestXml_4(t *testing.T) {
a := []interface{}{"string", true, 36.4}
mv := Map{"array": map[string]interface{}{"innerkey": []interface{}{a, "string2"}}}
x, err := mv.Xml()
if err != nil {
t.Fatal("err:", err.Error())
}
fmt.Println("Xml_4, mv:", mv)
fmt.Println("Xml_4, x :", string(x))
}
func TestXml_5(t *testing.T) {
a := []interface{}{"string", true, 36.4}
mv := Map{"array": []interface{}{map[string]interface{}{"innerkey": []interface{}{a, "string2"}}, map[string]interface{}{"some": "more"}}}
x, err := mv.Xml()
if err != nil {
t.Fatal("err:", err.Error())
}
fmt.Println("Xml_5, mv:", mv)
fmt.Println("Xml_5, x :", string(x))
}
func TestXml_Strings(t *testing.T) {
mv := Map{"sometag": "some data", "strings": []string{"string1", "string2"}}
x, err := mv.Xml()
if err != nil {
t.Fatal("err:", err.Error())
}
fmt.Println("Xml_strings, mv:", mv)
fmt.Println("Xml_strings, x :", string(x))
}
func TestXmlWriter(t *testing.T) {
mv := Map{"tag1": "some data", "tag2": "more data", "boolean": true, "float": 3.14159625}
w := new(bytes.Buffer)
raw, err := mv.XmlWriterRaw(w, "myRootTag")
if err != nil {
t.Fatal("err:", err.Error())
}
b := make([]byte, w.Len())
_, err = w.Read(b)
if err != nil {
t.Fatal("err:", err.Error())
}
fmt.Println("XmlWriter, raw:", string(raw))
fmt.Println("XmlWriter, b :", string(b))
}
// -------------------------- XML Handler test cases -------------------------
/* tested in bulk_test.go ...
var xhdata = []byte(`<root><this>is a test</this></root><root2><newtag>something more</newtag><list><item>1</item><item>2</item></list></root2><root3><tag></root3>`)
func TestHandleXmlReader(t *testing.T) {
fmt.Println("HandleXmlReader:", string(xhdata))
rdr := bytes.NewReader(xhdata)
err := HandleXmlReader(rdr, xmhandler, xehandler)
if err != nil {
t.Fatal("err:", err.Error())
}
}
var xt *testing.T
func xmhandler(m Map, raw []byte) bool {
x, xerr := m.Xml()
if xerr != nil {
xt.Fatal("... xmhandler:", xerr.Error())
return false
}
fmt.Println("... xmhandler, raw:", string(raw))
fmt.Println("... xmhandler, x :", string(x))
return true
}
func xehandler(err error, raw []byte) bool {
if err == nil {
// shouldn't be here
xt.Fatal("... xehandler: <nil>")
return false
}
if err == io.EOF {
return true
}
fmt.Println("... xehandler raw:", string(raw))
fmt.Println("... xehandler err:", err.Error())
return true
}
*/

View File

@ -0,0 +1,828 @@
// Copyright 2012-2016 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
// xmlseq.go - version of xml.go with sequence # injection on Decoding and sorting on Encoding.
// Also, handles comments, directives and process instructions.
package mxj
import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"io"
"sort"
"strings"
)
var NoRoot = errors.New("no root key")
var NO_ROOT = NoRoot // maintain backwards compatibility
// ------------------- NewMapXmlSeq & NewMapXmlSeqReader ... -------------------------
// This is only useful if you want to re-encode the Map as XML using mv.XmlSeq(), etc., to preserve the original structure.
// The xml.Decoder.RawToken method is used to parse the XML, so there is no checking for appropriate xml.EndElement values;
// thus, it is assumed that the XML is valid.
//
// NewMapXmlSeq - convert a XML doc into a Map with elements id'd with decoding sequence int - #seq.
// If the optional argument 'cast' is 'true', then values will be converted to boolean or float64 if possible.
// NOTE: "#seq" key/value pairs are removed on encoding with mv.XmlSeq() / mv.XmlSeqIndent().
// • attributes are a map - map["#attr"]map["attr_key"]map[string]interface{}{"#text":<aval>, "#seq":<num>}
// • all simple elements are decoded as map["#text"]interface{} with a "#seq" k:v pair, as well.
// • lists always decode as map["list_tag"][]map[string]interface{} where the array elements are maps that
// include a "#seq" k:v pair based on sequence they are decoded. Thus, XML like:
// <doc>
// <ltag>value 1</ltag>
// <newtag>value 2</newtag>
// <ltag>value 3</ltag>
// </doc>
// is decoded as:
// doc :
// ltag :[[]interface{}]
// [item: 0]
// #seq :[int] 0
// #text :[string] value 1
// [item: 1]
// #seq :[int] 2
// #text :[string] value 3
// newtag :
// #seq :[int] 1
// #text :[string] value 2
// It will encode in proper sequence even though the Map representation merges all "ltag" elements in an array.
// • comments - "<!--comment-->" - are decoded as map["#comment"]map["#text"]"cmnt_text" with a "#seq" k:v pair.
// • directives - "<!text>" - are decoded as map["#directive"]map[#text"]"directive_text" with a "#seq" k:v pair.
// • process instructions - "<?instr?>" - are decoded as map["#procinst"]interface{} where the #procinst value
// is of map[string]interface{} type with the following keys: #target, #inst, and #seq.
// • comments, directives, and procinsts that are NOT part of a document with a root key will be returned as
// map[string]interface{} and the error value 'NoRoot'.
// • note: "<![CDATA[" syntax is lost in xml.Decode parser - and is not handled here, either.
// and: "\r\n" is converted to "\n"
//
// NOTES:
// 1. The 'xmlVal' will be parsed looking for an xml.StartElement, xml.Comment, etc., so BOM and other
// extraneous xml.CharData will be ignored unless io.EOF is reached first.
// 2. CoerceKeysToLower() is NOT recognized, since the intent here is to eventually call m.XmlSeq() to
// re-encode the message in its original structure.
// 3. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case.
//
// NAME SPACES:
// 1. Keys in the Map value that are parsed from a <name space prefix>:<local name> tag preserve the
// "<prefix>:" notation rather than stripping it as with NewMapXml().
// 2. Attribute keys for name space prefix declarations preserve "xmlns:<prefix>" notation.
func NewMapXmlSeq(xmlVal []byte, cast ...bool) (Map, error) {
var r bool
if len(cast) == 1 {
r = cast[0]
}
return xmlSeqToMap(xmlVal, r)
}
// This is only useful if you want to re-encode the Map as XML using mv.XmlSeq(), etc., to preserve the original structure.
//
// Get next XML doc from an io.Reader as a Map value. Returns Map value.
// NOTES:
// 1. The 'xmlReader' will be parsed looking for an xml.StartElement, xml.Comment, etc., so BOM and other
// extraneous xml.CharData will be ignored unless io.EOF is reached first.
// 2. CoerceKeysToLower() is NOT recognized, since the intent here is to eventually call m.XmlSeq() to
// re-encode the message in its original structure.
// 3. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case.
func NewMapXmlSeqReader(xmlReader io.Reader, cast ...bool) (Map, error) {
var r bool
if len(cast) == 1 {
r = cast[0]
}
// We need to put an *os.File reader in a ByteReader or the xml.NewDecoder
// will wrap it in a bufio.Reader and seek on the file beyond where the
// xml.Decoder parses!
if _, ok := xmlReader.(io.ByteReader); !ok {
xmlReader = myByteReader(xmlReader) // see code at EOF
}
// build the map
return xmlSeqReaderToMap(xmlReader, r)
}
// This is only useful if you want to re-encode the Map as XML using mv.XmlSeq(), etc., to preserve the original structure.
//
// Get next XML doc from an io.Reader as a Map value. Returns Map value and slice with the raw XML.
// NOTES:
// 1. Due to the implementation of xml.Decoder, the raw XML off the reader is buffered to []byte
// using a ByteReader. If the io.Reader is an os.File, there may be significant performance impact.
// See the examples - getmetrics1.go through getmetrics4.go - for comparative use cases on a large
// data set. If the io.Reader is wrapping a []byte value in-memory, however, such as http.Request.Body
// you CAN use it to efficiently unmarshal a XML doc and retrieve the raw XML in a single call.
// 2. The 'raw' return value may be larger than the XML text value.
// 3. The 'xmlReader' will be parsed looking for an xml.StartElement, xml.Comment, etc., so BOM and other
// extraneous xml.CharData will be ignored unless io.EOF is reached first.
// 4. CoerceKeysToLower() is NOT recognized, since the intent here is to eventually call m.XmlSeq() to
// re-encode the message in its original structure.
// 5. If CoerceKeysToSnakeCase() has been called, then all key values will be converted to snake case.
func NewMapXmlSeqReaderRaw(xmlReader io.Reader, cast ...bool) (Map, []byte, error) {
var r bool
if len(cast) == 1 {
r = cast[0]
}
// create TeeReader so we can retrieve raw XML
buf := make([]byte, 0)
wb := bytes.NewBuffer(buf)
trdr := myTeeReader(xmlReader, wb)
m, err := xmlSeqReaderToMap(trdr, r)
// retrieve the raw XML that was decoded
b := wb.Bytes()
// err may be NoRoot
return m, b, err
}
// xmlSeqReaderToMap() - parse a XML io.Reader to a map[string]interface{} value
func xmlSeqReaderToMap(rdr io.Reader, r bool) (map[string]interface{}, error) {
// parse the Reader
p := xml.NewDecoder(rdr)
if CustomDecoder != nil {
useCustomDecoder(p)
} else {
p.CharsetReader = XmlCharsetReader
}
return xmlSeqToMapParser("", nil, p, r)
}
// xmlSeqToMap - convert a XML doc into map[string]interface{} value
func xmlSeqToMap(doc []byte, r bool) (map[string]interface{}, error) {
b := bytes.NewReader(doc)
p := xml.NewDecoder(b)
if CustomDecoder != nil {
useCustomDecoder(p)
} else {
p.CharsetReader = XmlCharsetReader
}
return xmlSeqToMapParser("", nil, p, r)
}
// ===================================== where the work happens =============================
// xmlSeqToMapParser - load a 'clean' XML doc into a map[string]interface{} directly.
// Add #seq tag value for each element decoded - to be used for Encoding later.
func xmlSeqToMapParser(skey string, a []xml.Attr, p *xml.Decoder, r bool) (map[string]interface{}, error) {
if snakeCaseKeys {
skey = strings.Replace(skey, "-", "_", -1)
}
// NOTE: all attributes and sub-elements parsed into 'na', 'na' is returned as value for 'skey' in 'n'.
var n, na map[string]interface{}
var seq int // for including seq num when decoding
// Allocate maps and load attributes, if any.
// NOTE: on entry from NewMapXml(), etc., skey=="", and we fall through
// to get StartElement then recurse with skey==xml.StartElement.Name.Local
// where we begin allocating map[string]interface{} values 'n' and 'na'.
if skey != "" {
// 'n' only needs one slot - save call to runtime•hashGrow()
// 'na' we don't know
n = make(map[string]interface{}, 1)
na = make(map[string]interface{})
if len(a) > 0 {
// xml.Attr is decoded into: map["#attr"]map[<attr_label>]interface{}
// where interface{} is map[string]interface{}{"#text":<attr_val>, "#seq":<attr_seq>}
aa := make(map[string]interface{}, len(a))
for i, v := range a {
if snakeCaseKeys {
v.Name.Local = strings.Replace(v.Name.Local, "-", "_", -1)
}
if len(v.Name.Space) > 0 {
aa[v.Name.Space+`:`+v.Name.Local] = map[string]interface{}{"#text": cast(v.Value, r), "#seq": i}
} else {
aa[v.Name.Local] = map[string]interface{}{"#text": cast(v.Value, r), "#seq": i}
}
}
na["#attr"] = aa
}
}
// Return XMPP <stream:stream> message.
if handleXMPPStreamTag && skey == "stream:stream" {
n[skey] = na
return n, nil
}
for {
t, err := p.RawToken()
if err != nil {
if err != io.EOF {
return nil, errors.New("xml.Decoder.Token() - " + err.Error())
}
return nil, err
}
switch t.(type) {
case xml.StartElement:
tt := t.(xml.StartElement)
// First call to xmlSeqToMapParser() doesn't pass xml.StartElement - the map key.
// So when the loop is first entered, the first token is the root tag along
// with any attributes, which we process here.
//
// Subsequent calls to xmlSeqToMapParser() will pass in tag+attributes for
// processing before getting the next token which is the element value,
// which is done above.
if skey == "" {
if len(tt.Name.Space) > 0 {
return xmlSeqToMapParser(tt.Name.Space+`:`+tt.Name.Local, tt.Attr, p, r)
} else {
return xmlSeqToMapParser(tt.Name.Local, tt.Attr, p, r)
}
}
// If not initializing the map, parse the element.
// len(nn) == 1, necessarily - it is just an 'n'.
var nn map[string]interface{}
if len(tt.Name.Space) > 0 {
nn, err = xmlSeqToMapParser(tt.Name.Space+`:`+tt.Name.Local, tt.Attr, p, r)
} else {
nn, err = xmlSeqToMapParser(tt.Name.Local, tt.Attr, p, r)
}
if err != nil {
return nil, err
}
// The nn map[string]interface{} value is a na[nn_key] value.
// We need to see if nn_key already exists - means we're parsing a list.
// This may require converting na[nn_key] value into []interface{} type.
// First, extract the key:val for the map - it's a singleton.
var key string
var val interface{}
for key, val = range nn {
break
}
// add "#seq" k:v pair -
// Sequence number included even in list elements - this should allow us
// to properly resequence even something goofy like:
// <list>item 1</list>
// <subelement>item 2</subelement>
// <list>item 3</list>
// where all the "list" subelements are decoded into an array.
switch val.(type) {
case map[string]interface{}:
val.(map[string]interface{})["#seq"] = seq
seq++
case interface{}: // a non-nil simple element: string, float64, bool
v := map[string]interface{}{"#text": val, "#seq": seq}
seq++
val = v
}
// 'na' holding sub-elements of n.
// See if 'key' already exists.
// If 'key' exists, then this is a list, if not just add key:val to na.
if v, ok := na[key]; ok {
var a []interface{}
switch v.(type) {
case []interface{}:
a = v.([]interface{})
default: // anything else - note: v.(type) != nil
a = []interface{}{v}
}
a = append(a, val)
na[key] = a
} else {
na[key] = val // save it as a singleton
}
case xml.EndElement:
if skey != "" {
tt := t.(xml.EndElement)
if snakeCaseKeys {
tt.Name.Local = strings.Replace(tt.Name.Local, "-", "_", -1)
}
var name string
if len(tt.Name.Space) > 0 {
name = tt.Name.Space + `:` + tt.Name.Local
} else {
name = tt.Name.Local
}
if skey != name {
return nil, fmt.Errorf("element %s not properly terminated, got %s at #%d",
skey, name, p.InputOffset())
}
}
// len(n) > 0 if this is a simple element w/o xml.Attrs - see xml.CharData case.
if len(n) == 0 {
// If len(na)==0 we have an empty element == "";
// it has no xml.Attr nor xml.CharData.
// Empty element content will be map["etag"]map["#text"]""
// after #seq injection - map["etag"]map["#seq"]seq - after return.
if len(na) > 0 {
n[skey] = na
} else {
n[skey] = "" // empty element
}
}
return n, nil
case xml.CharData:
// clean up possible noise
tt := strings.Trim(string(t.(xml.CharData)), "\t\r\b\n ")
if skey == "" {
// per Adrian (http://www.adrianlungu.com/) catch stray text
// in decoder stream -
// https://github.com/clbanning/mxj/pull/14#issuecomment-182816374
// NOTE: CharSetReader must be set to non-UTF-8 CharSet or you'll get
// a p.Token() decoding error when the BOM is UTF-16 or UTF-32.
continue
}
if len(tt) > 0 {
// every simple element is a #text and has #seq associated with it
na["#text"] = cast(tt, r)
na["#seq"] = seq
seq++
}
case xml.Comment:
if n == nil { // no root 'key'
n = map[string]interface{}{"#comment": string(t.(xml.Comment))}
return n, NoRoot
}
cm := make(map[string]interface{}, 2)
cm["#text"] = string(t.(xml.Comment))
cm["#seq"] = seq
seq++
na["#comment"] = cm
case xml.Directive:
if n == nil { // no root 'key'
n = map[string]interface{}{"#directive": string(t.(xml.Directive))}
return n, NoRoot
}
dm := make(map[string]interface{}, 2)
dm["#text"] = string(t.(xml.Directive))
dm["#seq"] = seq
seq++
na["#directive"] = dm
case xml.ProcInst:
if n == nil {
na = map[string]interface{}{"#target": t.(xml.ProcInst).Target, "#inst": string(t.(xml.ProcInst).Inst)}
n = map[string]interface{}{"#procinst": na}
return n, NoRoot
}
pm := make(map[string]interface{}, 3)
pm["#target"] = t.(xml.ProcInst).Target
pm["#inst"] = string(t.(xml.ProcInst).Inst)
pm["#seq"] = seq
seq++
na["#procinst"] = pm
default:
// noop - shouldn't ever get here, now, since we handle all token types
}
}
}
// ------------------ END: NewMapXml & NewMapXmlReader -------------------------
// --------------------- mv.XmlSeq & mv.XmlSeqWriter -------------------------
// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Encode a Map as XML with elements sorted on #seq. The companion of NewMapXmlSeq().
// The following rules apply.
// - The key label "#text" is treated as the value for a simple element with attributes.
// - The "#seq" key is used to seqence the subelements or attributes but is ignored for writing.
// - The "#attr" map key identifies the map of attribute map[string]interface{} values with "#text" key.
// - The "#comment" map key identifies a comment in the value "#text" map entry - <!--comment-->.
// - The "#directive" map key identifies a directive in the value "#text" map entry - <!directive>.
// - The "#procinst" map key identifies a process instruction in the value "#target" and "#inst"
// map entries - <?target inst?>.
// - Value type encoding:
// > string, bool, float64, int, int32, int64, float32: per "%v" formating
// > []bool, []uint8: by casting to string
// > structures, etc.: handed to xml.Marshal() - if there is an error, the element
// value is "UNKNOWN"
// - Elements with only attribute values or are null are terminated using "/>" unless XmlGoEmptyElemSystax() called.
// - If len(mv) == 1 and no rootTag is provided, then the map key is used as the root tag, possible.
// Thus, `{ "key":"value" }` encodes as "<key>value</key>".
func (mv Map) XmlSeq(rootTag ...string) ([]byte, error) {
m := map[string]interface{}(mv)
var err error
s := new(string)
p := new(pretty) // just a stub
if len(m) == 1 && len(rootTag) == 0 {
for key, value := range m {
// if it's an array, see if all values are map[string]interface{}
// we force a new root tag if we'll end up with no key:value in the list
// so: key:[string_val, bool:true] --> <doc><key>string_val</key><bool>true</bool></doc>
switch value.(type) {
case []interface{}:
for _, v := range value.([]interface{}) {
switch v.(type) {
case map[string]interface{}: // noop
default: // anything else
err = mapToXmlSeqIndent(false, s, DefaultRootTag, m, p)
goto done
}
}
}
err = mapToXmlSeqIndent(false, s, key, value, p)
}
} else if len(rootTag) == 1 {
err = mapToXmlSeqIndent(false, s, rootTag[0], m, p)
} else {
err = mapToXmlSeqIndent(false, s, DefaultRootTag, m, p)
}
done:
return []byte(*s), err
}
// The following implementation is provided only for symmetry with NewMapXmlReader[Raw]
// The names will also provide a key for the number of return arguments.
// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Writes the Map as XML on the Writer.
// See XmlSeq() for encoding rules.
func (mv Map) XmlSeqWriter(xmlWriter io.Writer, rootTag ...string) error {
x, err := mv.XmlSeq(rootTag...)
if err != nil {
return err
}
_, err = xmlWriter.Write(x)
return err
}
// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Writes the Map as XML on the Writer. []byte is the raw XML that was written.
// See XmlSeq() for encoding rules.
func (mv Map) XmlSeqWriterRaw(xmlWriter io.Writer, rootTag ...string) ([]byte, error) {
x, err := mv.XmlSeq(rootTag...)
if err != nil {
return x, err
}
_, err = xmlWriter.Write(x)
return x, err
}
// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Writes the Map as pretty XML on the Writer.
// See Xml() for encoding rules.
func (mv Map) XmlSeqIndentWriter(xmlWriter io.Writer, prefix, indent string, rootTag ...string) error {
x, err := mv.XmlSeqIndent(prefix, indent, rootTag...)
if err != nil {
return err
}
_, err = xmlWriter.Write(x)
return err
}
// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Writes the Map as pretty XML on the Writer. []byte is the raw XML that was written.
// See XmlSeq() for encoding rules.
func (mv Map) XmlSeqIndentWriterRaw(xmlWriter io.Writer, prefix, indent string, rootTag ...string) ([]byte, error) {
x, err := mv.XmlSeqIndent(prefix, indent, rootTag...)
if err != nil {
return x, err
}
_, err = xmlWriter.Write(x)
return x, err
}
// -------------------- END: mv.Xml & mv.XmlWriter -------------------------------
// ---------------------- XmlSeqIndent ----------------------------
// This should ONLY be used on Map values that were decoded using NewMapXmlSeq() & co.
//
// Encode a map[string]interface{} as a pretty XML string.
// See mv.XmlSeq() for encoding rules.
func (mv Map) XmlSeqIndent(prefix, indent string, rootTag ...string) ([]byte, error) {
m := map[string]interface{}(mv)
var err error
s := new(string)
p := new(pretty)
p.indent = indent
p.padding = prefix
if len(m) == 1 && len(rootTag) == 0 {
// this can extract the key for the single map element
// use it if it isn't a key for a list
for key, value := range m {
if _, ok := value.([]interface{}); ok {
err = mapToXmlSeqIndent(true, s, DefaultRootTag, m, p)
} else {
err = mapToXmlSeqIndent(true, s, key, value, p)
}
}
} else if len(rootTag) == 1 {
err = mapToXmlSeqIndent(true, s, rootTag[0], m, p)
} else {
err = mapToXmlSeqIndent(true, s, DefaultRootTag, m, p)
}
return []byte(*s), err
}
// where the work actually happens
// returns an error if an attribute is not atomic
func mapToXmlSeqIndent(doIndent bool, s *string, key string, value interface{}, pp *pretty) error {
var endTag bool
var isSimple bool
var noEndTag bool
var elen int
var ss string
p := &pretty{pp.indent, pp.cnt, pp.padding, pp.mapDepth, pp.start}
switch value.(type) {
case map[string]interface{}, []byte, string, float64, bool, int, int32, int64, float32:
if doIndent {
*s += p.padding
}
if key != "#comment" && key != "#directive" && key != "#procinst" {
*s += `<` + key
}
}
switch value.(type) {
case map[string]interface{}:
val := value.(map[string]interface{})
if key == "#comment" {
*s += `<!--` + val["#text"].(string) + `-->`
noEndTag = true
break
}
if key == "#directive" {
*s += `<!` + val["#text"].(string) + `>`
noEndTag = true
break
}
if key == "#procinst" {
*s += `<?` + val["#target"].(string) + ` ` + val["#inst"].(string) + `?>`
noEndTag = true
break
}
haveAttrs := false
// process attributes first
if v, ok := val["#attr"].(map[string]interface{}); ok {
// First, unroll the map[string]interface{} into a []keyval array.
// Then sequence it.
kv := make([]keyval, len(v))
n := 0
for ak, av := range v {
kv[n] = keyval{ak, av}
n++
}
sort.Sort(elemListSeq(kv))
// Now encode the attributes in original decoding sequence, using keyval array.
for _, a := range kv {
vv := a.v.(map[string]interface{})
switch vv["#text"].(type) {
case string:
if xmlEscapeChars {
ss = escapeChars(vv["#text"].(string))
} else {
ss = vv["#text"].(string)
}
*s += ` ` + a.k + `="` + ss + `"`
case float64, bool, int, int32, int64, float32:
*s += ` ` + a.k + `="` + fmt.Sprintf("%v", vv["#text"]) + `"`
case []byte:
if xmlEscapeChars {
ss = escapeChars(string(vv["#text"].([]byte)))
} else {
ss = string(vv["#text"].([]byte))
}
*s += ` ` + a.k + `="` + ss + `"`
default:
return fmt.Errorf("invalid attribute value for: %s", a.k)
}
}
haveAttrs = true
}
// simple element?
// every map value has, at least, "#seq" and, perhaps, "#text" and/or "#attr"
_, seqOK := val["#seq"] // have key
if v, ok := val["#text"]; ok && ((len(val) == 3 && haveAttrs) || (len(val) == 2 && !haveAttrs)) && seqOK {
if stmp, ok := v.(string); ok && stmp != "" {
if xmlEscapeChars {
stmp = escapeChars(stmp)
}
*s += ">" + stmp
endTag = true
elen = 1
}
isSimple = true
break
} else if !ok && ((len(val) == 2 && haveAttrs) || (len(val) == 1 && !haveAttrs)) && seqOK {
// here no #text but have #seq or #seq+#attr
endTag = false
break
}
// we now need to sequence everything except attributes
// 'kv' will hold everything that needs to be written
kv := make([]keyval, 0)
for k, v := range val {
if k == "#attr" { // already processed
continue
}
if k == "#seq" { // ignore - just for sorting
continue
}
switch v.(type) {
case []interface{}:
// unwind the array as separate entries
for _, vv := range v.([]interface{}) {
kv = append(kv, keyval{k, vv})
}
default:
kv = append(kv, keyval{k, v})
}
}
// close tag with possible attributes
*s += ">"
if doIndent {
*s += "\n"
}
// something more complex
p.mapDepth++
sort.Sort(elemListSeq(kv))
i := 0
for _, v := range kv {
switch v.v.(type) {
case []interface{}:
default:
if i == 0 && doIndent {
p.Indent()
}
}
i++
if err := mapToXmlSeqIndent(doIndent, s, v.k, v.v, p); err != nil {
return err
}
switch v.v.(type) {
case []interface{}: // handled in []interface{} case
default:
if doIndent {
p.Outdent()
}
}
i--
}
p.mapDepth--
endTag = true
elen = 1 // we do have some content other than attrs
case []interface{}:
for _, v := range value.([]interface{}) {
if doIndent {
p.Indent()
}
if err := mapToXmlSeqIndent(doIndent, s, key, v, p); err != nil {
return err
}
if doIndent {
p.Outdent()
}
}
return nil
case nil:
// terminate the tag
if doIndent {
*s += p.padding
}
*s += "<" + key
endTag, isSimple = true, true
break
default: // handle anything - even goofy stuff
elen = 0
switch value.(type) {
case string:
if xmlEscapeChars {
ss = escapeChars(value.(string))
} else {
ss = value.(string)
}
elen = len(ss)
if elen > 0 {
*s += ">" + ss
}
case float64, bool, int, int32, int64, float32:
v := fmt.Sprintf("%v", value)
elen = len(v)
if elen > 0 {
*s += ">" + v
}
case []byte: // NOTE: byte is just an alias for uint8
// similar to how xml.Marshal handles []byte structure members
if xmlEscapeChars {
ss = escapeChars(string(value.([]byte)))
} else {
ss = string(value.([]byte))
}
elen = len(ss)
if elen > 0 {
*s += ">" + ss
}
default:
var v []byte
var err error
if doIndent {
v, err = xml.MarshalIndent(value, p.padding, p.indent)
} else {
v, err = xml.Marshal(value)
}
if err != nil {
*s += ">UNKNOWN"
} else {
elen = len(v)
if elen > 0 {
*s += string(v)
}
}
}
isSimple = true
endTag = true
}
if endTag && !noEndTag {
if doIndent {
if !isSimple {
*s += p.padding
}
}
switch value.(type) {
case map[string]interface{}, []byte, string, float64, bool, int, int32, int64, float32:
if elen > 0 || useGoXmlEmptyElemSyntax {
if elen == 0 {
*s += ">"
}
*s += `</` + key + ">"
} else {
*s += `/>`
}
}
} else if !noEndTag {
if useGoXmlEmptyElemSyntax {
*s += `</` + key + ">"
// *s += "></" + key + ">"
} else {
*s += "/>"
}
}
if doIndent {
if p.cnt > p.start {
*s += "\n"
}
p.Outdent()
}
return nil
}
// the element sort implementation
type keyval struct {
k string
v interface{}
}
type elemListSeq []keyval
func (e elemListSeq) Len() int {
return len(e)
}
func (e elemListSeq) Swap(i, j int) {
e[i], e[j] = e[j], e[i]
}
func (e elemListSeq) Less(i, j int) bool {
var iseq, jseq int
var ok bool
if iseq, ok = e[i].v.(map[string]interface{})["#seq"].(int); !ok {
iseq = 9999999
}
if jseq, ok = e[j].v.(map[string]interface{})["#seq"].(int); !ok {
jseq = 9999999
}
return iseq <= jseq
}
// =============== https://groups.google.com/forum/#!topic/golang-nuts/lHPOHD-8qio
// BeautifyXml (re)formats an XML doc similar to Map.XmlIndent().
func BeautifyXml(b []byte, prefix, indent string) ([]byte, error) {
x, err := NewMapXmlSeq(b)
if err != nil {
return nil, err
}
return x.XmlSeqIndent(prefix, indent)
}

View File

@ -0,0 +1,88 @@
package mxj
import (
"fmt"
"io"
"testing"
)
func TestXmlSeqHeader(t *testing.T) {
fmt.Println("\n---------------- xmlseq_test.go ...")
}
func TestNewMapXmlSeq(t *testing.T) {
x := []byte(`<doc>
<books>
<book seq="1">
<author>William T. Gaddis</author>
<review>Gaddis is one of the most influential but little know authors in America.</review>
<title>The Recognitions</title>
<!-- here's the rest of the review -->
<review>One of the great seminal American novels of the 20th century.</review>
<review>Without it Thomas Pynchon probably wouldn't have written Gravity's Rainbow.</review>
</book>
<book seq="2">
<author>Austin Tappan Wright</author>
<title>Islandia</title>
<review>An example of earlier 20th century American utopian fiction.</review>
</book>
<book>
<author>John Hawkes</author>
<title>The Beetle Leg</title>
<!throw in a directive here>
<review>A lyrical novel about the construction of Ft. Peck Dam in Montana.</review>
</book>
<book>
<author>
<?cat first_name last_name?>
<first_name>T.E.</first_name>
<last_name>Porter</last_name>
</author>
<title>King's Day</title>
<review>A magical novella.</review>
</book>
</books>
</doc>`)
m, err := NewMapXmlSeq(x)
if err != nil && err != io.EOF {
t.Fatal("err:", err.Error())
}
fmt.Println("NewMapXmlSeq, x:\n", string(x))
// fmt.Println("NewMapXmlSeq, m:\n", m)
fmt.Println("NewMapXmlSeq, s:\n", m.StringIndent())
b, err := m.XmlIndent("", " ")
if err == nil {
fmt.Println("NewMapXmlSeq, mv.XmlIndent():\n", string(b))
t.Fatal("didn't catch invalid key: #text")
}
fmt.Println("err ok:", err)
b, err = m.XmlSeqIndent("", " ")
if err != nil {
t.Fatal("err:", err)
}
fmt.Println("NewMapXmlSeq, mv.XmlSeqIndent():\n", string(b))
}
func TestXmlSeqDecodeError(t *testing.T) {
fmt.Println("------------ TestXmlSeqDecodeError ...")
x := []byte(`<doc>
<books>
<book seq="1">
<author>William T. Gaddis</author>
<review>Gaddis is one of the most influential but little know authors in America.</review>
<title>The Recognitions</title>
<!-- here's the rest of the review -->
<review>One of the great seminal American novels of the 20th century.</review>
<review>Without it Thomas Pynchon probably wouldn't have written Gravity's Rainbow.</review>
</books>
</doc>`)
_, err := NewMapXmlSeq(x)
if err == nil {
t.Fatal("didn't catch EndElement error")
}
fmt.Println("err ok:", err)
}

View File

@ -0,0 +1,68 @@
package mxj
import (
"bytes"
"fmt"
"io"
"testing"
)
func TestXMPPStreamTag(t *testing.T) {
fmt.Println("----------- TestXMPPStreamTag ...")
var data = `
<stream:stream
from='example.com'
xmlns="jabber:client"
xmlns:stream="http://etherx.jabber.org/streams"
version="1.0">
<stream:features>
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/>
<sm xmlns="urn:xmpp:sm:3"/>
</stream:features>
<stream:stream>`
HandleXMPPStreamTag()
defer HandleXMPPStreamTag()
buf := bytes.NewBufferString(data)
for {
m, raw, err := NewMapXmlReaderRaw(buf)
if err == io.EOF {
break
}
if err != nil {
t.Fatal("err:", err)
}
fmt.Println(string(raw))
fmt.Println(m)
}
}
func TestXMPPStreamTagSeq(t *testing.T) {
fmt.Println("----------- TestXMPPStreamTagSeq ...")
var data = `
<stream:stream
from='example.com'
xmlns="jabber:client"
xmlns:stream="http://etherx.jabber.org/streams"
version="1.0">
<stream:features>
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/>
<sm xmlns="urn:xmpp:sm:3"/>
</stream:features>
<stream:stream>`
HandleXMPPStreamTag()
defer HandleXMPPStreamTag()
buf := bytes.NewBufferString(data)
for {
m, raw, err := NewMapXmlSeqReaderRaw(buf)
if err == io.EOF {
break
}
if err != nil {
t.Fatal("err:", err)
}
fmt.Println(string(raw))
fmt.Println(m)
}
}