mirror of
https://gitee.com/johng/gf
synced 2026-07-05 05:13:14 +08:00
新增go modules支持,自行管理第三方包依赖,方便开发者使用
This commit is contained in:
55
third/github.com/clbanning/mxj/LICENSE
Normal file
55
third/github.com/clbanning/mxj/LICENSE
Normal 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.
|
||||
177
third/github.com/clbanning/mxj/anyxml.go
Normal file
177
third/github.com/clbanning/mxj/anyxml.go
Normal 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
|
||||
}
|
||||
110
third/github.com/clbanning/mxj/anyxml_test.go
Normal file
110
third/github.com/clbanning/mxj/anyxml_test.go
Normal 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))
|
||||
}
|
||||
54
third/github.com/clbanning/mxj/atomFeedString.xml
Normal file
54
third/github.com/clbanning/mxj/atomFeedString.xml
Normal 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<></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 &lt;link rel=&quot;hub&quot; href=&quot;hub-server&quot;&gt; tag to all
|
||||
feeds that will be pubsubhubbubbed.
|
||||
2. every time one of those feeds changes, tell the hub
|
||||
with a simple POST request.
|
||||
|
||||
I have tested this by adding debug prints to a local hub
|
||||
server and checking that the server got the right publish
|
||||
requests.
|
||||
|
||||
I can&#39;t quite get the server to work, but I think the bug
|
||||
is not in my code. I think that the server expects to be
|
||||
able to grab the feed and see the feed&#39;s actual URL in
|
||||
the link rel=&quot;self&quot;, but the default value for that drops
|
||||
the :port from the URL, and I cannot for the life of me
|
||||
figure out how to get the Atom generator deep inside
|
||||
django not to do that, or even where it is doing that,
|
||||
or even what code is running to generate the Atom feed.
|
||||
(I thought I knew but I added some assert False statements
|
||||
and it kept running!)
|
||||
|
||||
Ignoring that particular problem, I would appreciate
|
||||
feedback on the right way to get the two values at
|
||||
the top of feeds.py marked NOTE(rsc).
|
||||
|
||||
|
||||
</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&#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> `
|
||||
|
||||
148
third/github.com/clbanning/mxj/attrprefix_test.go
Normal file
148
third/github.com/clbanning/mxj/attrprefix_test.go
Normal 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))
|
||||
}
|
||||
|
||||
68
third/github.com/clbanning/mxj/badxml_test.go
Normal file
68
third/github.com/clbanning/mxj/badxml_test.go
Normal 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))
|
||||
}
|
||||
88
third/github.com/clbanning/mxj/bom_test.go
Normal file
88
third/github.com/clbanning/mxj/bom_test.go
Normal 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))
|
||||
}
|
||||
107
third/github.com/clbanning/mxj/bulk_test.go
Normal file
107
third/github.com/clbanning/mxj/bulk_test.go
Normal 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
|
||||
}
|
||||
113
third/github.com/clbanning/mxj/bulkraw_test.go
Normal file
113
third/github.com/clbanning/mxj/bulkraw_test.go
Normal 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
|
||||
}
|
||||
38
third/github.com/clbanning/mxj/data_test.go
Normal file
38
third/github.com/clbanning/mxj/data_test.go
Normal 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 }`)
|
||||
133
third/github.com/clbanning/mxj/doc.go
Normal file
133
third/github.com/clbanning/mxj/doc.go
Normal 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
|
||||
54
third/github.com/clbanning/mxj/escapechars.go
Normal file
54
third/github.com/clbanning/mxj/escapechars.go
Normal 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 '&' will be re-escaped as '&amp;'.
|
||||
//
|
||||
/*
|
||||
The values are:
|
||||
" "
|
||||
' '
|
||||
< <
|
||||
> >
|
||||
& &
|
||||
*/
|
||||
func XMLEscapeChars(b bool) {
|
||||
xmlEscapeChars = b
|
||||
}
|
||||
|
||||
// Scan for '&' first, since 's' may contain "&" that is parsed to "&amp;"
|
||||
// - or "<" that is parsed to "&lt;".
|
||||
var escapechars = [][2][]byte{
|
||||
{[]byte(`&`), []byte(`&`)},
|
||||
{[]byte(`<`), []byte(`<`)},
|
||||
{[]byte(`>`), []byte(`>`)},
|
||||
{[]byte(`"`), []byte(`"`)},
|
||||
{[]byte(`'`), []byte(`'`)},
|
||||
}
|
||||
|
||||
func escapeChars(s string) string {
|
||||
if len(s) == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
b := []byte(s)
|
||||
for _, v := range escapechars {
|
||||
n := bytes.Count(b, v[0])
|
||||
if n == 0 {
|
||||
continue
|
||||
}
|
||||
b = bytes.Replace(b, v[0], v[1], n)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
91
third/github.com/clbanning/mxj/escapechars_test.go
Normal file
91
third/github.com/clbanning/mxj/escapechars_test.go
Normal 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 != `"'<>&` {
|
||||
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>>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="&something here">>0-2y</shortDescription>
|
||||
<shortDescription test="something there" quote="""><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))
|
||||
}
|
||||
347
third/github.com/clbanning/mxj/example_test.go
Normal file
347
third/github.com/clbanning/mxj/example_test.go
Normal 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
|
||||
//
|
||||
}
|
||||
7
third/github.com/clbanning/mxj/exists.go
Normal file
7
third/github.com/clbanning/mxj/exists.go
Normal 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
|
||||
}
|
||||
37
third/github.com/clbanning/mxj/exists_test.go
Normal file
37
third/github.com/clbanning/mxj/exists_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
287
third/github.com/clbanning/mxj/files.go
Normal file
287
third/github.com/clbanning/mxj/files.go
Normal 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
|
||||
}
|
||||
2
third/github.com/clbanning/mxj/files_test.badjson
Normal file
2
third/github.com/clbanning/mxj/files_test.badjson
Normal file
@ -0,0 +1,2 @@
|
||||
{ "this":"is", "a":"test", "file":"for", "files_test.go":"case" }
|
||||
{ "with":"some", "bad":JSON, "in":"it" }
|
||||
9
third/github.com/clbanning/mxj/files_test.badxml
Normal file
9
third/github.com/clbanning/mxj/files_test.badxml
Normal 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>
|
||||
168
third/github.com/clbanning/mxj/files_test.go
Normal file
168
third/github.com/clbanning/mxj/files_test.go
Normal 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")
|
||||
}
|
||||
2
third/github.com/clbanning/mxj/files_test.json
Normal file
2
third/github.com/clbanning/mxj/files_test.json
Normal file
@ -0,0 +1,2 @@
|
||||
{ "this":"is", "a":"test", "file":"for", "files_test.go":"case" }
|
||||
{ "with":"just", "two":2, "JSON":"values", "true":true }
|
||||
9
third/github.com/clbanning/mxj/files_test.xml
Normal file
9
third/github.com/clbanning/mxj/files_test.xml
Normal 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>
|
||||
1
third/github.com/clbanning/mxj/files_test_dup.json
Normal file
1
third/github.com/clbanning/mxj/files_test_dup.json
Normal file
@ -0,0 +1 @@
|
||||
{"a":"test","file":"for","files_test.go":"case","this":"is"}{"JSON":"values","true":true,"two":2,"with":"just"}
|
||||
1
third/github.com/clbanning/mxj/files_test_dup.xml
Normal file
1
third/github.com/clbanning/mxj/files_test_dup.xml
Normal 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>
|
||||
12
third/github.com/clbanning/mxj/files_test_indent.json
Normal file
12
third/github.com/clbanning/mxj/files_test_indent.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"a": "test",
|
||||
"file": "for",
|
||||
"files_test.go": "case",
|
||||
"this": "is"
|
||||
}
|
||||
{
|
||||
"JSON": "values",
|
||||
"true": true,
|
||||
"two": 2,
|
||||
"with": "just"
|
||||
}
|
||||
8
third/github.com/clbanning/mxj/files_test_indent.xml
Normal file
8
third/github.com/clbanning/mxj/files_test_indent.xml
Normal 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>
|
||||
35
third/github.com/clbanning/mxj/gob.go
Normal file
35
third/github.com/clbanning/mxj/gob.go
Normal 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
|
||||
}
|
||||
64
third/github.com/clbanning/mxj/gob_test.go
Normal file
64
third/github.com/clbanning/mxj/gob_test.go
Normal 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)
|
||||
}
|
||||
178
third/github.com/clbanning/mxj/j2x/j2x.go
Normal file
178
third/github.com/clbanning/mxj/j2x/j2x.go
Normal 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
|
||||
}
|
||||
67
third/github.com/clbanning/mxj/j2x/j2x_test.go
Normal file
67
third/github.com/clbanning/mxj/j2x/j2x_test.go
Normal 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))
|
||||
}
|
||||
29
third/github.com/clbanning/mxj/j2x_test.go
Normal file
29
third/github.com/clbanning/mxj/j2x_test.go
Normal 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))
|
||||
}
|
||||
323
third/github.com/clbanning/mxj/json.go
Normal file
323
third/github.com/clbanning/mxj/json.go
Normal 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
|
||||
}
|
||||
137
third/github.com/clbanning/mxj/json_test.go
Normal file
137
third/github.com/clbanning/mxj/json_test.go
Normal 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))
|
||||
}
|
||||
60
third/github.com/clbanning/mxj/keystolower_test.go
Normal file
60
third/github.com/clbanning/mxj/keystolower_test.go
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
653
third/github.com/clbanning/mxj/keyvalues.go
Normal file
653
third/github.com/clbanning/mxj/keyvalues.go
Normal 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
|
||||
}
|
||||
47
third/github.com/clbanning/mxj/keyvalues2_test.go
Normal file
47
third/github.com/clbanning/mxj/keyvalues2_test.go
Normal 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"])
|
||||
}
|
||||
}
|
||||
|
||||
446
third/github.com/clbanning/mxj/keyvalues_test.go
Normal file
446
third/github.com/clbanning/mxj/keyvalues_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
112
third/github.com/clbanning/mxj/leafnode.go
Normal file
112
third/github.com/clbanning/mxj/leafnode.go
Normal 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]
|
||||
}
|
||||
140
third/github.com/clbanning/mxj/leafnode_test.go
Normal file
140
third/github.com/clbanning/mxj/leafnode_test.go
Normal 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)
|
||||
}
|
||||
|
||||
}
|
||||
86
third/github.com/clbanning/mxj/misc.go
Normal file
86
third/github.com/clbanning/mxj/misc.go
Normal 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)
|
||||
}
|
||||
199
third/github.com/clbanning/mxj/misc_test.go
Normal file
199
third/github.com/clbanning/mxj/misc_test.go
Normal 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)
|
||||
}
|
||||
128
third/github.com/clbanning/mxj/mxj.go
Normal file
128
third/github.com/clbanning/mxj/mxj.go
Normal 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]
|
||||
}
|
||||
46
third/github.com/clbanning/mxj/mxj_test.go
Normal file
46
third/github.com/clbanning/mxj/mxj_test.go
Normal 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)
|
||||
}
|
||||
21
third/github.com/clbanning/mxj/namespace_test.go
Normal file
21
third/github.com/clbanning/mxj/namespace_test.go
Normal 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))
|
||||
}
|
||||
81
third/github.com/clbanning/mxj/nan_test.go
Normal file
81
third/github.com/clbanning/mxj/nan_test.go
Normal 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)
|
||||
}
|
||||
|
||||
183
third/github.com/clbanning/mxj/newmap.go
Normal file
183
third/github.com/clbanning/mxj/newmap.go
Normal 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)
|
||||
}
|
||||
}
|
||||
114
third/github.com/clbanning/mxj/newmap_test.go
Normal file
114
third/github.com/clbanning/mxj/newmap_test.go
Normal 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))
|
||||
}
|
||||
}
|
||||
178
third/github.com/clbanning/mxj/readme.md
Normal file
178
third/github.com/clbanning/mxj/readme.md
Normal 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.
|
||||
|
||||
37
third/github.com/clbanning/mxj/remove.go
Normal file
37
third/github.com/clbanning/mxj/remove.go
Normal 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
|
||||
}
|
||||
21
third/github.com/clbanning/mxj/remove_test.go
Normal file
21
third/github.com/clbanning/mxj/remove_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
54
third/github.com/clbanning/mxj/rename.go
Normal file
54
third/github.com/clbanning/mxj/rename.go
Normal 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)
|
||||
}
|
||||
40
third/github.com/clbanning/mxj/rename_test.go
Normal file
40
third/github.com/clbanning/mxj/rename_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
52
third/github.com/clbanning/mxj/seqnum_test.go
Normal file
52
third/github.com/clbanning/mxj/seqnum_test.go
Normal 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)
|
||||
}
|
||||
26
third/github.com/clbanning/mxj/set.go
Normal file
26
third/github.com/clbanning/mxj/set.go
Normal 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
|
||||
}
|
||||
43
third/github.com/clbanning/mxj/set_test.go
Normal file
43
third/github.com/clbanning/mxj/set_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
20
third/github.com/clbanning/mxj/setfieldsep.go
Normal file
20
third/github.com/clbanning/mxj/setfieldsep.go
Normal 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]
|
||||
}
|
||||
69
third/github.com/clbanning/mxj/snakecase_test.go
Normal file
69
third/github.com/clbanning/mxj/snakecase_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
29
third/github.com/clbanning/mxj/songtext.xml
Normal file
29
third/github.com/clbanning/mxj/songtext.xml
Normal 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>
|
||||
30
third/github.com/clbanning/mxj/strict.go
Normal file
30
third/github.com/clbanning/mxj/strict.go
Normal 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
|
||||
}
|
||||
|
||||
48
third/github.com/clbanning/mxj/strict_test.go
Normal file
48
third/github.com/clbanning/mxj/strict_test.go
Normal 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 & 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 & 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 & 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")
|
||||
}
|
||||
|
||||
54
third/github.com/clbanning/mxj/struct.go
Normal file
54
third/github.com/clbanning/mxj/struct.go
Normal 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)
|
||||
}
|
||||
86
third/github.com/clbanning/mxj/struct_test.go
Normal file
86
third/github.com/clbanning/mxj/struct_test.go
Normal 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())
|
||||
}
|
||||
256
third/github.com/clbanning/mxj/updatevalues.go
Normal file
256
third/github.com/clbanning/mxj/updatevalues.go
Normal 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
|
||||
}
|
||||
187
third/github.com/clbanning/mxj/updatevalues_test.go
Normal file
187
third/github.com/clbanning/mxj/updatevalues_test.go
Normal 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())
|
||||
}
|
||||
28
third/github.com/clbanning/mxj/x2j-wrapper/LICENSE
Executable file
28
third/github.com/clbanning/mxj/x2j-wrapper/LICENSE
Executable 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.
|
||||
109
third/github.com/clbanning/mxj/x2j-wrapper/README
Executable file
109
third/github.com/clbanning/mxj/x2j-wrapper/README
Executable 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
|
||||
|
||||
|
||||
54
third/github.com/clbanning/mxj/x2j-wrapper/atomFeedString.xml
Executable file
54
third/github.com/clbanning/mxj/x2j-wrapper/atomFeedString.xml
Executable 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<></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 &lt;link rel=&quot;hub&quot; href=&quot;hub-server&quot;&gt; tag to all
|
||||
feeds that will be pubsubhubbubbed.
|
||||
2. every time one of those feeds changes, tell the hub
|
||||
with a simple POST request.
|
||||
|
||||
I have tested this by adding debug prints to a local hub
|
||||
server and checking that the server got the right publish
|
||||
requests.
|
||||
|
||||
I can&#39;t quite get the server to work, but I think the bug
|
||||
is not in my code. I think that the server expects to be
|
||||
able to grab the feed and see the feed&#39;s actual URL in
|
||||
the link rel=&quot;self&quot;, but the default value for that drops
|
||||
the :port from the URL, and I cannot for the life of me
|
||||
figure out how to get the Atom generator deep inside
|
||||
django not to do that, or even where it is doing that,
|
||||
or even what code is running to generate the Atom feed.
|
||||
(I thought I knew but I added some assert False statements
|
||||
and it kept running!)
|
||||
|
||||
Ignoring that particular problem, I would appreciate
|
||||
feedback on the right way to get the two values at
|
||||
the top of feeds.py marked NOTE(rsc).
|
||||
|
||||
|
||||
</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&#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> `
|
||||
|
||||
56
third/github.com/clbanning/mxj/x2j-wrapper/goofy_test.go
Executable file
56
third/github.com/clbanning/mxj/x2j-wrapper/goofy_test.go
Executable 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))
|
||||
}
|
||||
|
||||
20
third/github.com/clbanning/mxj/x2j-wrapper/pathTestString.xml
Executable file
20
third/github.com/clbanning/mxj/x2j-wrapper/pathTestString.xml
Executable 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>
|
||||
|
||||
86
third/github.com/clbanning/mxj/x2j-wrapper/reader2j.go
Executable file
86
third/github.com/clbanning/mxj/x2j-wrapper/reader2j.go
Executable 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
|
||||
}
|
||||
76
third/github.com/clbanning/mxj/x2j-wrapper/reader2j_test.go
Executable file
76
third/github.com/clbanning/mxj/x2j-wrapper/reader2j_test.go
Executable 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
|
||||
}
|
||||
|
||||
29
third/github.com/clbanning/mxj/x2j-wrapper/songTextString.xml
Executable file
29
third/github.com/clbanning/mxj/x2j-wrapper/songTextString.xml
Executable 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>
|
||||
455
third/github.com/clbanning/mxj/x2j-wrapper/x2j.go
Executable file
455
third/github.com/clbanning/mxj/x2j-wrapper/x2j.go
Executable 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)
|
||||
}
|
||||
|
||||
129
third/github.com/clbanning/mxj/x2j-wrapper/x2j_bulk.go
Executable file
129
third/github.com/clbanning/mxj/x2j-wrapper/x2j_bulk.go
Executable 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
|
||||
}
|
||||
|
||||
148
third/github.com/clbanning/mxj/x2j-wrapper/x2j_findPath.go
Executable file
148
third/github.com/clbanning/mxj/x2j-wrapper/x2j_findPath.go
Executable 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
390
third/github.com/clbanning/mxj/x2j-wrapper/x2j_test.go
Executable file
390
third/github.com/clbanning/mxj/x2j-wrapper/x2j_test.go
Executable 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")
|
||||
}
|
||||
*/
|
||||
29
third/github.com/clbanning/mxj/x2j-wrapper/x2j_test.xml
Executable file
29
third/github.com/clbanning/mxj/x2j-wrapper/x2j_test.xml
Executable 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>
|
||||
90
third/github.com/clbanning/mxj/x2j-wrapper/x2j_valuesAt.go
Executable file
90
third/github.com/clbanning/mxj/x2j-wrapper/x2j_valuesAt.go
Executable 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
|
||||
}
|
||||
|
||||
127
third/github.com/clbanning/mxj/x2j-wrapper/x2j_valuesFrom.go
Executable file
127
third/github.com/clbanning/mxj/x2j-wrapper/x2j_valuesFrom.go
Executable 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
167
third/github.com/clbanning/mxj/x2j-wrapper/x2jat_test.go
Executable file
167
third/github.com/clbanning/mxj/x2j-wrapper/x2jat_test.go
Executable 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)
|
||||
}
|
||||
|
||||
109
third/github.com/clbanning/mxj/x2j-wrapper/x2jfindPath_test.go
Executable file
109
third/github.com/clbanning/mxj/x2j-wrapper/x2jfindPath_test.go
Executable 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)
|
||||
}
|
||||
169
third/github.com/clbanning/mxj/x2j-wrapper/x2jpath_test.go
Executable file
169
third/github.com/clbanning/mxj/x2j-wrapper/x2jpath_test.go
Executable 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
57
third/github.com/clbanning/mxj/x2j-wrapper/x2junmarshal_test.go
Executable file
57
third/github.com/clbanning/mxj/x2j-wrapper/x2junmarshal_test.go
Executable 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)
|
||||
}
|
||||
118
third/github.com/clbanning/mxj/x2j-wrapper/x2m_bulk.go
Executable file
118
third/github.com/clbanning/mxj/x2j-wrapper/x2m_bulk.go
Executable 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
|
||||
}
|
||||
|
||||
67
third/github.com/clbanning/mxj/x2j-wrapper/x2m_bulk.xml
Executable file
67
third/github.com/clbanning/mxj/x2j-wrapper/x2m_bulk.xml
Executable 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>
|
||||
13
third/github.com/clbanning/mxj/x2j-wrapper/xml.go
Normal file
13
third/github.com/clbanning/mxj/x2j-wrapper/xml.go
Normal 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
|
||||
}
|
||||
184
third/github.com/clbanning/mxj/x2j/x2j.go
Normal file
184
third/github.com/clbanning/mxj/x2j/x2j.go
Normal 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
|
||||
}
|
||||
1108
third/github.com/clbanning/mxj/xml.go
Normal file
1108
third/github.com/clbanning/mxj/xml.go
Normal file
File diff suppressed because it is too large
Load Diff
322
third/github.com/clbanning/mxj/xml2_test.go
Normal file
322
third/github.com/clbanning/mxj/xml2_test.go
Normal 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)
|
||||
}
|
||||
47
third/github.com/clbanning/mxj/xml3_test.go
Normal file
47
third/github.com/clbanning/mxj/xml3_test.go
Normal 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))
|
||||
}
|
||||
241
third/github.com/clbanning/mxj/xml_test.go
Normal file
241
third/github.com/clbanning/mxj/xml_test.go
Normal 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
|
||||
}
|
||||
*/
|
||||
828
third/github.com/clbanning/mxj/xmlseq.go
Normal file
828
third/github.com/clbanning/mxj/xmlseq.go
Normal 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)
|
||||
}
|
||||
88
third/github.com/clbanning/mxj/xmlseq_test.go
Normal file
88
third/github.com/clbanning/mxj/xmlseq_test.go
Normal 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)
|
||||
}
|
||||
68
third/github.com/clbanning/mxj/xmppStream_test.go
Normal file
68
third/github.com/clbanning/mxj/xmppStream_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user