mirror of https://github.com/minio/minio.git
Remove mxj, Update gopkg.in/check.v1
This commit is contained in:
parent
4079dd1d12
commit
717913bc8a
|
@ -1,18 +1,10 @@
|
|||
{
|
||||
"ImportPath": "github.com/minio-io/minio",
|
||||
"GoVersion": "go1.4.2",
|
||||
"GoVersion": "go1.4",
|
||||
"Packages": [
|
||||
"./..."
|
||||
],
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/clbanning/mxj",
|
||||
"Rev": "e11b85050263aff26728fb9863bf2ebaf6591279"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/fatih/structs",
|
||||
"Rev": "c00d27128bb88e9c1adab1a53cda9c72c6d1ff9b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gorilla/context",
|
||||
"Rev": "50c25fb3b2b3b3cc724e9b6ac75fb44b3bccd0da"
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
Copyright (c) 2012-2014 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.
|
|
@ -1,177 +0,0 @@
|
|||
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"
|
||||
"github/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 := anyxml.XmlIndent(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:
|
||||
// AnyXmlIndent( 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
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
package mxj
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAnyXmlHeader(t *testing.T) {
|
||||
fmt.Println("\n---------------- anyxml_test.go ...\n")
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
// 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 ...\n")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
// 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 ...\n")
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
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 }`)
|
|
@ -1,84 +0,0 @@
|
|||
// 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
|
||||
|
||||
/*
|
||||
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. If you want the old syntax, use mxj/x2j or mxj/j2x packages.
|
||||
|
||||
Note: this library was designed for processing ad hoc anonymous messages. Bulk processing large data sets may be much more efficiently performed using the encoding/xml or encoding/json packages from Go's standard library directly.
|
||||
|
||||
Note:
|
||||
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':
|
||||
m := Map(v)
|
||||
|
||||
Unmarshal / marshal XML as a Map value, 'm':
|
||||
m, err := NewMapXml(xmlValue) // unmarshal
|
||||
xmlValue, err := m.Xml() // marshal
|
||||
|
||||
Unmarshal XML from an io.Reader as a Map value, 'm':
|
||||
m, err := NewMapReader(xmlReader) // repeated calls, as with an os.File Reader, will process stream
|
||||
m, raw, err := NewMapReaderRaw(xmlReader) // 'raw' is the raw XML that was decoded
|
||||
|
||||
Marshal Map value, 'm', to an XML Writer (io.Writer):
|
||||
err := m.XmlWriter(xmlWriter)
|
||||
raw, err := m.XmlWriterRaw(xmlWriter) // 'raw' is the raw XML that was written on xmlWriter
|
||||
|
||||
Also, for prettified output:
|
||||
xmlValue, err := m.XmlIndent(prefix, indent, ...)
|
||||
err := m.XmlIndentWriter(xmlWriter, prefix, indent, ...)
|
||||
raw, err := m.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:
|
||||
m, err := NewMapStruct(structVal)
|
||||
err := m.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 := m.PathsForKey(key)
|
||||
path := m.PathForKeyShortest(key)
|
||||
values, err := m.ValuesForKey(key, subkeys)
|
||||
values, err := m.ValuesForPath(path, subkeys) // 'path' can be dot-notation with wildcards and indexed arrays.
|
||||
count, err := m.UpdateValuesForPath(newVal, path, subkeys)
|
||||
|
||||
Get everything at once, irrespective of path depth:
|
||||
leafnodes := m.LeafNodes()
|
||||
leafvalues := m.LeafValues()
|
||||
|
||||
A new Map with whatever keys are desired can be created from the current Map and then encoded in XML
|
||||
or JSON. (Note: keys can use dot-notation. 'oldKey' can also use wildcards and indexed arrays.)
|
||||
newMap := m.NewMap("oldKey_1:newKey_1", "oldKey_2:newKey_2", ..., "oldKey_N:newKey_N")
|
||||
newXml := newMap.Xml() // for example
|
||||
newJson := newMap.Json() // ditto
|
||||
|
||||
XML PARSING CONVENTIONS
|
||||
|
||||
- Attributes are parsed to map[string]interface{} values by prefixing a hyphen, '-',
|
||||
to the attribute label. (PrependAttrWithHyphen(false) will override this.)
|
||||
- If the element is a simple element and has attributes, the element value
|
||||
is given the key '#text' for its map[string]interface{} representation.
|
||||
|
||||
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..
|
||||
|
||||
*/
|
||||
package mxj
|
|
@ -1,344 +0,0 @@
|
|||
// 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"
|
||||
"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 `json:"int"`
|
||||
StrVal string `json:"str"`
|
||||
FloatVal float64 `json:"float"`
|
||||
BoolVal bool `json:"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.
|
||||
|
||||
// Output:
|
||||
// strVal: mxj_test.str{IntVal:4, StrVal:"now's the time", FloatVal:3.14159, BoolVal:true, private:"Skies are blue"}
|
||||
// mapVal: mxj.Map{"int":4, "str":"now's the time", "float":3.14159, "bool":true}
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
// 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_ValuesForKeyPath() {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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:%s\n", mv.StringIndent(2))
|
||||
fmt.Printf("cp:%s\n", cp.StringIndent(2))
|
||||
|
||||
// Output:
|
||||
// mv:
|
||||
// struct :[unknown] mxj_test.str{IntVal:4, StrVal:"now's the time", FloatVal:3.14159, BoolVal:true, private:"Skies are blue"}
|
||||
// struct_ptr :[unknown] &mxj_test.str{IntVal:4, StrVal:"now's the time", FloatVal:3.14159, BoolVal:true, private:"Skies are blue"}
|
||||
// misc :[string] Now is the time
|
||||
// cp:
|
||||
// misc :[string] Now is the time
|
||||
// struct :
|
||||
// int :[float64] 4.00e+00
|
||||
// str :[string] now's the time
|
||||
// float :[float64] 3.14e+00
|
||||
// bool :[bool] true
|
||||
// struct_ptr :
|
||||
// int :[float64] 4.00e+00
|
||||
// str :[string] now's the time
|
||||
// float :[float64] 3.14e+00
|
||||
// bool :[bool] true
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
Examples of using ValuesFromTagPath().
|
||||
|
||||
A number of interesting examples have shown up in the gonuts discussion group
|
||||
that could be handled - after a fashion - using the ValuesFromTagPath() function.
|
||||
|
||||
gonuts1.go -
|
||||
|
||||
Here we see that the message stream has a problem with multiple tag spellings,
|
||||
though the message structure remains constant. In this example we 'anonymize'
|
||||
the tag for the variant spellings.
|
||||
values := m.ValuesForPath("data.*)
|
||||
where '*' is any possible spelling - "netid" or "idnet"
|
||||
and the result is a list with 1 member of map[string]interface{} type.
|
||||
|
||||
Once we've retrieved the Map, we can parse it using the known keys - "disable",
|
||||
"text1" and "word1".
|
||||
|
||||
|
||||
gonuts1a.go - (03-mar-14)
|
||||
|
||||
Here we just permute the tag labels using m.NewMap() to make all the messages
|
||||
consistent. Then they can be decoded into a single structure definition.
|
||||
|
||||
|
||||
gonuts2.go -
|
||||
|
||||
This is an interesting case where there was a need to handle messages with lists
|
||||
of "ClaimStatusCodeRecord" entries as well as messages with NONE. (Here we see
|
||||
some of the vagaries of dealing with mixed messages that are verging on becoming
|
||||
anonymous.)
|
||||
|
||||
msg1 - the message with two ClaimStatusCodeRecord entries
|
||||
msg2 - the message with one ClaimStatusCodeRecord entry
|
||||
msg3 - the message with NO ClaimStatusCodeRecord entries
|
||||
|
||||
ValuesForPath options:
|
||||
|
||||
path == "Envelope.Body.GetClaimStatusCodesResponse.GetClaimStatusCodesResult.ClaimStatusCodeRecord"
|
||||
for msg == msg1:
|
||||
returns: a list - []interface{} - with two values of map[string]interface{} type
|
||||
for msg == msg2:
|
||||
returns: a list - []interface{} - with one map[string]interface{} type
|
||||
for msg == msg3:
|
||||
returns 'nil' - no values
|
||||
|
||||
path == "*.*.*.*.*"
|
||||
for msg == msg1:
|
||||
returns: a list - []interface{} - with two values of map[string]interface{} type
|
||||
|
||||
path == "*.*.*.*.*.Description
|
||||
for msg == msg1:
|
||||
returns: a list - []interface{} - with two values of string type, the individual
|
||||
values from parsing the two map[string]interface{} values where key=="Description"
|
||||
|
||||
path == "*.*.*.*.*.*"
|
||||
for msg == msg1:
|
||||
returns: a list - []interface{} - with six values of string type, the individual
|
||||
values from parsing all keys in the two map[string]interface{} values
|
||||
|
||||
Think of the wildcard character "*" as anonymizing the tag in the position of the path where
|
||||
it occurs. The books.go example has a range of use cases.
|
||||
|
||||
|
||||
gonuts3.go -
|
||||
|
||||
Uses the ValuesForKey method to extract a list of image "src" file names that are encoded as
|
||||
attribute values.
|
||||
|
||||
|
||||
gonuts4.go -
|
||||
|
||||
Here we use the ValuesForPath to extract attribute values for country names. The attribute
|
||||
is included in the 'path' argument by prepending it with a hyphen: ""doc.some_tag.geoInfo.country.-name".
|
||||
|
||||
|
||||
gonuts5.go (10-mar-14) -
|
||||
|
||||
Extract a node of values using ValuesForPath based on name="list3-1-1-1". Then get the values
|
||||
for the 'int' entries based on attribute 'name' values - mv.ValuesForKey("int", "-name:"+n).
|
||||
|
||||
gonuts5a.go (10-mar-14) -
|
||||
|
||||
Extract a node of values using ValuesForPath based on name="list3-1-1-1". Then get the values
|
||||
for the 'int' entries based on attribute 'name' values - mv.ValuesForKey("*", "-name:"+n).
|
||||
(Same as gonuts5.go but with wildcarded key value, since we're matching elements on subkey.)
|
||||
|
||||
|
||||
EAT YOUR OWN DOG FOOD ...
|
||||
|
||||
I needed to convert a large (14.9 MB) XML data set from an Eclipse metrics report on an
|
||||
application that had 355,100 lines of code in 211 packages into CSV data sets. The report
|
||||
included application-, package-, class- and method-level metrics reported in an element,
|
||||
"Value", with varying attributes.
|
||||
<Value value=""/>
|
||||
<Value name="" package="" value=""/>
|
||||
<Value name="" source="" package="" value=""/>
|
||||
<Value name="" source="" package="" value="" inrange=""/>
|
||||
|
||||
In addition, the metrics were reported with two different "Metric" compound elements:
|
||||
<Metrics>
|
||||
<Metric id="" description="">
|
||||
<Values>
|
||||
<Value.../>
|
||||
...
|
||||
</Values>
|
||||
</Metric>
|
||||
...
|
||||
<Metric id="" description="">
|
||||
<Value.../>
|
||||
</Metric>
|
||||
...
|
||||
</Metrics>
|
||||
|
||||
Using the mxj package seemed a more straightforward approach than using Go vernacular
|
||||
and the standard xml package. I wrote the program getmetrics.go to do this. Here are
|
||||
three version to illustrate using
|
||||
getmetrics1.go - pass os.File handle for metrics_data.xml to NewMapXmlReader.
|
||||
getmetrics2.go - load metrics_data.xml into an in-memory buffer, then pass it to NewMapXml.
|
||||
getmetrics3.go - demonstrates overhead of extracting the raw XML while decoding with NewMapXmlReaderRaw.
|
||||
|
||||
To run example getmetrics1.go, extract a 120,000+ row data set from metrics_data.zip. Then:
|
||||
go run getmetrics1.go -file=metrics_data.xml
|
||||
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
// Note: this illustrates ValuesForKey() and ValuesForPath() methods
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/clbanning/mxj"
|
||||
"log"
|
||||
)
|
||||
|
||||
var xmldata = []byte(`
|
||||
<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>T.E. Porter</author>
|
||||
<title>King's Day</title>
|
||||
<review>A magical novella.</review>
|
||||
</book>
|
||||
</books>
|
||||
`)
|
||||
|
||||
func main() {
|
||||
fmt.Println(string(xmldata))
|
||||
|
||||
m, err := mxj.NewMapXml(xmldata)
|
||||
if err != nil {
|
||||
log.Fatal("err:", err.Error())
|
||||
}
|
||||
|
||||
v, _ := m.ValuesForKey("books")
|
||||
fmt.Println("path: books; len(v):", len(v))
|
||||
fmt.Printf("\t%+v\n", v)
|
||||
|
||||
v, _ = m.ValuesForPath("books.book")
|
||||
fmt.Println("path: books.book; len(v):", len(v))
|
||||
for _, vv := range v {
|
||||
fmt.Printf("\t%+v\n", vv)
|
||||
}
|
||||
|
||||
v, _ = m.ValuesForPath("books.*")
|
||||
fmt.Println("path: books.*; len(v):", len(v))
|
||||
for _, vv := range v {
|
||||
fmt.Printf("\t%+v\n", vv)
|
||||
}
|
||||
|
||||
v, _ = m.ValuesForPath("books.*.title")
|
||||
fmt.Println("path: books.*.title len(v):", len(v))
|
||||
for _, vv := range v {
|
||||
fmt.Printf("\t%+v\n", vv)
|
||||
}
|
||||
|
||||
v, _ = m.ValuesForPath("books.*.*")
|
||||
fmt.Println("path: books.*.*; len(v):", len(v))
|
||||
for _, vv := range v {
|
||||
fmt.Printf("\t%+v\n", vv)
|
||||
}
|
||||
}
|
|
@ -1,176 +0,0 @@
|
|||
// getmetrics1.go - transform Eclipse Metrics (v3) XML report into CSV files for each metric
|
||||
// Uses NewMapXmlReader on os.File without copying the raw XML into a buffer while decoding..
|
||||
// Shows no significant overhead for not first buffering large XML file as in getmetrics2.go.
|
||||
|
||||
/*
|
||||
I needed to convert a large (14.9 MB) XML data set from an Eclipse metrics report on an
|
||||
application that had 355,100 lines of code in 211 packages into CSV data sets. The report
|
||||
included application-, package-, class- and method-level metrics reported in an element,
|
||||
"Value", with varying attributes.
|
||||
<Value value=""/>
|
||||
<Value name="" package="" value=""/>
|
||||
<Value name="" source="" package="" value=""/>
|
||||
<Value name="" source="" package="" value="" inrange=""/>
|
||||
|
||||
In addition, the metrics were reported with two different "Metric" compound elements:
|
||||
<Metrics>
|
||||
<Metric id="" description="">
|
||||
<Values>
|
||||
<Value.../>
|
||||
...
|
||||
</Values>
|
||||
</Metric>
|
||||
...
|
||||
<Metric id="" description="">
|
||||
<Value.../>
|
||||
</Metric>
|
||||
...
|
||||
</Metrics>
|
||||
|
||||
To run this example, extract the metrics_data.xml file from metrics_data.zip, then:
|
||||
> go run getmetrics1 -file=metrics_data.xml
|
||||
|
||||
The output will be a set of "csv" files.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/clbanning/mxj"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var file string
|
||||
flag.StringVar(&file, "file", "", "file to process")
|
||||
flag.Parse()
|
||||
|
||||
fh, fherr := os.Open(file)
|
||||
if fherr != nil {
|
||||
fmt.Println("fherr:", fherr.Error())
|
||||
return
|
||||
}
|
||||
defer fh.Close()
|
||||
fmt.Println(time.Now().String(), "... File Opened:", file)
|
||||
|
||||
/*
|
||||
// Get the XML data set from the file.
|
||||
fs, _ := fh.Stat()
|
||||
xmldata := make([]byte, fs.Size())
|
||||
n, frerr := fh.Read(xmldata)
|
||||
if frerr != nil {
|
||||
fmt.Println("frerr:", frerr.Error())
|
||||
return
|
||||
}
|
||||
if int64(n) != fs.Size() {
|
||||
fmt.Println("n:", n, "fs.Size():", fs.Size())
|
||||
return
|
||||
}
|
||||
fmt.Println(time.Now().String(), "... File Read - size:", fs.Size())
|
||||
|
||||
// load XML into a Map value
|
||||
m, merr := mxj.NewMapXml(xmldata)
|
||||
*/
|
||||
// Consume the file using os.File Reader.
|
||||
// Note: there is a single record with root tag of "Metrics".
|
||||
m, merr := mxj.NewMapXmlReader(fh)
|
||||
if merr != nil {
|
||||
log.Fatal("merr:", merr.Error())
|
||||
}
|
||||
fmt.Println(time.Now().String(), "... XML Unmarshaled - len:", len(m))
|
||||
|
||||
// Get just the key values of interest.
|
||||
// Could also use m.ValuesForKey("Metric"),
|
||||
// since there's just the one path.
|
||||
metricVals, err := m.ValuesForPath("Metrics.Metric")
|
||||
if err != nil {
|
||||
log.Fatal("err:", err.Error())
|
||||
}
|
||||
fmt.Println(time.Now().String(), "... ValuesFromKeyPath - len:", len(metricVals))
|
||||
|
||||
// now just manipulate Map entries returned as []interface{} array.
|
||||
for _, v := range metricVals {
|
||||
aMetricVal := v.(map[string]interface{})
|
||||
|
||||
// create file to hold csv data sets
|
||||
id := aMetricVal["-id"].(string)
|
||||
desc := aMetricVal["-description"].(string)
|
||||
mf, mferr := os.OpenFile(id+".csv", os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)
|
||||
if mferr != nil {
|
||||
fmt.Println("mferr:", mferr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Print(time.Now().String(), " id: ", id, " desc: ", desc)
|
||||
mf.WriteString(id + "," + desc + "\n")
|
||||
|
||||
// rescan looking for keys with data: Values or Value
|
||||
for key, val := range aMetricVal {
|
||||
switch key {
|
||||
case "Values":
|
||||
// extract the list of "Value" from map
|
||||
values := val.(map[string]interface{})["Value"].([]interface{})
|
||||
fmt.Println(" len(Values):", len(values))
|
||||
|
||||
// first line in file is the metric label values (keys)
|
||||
var gotKeys bool
|
||||
for _, vval := range values {
|
||||
valueEntry := vval.(map[string]interface{})
|
||||
|
||||
// no guarantee that range on map will follow any sequence
|
||||
lv := len(valueEntry)
|
||||
type ev [2]string
|
||||
list := make([]ev, lv)
|
||||
var i int
|
||||
for k, v := range valueEntry {
|
||||
list[i][0] = k
|
||||
list[i][1] = v.(string)
|
||||
i++
|
||||
}
|
||||
|
||||
// extract keys as column header on first pass
|
||||
if !gotKeys {
|
||||
// print out the keys
|
||||
var gotFirstKey bool
|
||||
// for kk, _ := range valueEntry {
|
||||
for i := 0; i < lv; i++ {
|
||||
if gotFirstKey {
|
||||
mf.WriteString(",")
|
||||
} else {
|
||||
gotFirstKey = true
|
||||
}
|
||||
// strip prepended hyphen
|
||||
mf.WriteString((list[i][0])[1:])
|
||||
}
|
||||
mf.WriteString("\n")
|
||||
gotKeys = true
|
||||
}
|
||||
|
||||
// print out values
|
||||
var gotFirstVal bool
|
||||
// for _, vv := range valueEntry {
|
||||
for i := 0; i < lv; i++ {
|
||||
if gotFirstVal {
|
||||
mf.WriteString(",")
|
||||
} else {
|
||||
gotFirstVal = true
|
||||
}
|
||||
mf.WriteString(list[i][1])
|
||||
}
|
||||
|
||||
// terminate row of data
|
||||
mf.WriteString("\n")
|
||||
}
|
||||
case "Value":
|
||||
vv := val.(map[string]interface{})
|
||||
fmt.Println(" len(Value):", len(vv))
|
||||
mf.WriteString("value\n" + vv["-value"].(string) + "\n")
|
||||
}
|
||||
}
|
||||
mf.Close()
|
||||
}
|
||||
}
|
|
@ -1,173 +0,0 @@
|
|||
// getmetrics2.go - transform Eclipse Metrics (v3) XML report into CSV files for each metric
|
||||
// Uses an in-memory buffer for the XML data and direct XML decoding of the buffer into a Map.
|
||||
// Not significantly faster than getmetrics1.go that uses an io.Reader (os.File) to directly
|
||||
// decode the XML from the file.
|
||||
|
||||
/*
|
||||
I needed to convert a large (14.9 MB) XML data set from an Eclipse metrics report on an
|
||||
application that had 355,100 lines of code in 211 packages into CSV data sets. The report
|
||||
included application-, package-, class- and method-level metrics reported in an element,
|
||||
"Value", with varying attributes.
|
||||
<Value value=""/>
|
||||
<Value name="" package="" value=""/>
|
||||
<Value name="" source="" package="" value=""/>
|
||||
<Value name="" source="" package="" value="" inrange=""/>
|
||||
|
||||
In addition, the metrics were reported with two different "Metric" compound elements:
|
||||
<Metrics>
|
||||
<Metric id="" description="">
|
||||
<Values>
|
||||
<Value.../>
|
||||
...
|
||||
</Values>
|
||||
</Metric>
|
||||
...
|
||||
<Metric id="" description="">
|
||||
<Value.../>
|
||||
</Metric>
|
||||
...
|
||||
</Metrics>
|
||||
|
||||
To run this example, extract the metrics_data.xml file from metrics_data.zip, then:
|
||||
> go run getmetrics -file=metrics_data.xml
|
||||
|
||||
The output will be a set of "csv" files.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/clbanning/mxj"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var file string
|
||||
flag.StringVar(&file, "file", "", "file to process")
|
||||
flag.Parse()
|
||||
|
||||
fh, fherr := os.Open(file)
|
||||
if fherr != nil {
|
||||
fmt.Println("fherr:", fherr.Error())
|
||||
return
|
||||
}
|
||||
defer fh.Close()
|
||||
fmt.Println(time.Now().String(), "... File Opened:", file)
|
||||
|
||||
// Get the XML data set from the file.
|
||||
fs, _ := fh.Stat()
|
||||
xmldata := make([]byte, fs.Size())
|
||||
n, frerr := fh.Read(xmldata)
|
||||
if frerr != nil {
|
||||
fmt.Println("frerr:", frerr.Error())
|
||||
return
|
||||
}
|
||||
if int64(n) != fs.Size() {
|
||||
fmt.Println("n:", n, "fs.Size():", fs.Size())
|
||||
return
|
||||
}
|
||||
fmt.Println(time.Now().String(), "... File Read - size:", fs.Size())
|
||||
|
||||
// load XML into a Map value
|
||||
// Note: there is a single record with root tag of "Metrics".
|
||||
m, merr := mxj.NewMapXml(xmldata)
|
||||
if merr != nil {
|
||||
log.Fatal("merr:", merr.Error())
|
||||
}
|
||||
fmt.Println(time.Now().String(), "... XML Unmarshaled - len:", len(m))
|
||||
|
||||
// Get just the key values of interest.
|
||||
// Could also use m.ValuesForKey("Metric"),
|
||||
// since there's just the one path.
|
||||
metricVals, err := m.ValuesForPath("Metrics.Metric")
|
||||
if err != nil {
|
||||
log.Fatal("err:", err.Error())
|
||||
}
|
||||
fmt.Println(time.Now().String(), "... ValuesFromKeyPath - len:", len(metricVals))
|
||||
|
||||
// now just manipulate Map entries returned as []interface{} array.
|
||||
for _, v := range metricVals {
|
||||
aMetricVal := v.(map[string]interface{})
|
||||
|
||||
// create file to hold csv data sets
|
||||
id := aMetricVal["-id"].(string)
|
||||
desc := aMetricVal["-description"].(string)
|
||||
mf, mferr := os.OpenFile(id+".csv", os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)
|
||||
if mferr != nil {
|
||||
fmt.Println("mferr:", mferr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Print(time.Now().String(), " id: ", id, " desc: ", desc)
|
||||
mf.WriteString(id + "," + desc + "\n")
|
||||
|
||||
// rescan looking for keys with data: Values or Value
|
||||
for key, val := range aMetricVal {
|
||||
switch key {
|
||||
case "Values":
|
||||
// extract the list of "Value" from map
|
||||
values := val.(map[string]interface{})["Value"].([]interface{})
|
||||
fmt.Println(" len(Values):", len(values))
|
||||
|
||||
// first line in file is the metric label values (keys)
|
||||
var gotKeys bool
|
||||
for _, vval := range values {
|
||||
valueEntry := vval.(map[string]interface{})
|
||||
|
||||
// no guarantee that range on map will follow any sequence
|
||||
lv := len(valueEntry)
|
||||
type ev [2]string
|
||||
list := make([]ev, lv)
|
||||
var i int
|
||||
for k, v := range valueEntry {
|
||||
list[i][0] = k
|
||||
list[i][1] = v.(string)
|
||||
i++
|
||||
}
|
||||
|
||||
// extract keys as column header on first pass
|
||||
if !gotKeys {
|
||||
// print out the keys
|
||||
var gotFirstKey bool
|
||||
// for kk, _ := range valueEntry {
|
||||
for i := 0; i < lv; i++ {
|
||||
if gotFirstKey {
|
||||
mf.WriteString(",")
|
||||
} else {
|
||||
gotFirstKey = true
|
||||
}
|
||||
// strip prepended hyphen
|
||||
mf.WriteString((list[i][0])[1:])
|
||||
}
|
||||
mf.WriteString("\n")
|
||||
gotKeys = true
|
||||
}
|
||||
|
||||
// print out values
|
||||
var gotFirstVal bool
|
||||
// for _, vv := range valueEntry {
|
||||
for i := 0; i < lv; i++ {
|
||||
if gotFirstVal {
|
||||
mf.WriteString(",")
|
||||
} else {
|
||||
gotFirstVal = true
|
||||
}
|
||||
mf.WriteString(list[i][1])
|
||||
}
|
||||
|
||||
// terminate row of data
|
||||
mf.WriteString("\n")
|
||||
}
|
||||
case "Value":
|
||||
vv := val.(map[string]interface{})
|
||||
fmt.Println(" len(Value):", len(vv))
|
||||
mf.WriteString("value\n" + vv["-value"].(string) + "\n")
|
||||
}
|
||||
}
|
||||
mf.Close()
|
||||
}
|
||||
}
|
|
@ -1,180 +0,0 @@
|
|||
// getmetrics3.go - transform Eclipse Metrics (v3) XML report into CSV files for each metric
|
||||
// Uses NewMapXmlReaderRaw that requires loading raw XML into a []byte buffer using a ByteReader.
|
||||
// Show's performance impact of copying the raw XML while simultaneously decoding it from on os.File
|
||||
// Reader. (vs. getmetrics1.go) If you're processing a file and need a copy of the raw XML and SPEED,
|
||||
// should buffer the file in memory and decode using mxj.NewMapXmlReaderRaw as in getmetrics4.go.
|
||||
|
||||
/*
|
||||
I needed to convert a large (14.9 MB) XML data set from an Eclipse metrics report on an
|
||||
application that had 355,100 lines of code in 211 packages into CSV data sets. The report
|
||||
included application-, package-, class- and method-level metrics reported in an element,
|
||||
"Value", with varying attributes.
|
||||
<Value value=""/>
|
||||
<Value name="" package="" value=""/>
|
||||
<Value name="" source="" package="" value=""/>
|
||||
<Value name="" source="" package="" value="" inrange=""/>
|
||||
|
||||
In addition, the metrics were reported with two different "Metric" compound elements:
|
||||
<Metrics>
|
||||
<Metric id="" description="">
|
||||
<Values>
|
||||
<Value.../>
|
||||
...
|
||||
</Values>
|
||||
</Metric>
|
||||
...
|
||||
<Metric id="" description="">
|
||||
<Value.../>
|
||||
</Metric>
|
||||
...
|
||||
</Metrics>
|
||||
|
||||
To run this example, extract the metrics_data.xml file from metrics_data.zip, then:
|
||||
> go run getmetrics3 -file=metrics_data.xml
|
||||
|
||||
The output will be a set of "csv" files.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/clbanning/mxj"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var file string
|
||||
flag.StringVar(&file, "file", "", "file to process")
|
||||
flag.Parse()
|
||||
|
||||
fh, fherr := os.Open(file)
|
||||
if fherr != nil {
|
||||
fmt.Println("fherr:", fherr.Error())
|
||||
return
|
||||
}
|
||||
defer fh.Close()
|
||||
fmt.Println(time.Now().String(), "... File Opened:", file)
|
||||
|
||||
/*
|
||||
// Get the XML data set from the file.
|
||||
fs, _ := fh.Stat()
|
||||
xmldata := make([]byte, fs.Size())
|
||||
n, frerr := fh.Read(xmldata)
|
||||
if frerr != nil {
|
||||
fmt.Println("frerr:", frerr.Error())
|
||||
return
|
||||
}
|
||||
if int64(n) != fs.Size() {
|
||||
fmt.Println("n:", n, "fs.Size():", fs.Size())
|
||||
return
|
||||
}
|
||||
fmt.Println(time.Now().String(), "... File Read - size:", fs.Size())
|
||||
|
||||
// load XML into a Map value
|
||||
m, merr := mxj.NewMapXml(xmldata)
|
||||
*/
|
||||
// Consume the file using os.File Reader.
|
||||
// Note: there is a single record with root tag of "Metrics".
|
||||
// Also: this is MUCH slower than using buffer or not loading raw XML.
|
||||
m, raw, merr := mxj.NewMapXmlReaderRaw(fh)
|
||||
if merr != nil {
|
||||
log.Fatal("merr:", merr.Error())
|
||||
}
|
||||
fmt.Println(time.Now().String(), "... XML Unmarshaled - len:", len(m))
|
||||
fmt.Println("raw XML buffer size (should be same as File size):", len(*raw))
|
||||
|
||||
// Get just the key values of interest.
|
||||
// Could also use m.ValuesForKey("Metric"),
|
||||
// since there's just the one path.
|
||||
metricVals, err := m.ValuesForPath("Metrics.Metric")
|
||||
if err != nil {
|
||||
log.Fatal("err:", err.Error())
|
||||
}
|
||||
fmt.Println(time.Now().String(), "... ValuesFromKeyPath - len:", len(metricVals))
|
||||
|
||||
// now just manipulate Map entries returned as []interface{} array.
|
||||
for _, v := range metricVals {
|
||||
aMetricVal := v.(map[string]interface{})
|
||||
|
||||
// create file to hold csv data sets
|
||||
id := aMetricVal["-id"].(string)
|
||||
desc := aMetricVal["-description"].(string)
|
||||
mf, mferr := os.OpenFile(id+".csv", os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)
|
||||
if mferr != nil {
|
||||
fmt.Println("mferr:", mferr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Print(time.Now().String(), " id: ", id, " desc: ", desc)
|
||||
mf.WriteString(id + "," + desc + "\n")
|
||||
|
||||
// rescan looking for keys with data: Values or Value
|
||||
for key, val := range aMetricVal {
|
||||
switch key {
|
||||
case "Values":
|
||||
// extract the list of "Value" from map
|
||||
values := val.(map[string]interface{})["Value"].([]interface{})
|
||||
fmt.Println(" len(Values):", len(values))
|
||||
|
||||
// first line in file is the metric label values (keys)
|
||||
var gotKeys bool
|
||||
for _, vval := range values {
|
||||
valueEntry := vval.(map[string]interface{})
|
||||
|
||||
// no guarantee that range on map will follow any sequence
|
||||
lv := len(valueEntry)
|
||||
type ev [2]string
|
||||
list := make([]ev, lv)
|
||||
var i int
|
||||
for k, v := range valueEntry {
|
||||
list[i][0] = k
|
||||
list[i][1] = v.(string)
|
||||
i++
|
||||
}
|
||||
|
||||
// extract keys as column header on first pass
|
||||
if !gotKeys {
|
||||
// print out the keys
|
||||
var gotFirstKey bool
|
||||
// for kk, _ := range valueEntry {
|
||||
for i := 0; i < lv; i++ {
|
||||
if gotFirstKey {
|
||||
mf.WriteString(",")
|
||||
} else {
|
||||
gotFirstKey = true
|
||||
}
|
||||
// strip prepended hyphen
|
||||
mf.WriteString((list[i][0])[1:])
|
||||
}
|
||||
mf.WriteString("\n")
|
||||
gotKeys = true
|
||||
}
|
||||
|
||||
// print out values
|
||||
var gotFirstVal bool
|
||||
// for _, vv := range valueEntry {
|
||||
for i := 0; i < lv; i++ {
|
||||
if gotFirstVal {
|
||||
mf.WriteString(",")
|
||||
} else {
|
||||
gotFirstVal = true
|
||||
}
|
||||
mf.WriteString(list[i][1])
|
||||
}
|
||||
|
||||
// terminate row of data
|
||||
mf.WriteString("\n")
|
||||
}
|
||||
case "Value":
|
||||
vv := val.(map[string]interface{})
|
||||
fmt.Println(" len(Value):", len(vv))
|
||||
mf.WriteString("value\n" + vv["-value"].(string) + "\n")
|
||||
}
|
||||
}
|
||||
mf.Close()
|
||||
}
|
||||
}
|
|
@ -1,179 +0,0 @@
|
|||
// getmetrics2.go - transform Eclipse Metrics (v3) XML report into CSV files for each metric
|
||||
// Uses an in-memory buffer for the XML data and direct XML decoding of the buffer into a Map.
|
||||
// Then XML buffer is decoded into a Map while the raw XML is copied using NewMapXmlReaderRaw()
|
||||
// to illustrate processing overhead relative to getmetrics2.go. Not a practical example,
|
||||
// but confirms the getmetrics1.go vs. getmetrics3.go use case.
|
||||
|
||||
/*
|
||||
I needed to convert a large (14.9 MB) XML data set from an Eclipse metrics report on an
|
||||
application that had 355,100 lines of code in 211 packages into CSV data sets. The report
|
||||
included application-, package-, class- and method-level metrics reported in an element,
|
||||
"Value", with varying attributes.
|
||||
<Value value=""/>
|
||||
<Value name="" package="" value=""/>
|
||||
<Value name="" source="" package="" value=""/>
|
||||
<Value name="" source="" package="" value="" inrange=""/>
|
||||
|
||||
In addition, the metrics were reported with two different "Metric" compound elements:
|
||||
<Metrics>
|
||||
<Metric id="" description="">
|
||||
<Values>
|
||||
<Value.../>
|
||||
...
|
||||
</Values>
|
||||
</Metric>
|
||||
...
|
||||
<Metric id="" description="">
|
||||
<Value.../>
|
||||
</Metric>
|
||||
...
|
||||
</Metrics>
|
||||
|
||||
To run this example, extract the metrics_data.xml file from metrics_data.zip, then:
|
||||
> go run getmetrics -file=metrics_data.xml
|
||||
|
||||
The output will be a set of "csv" files.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/clbanning/mxj"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var file string
|
||||
flag.StringVar(&file, "file", "", "file to process")
|
||||
flag.Parse()
|
||||
|
||||
fh, fherr := os.Open(file)
|
||||
if fherr != nil {
|
||||
fmt.Println("fherr:", fherr.Error())
|
||||
return
|
||||
}
|
||||
defer fh.Close()
|
||||
fmt.Println(time.Now().String(), "... File Opened:", file)
|
||||
|
||||
// Get the XML data set from the file.
|
||||
fs, _ := fh.Stat()
|
||||
xmldata := make([]byte, fs.Size())
|
||||
n, frerr := fh.Read(xmldata)
|
||||
if frerr != nil {
|
||||
fmt.Println("frerr:", frerr.Error())
|
||||
return
|
||||
}
|
||||
if int64(n) != fs.Size() {
|
||||
fmt.Println("n:", n, "fs.Size():", fs.Size())
|
||||
return
|
||||
}
|
||||
fmt.Println(time.Now().String(), "... File Read - size:", fs.Size())
|
||||
|
||||
// wrap the buffer in an io.Reader
|
||||
xmlReader := bytes.NewBuffer(xmldata)
|
||||
|
||||
// load XML into a Map value
|
||||
// Note: there is a single record with root tag of "Metrics".
|
||||
m, raw, merr := mxj.NewMapXmlReaderRaw(xmlReader) // don't catch the pointer to raw XML
|
||||
if merr != nil {
|
||||
log.Fatal("merr:", merr.Error())
|
||||
}
|
||||
fmt.Println(time.Now().String(), "... XML Unmarshaled - len:", len(m))
|
||||
fmt.Println("raw XML buffer size (should be same as File size):", len(*raw))
|
||||
|
||||
// Get just the key values of interest.
|
||||
// Could also use m.ValuesForKey("Metric"),
|
||||
// since there's just the one path.
|
||||
metricVals, err := m.ValuesForPath("Metrics.Metric")
|
||||
if err != nil {
|
||||
log.Fatal("err:", err.Error())
|
||||
}
|
||||
fmt.Println(time.Now().String(), "... ValuesFromKeyPath - len:", len(metricVals))
|
||||
|
||||
// now just manipulate Map entries returned as []interface{} array.
|
||||
for _, v := range metricVals {
|
||||
aMetricVal := v.(map[string]interface{})
|
||||
|
||||
// create file to hold csv data sets
|
||||
id := aMetricVal["-id"].(string)
|
||||
desc := aMetricVal["-description"].(string)
|
||||
mf, mferr := os.OpenFile(id+".csv", os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)
|
||||
if mferr != nil {
|
||||
fmt.Println("mferr:", mferr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Print(time.Now().String(), " id: ", id, " desc: ", desc)
|
||||
mf.WriteString(id + "," + desc + "\n")
|
||||
|
||||
// rescan looking for keys with data: Values or Value
|
||||
for key, val := range aMetricVal {
|
||||
switch key {
|
||||
case "Values":
|
||||
// extract the list of "Value" from map
|
||||
values := val.(map[string]interface{})["Value"].([]interface{})
|
||||
fmt.Println(" len(Values):", len(values))
|
||||
|
||||
// first line in file is the metric label values (keys)
|
||||
var gotKeys bool
|
||||
for _, vval := range values {
|
||||
valueEntry := vval.(map[string]interface{})
|
||||
|
||||
// no guarantee that range on map will follow any sequence
|
||||
lv := len(valueEntry)
|
||||
type ev [2]string
|
||||
list := make([]ev, lv)
|
||||
var i int
|
||||
for k, v := range valueEntry {
|
||||
list[i][0] = k
|
||||
list[i][1] = v.(string)
|
||||
i++
|
||||
}
|
||||
|
||||
// extract keys as column header on first pass
|
||||
if !gotKeys {
|
||||
// print out the keys
|
||||
var gotFirstKey bool
|
||||
// for kk, _ := range valueEntry {
|
||||
for i := 0; i < lv; i++ {
|
||||
if gotFirstKey {
|
||||
mf.WriteString(",")
|
||||
} else {
|
||||
gotFirstKey = true
|
||||
}
|
||||
// strip prepended hyphen
|
||||
mf.WriteString((list[i][0])[1:])
|
||||
}
|
||||
mf.WriteString("\n")
|
||||
gotKeys = true
|
||||
}
|
||||
|
||||
// print out values
|
||||
var gotFirstVal bool
|
||||
// for _, vv := range valueEntry {
|
||||
for i := 0; i < lv; i++ {
|
||||
if gotFirstVal {
|
||||
mf.WriteString(",")
|
||||
} else {
|
||||
gotFirstVal = true
|
||||
}
|
||||
mf.WriteString(list[i][1])
|
||||
}
|
||||
|
||||
// terminate row of data
|
||||
mf.WriteString("\n")
|
||||
}
|
||||
case "Value":
|
||||
vv := val.(map[string]interface{})
|
||||
fmt.Println(" len(Value):", len(vv))
|
||||
mf.WriteString("value\n" + vv["-value"].(string) + "\n")
|
||||
}
|
||||
}
|
||||
mf.Close()
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
// https://groups.google.com/forum/#!searchin/golang-nuts/idnet$20netid/golang-nuts/guM3ZHHqSF0/K1pBpMqQSSwJ
|
||||
// http://play.golang.org/p/BFFDxphKYK
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/clbanning/mxj"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Demo 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>
|
||||
`)
|
||||
|
||||
func main() {
|
||||
// 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 := mxj.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
|
||||
}
|
||||
fmt.Println("\nMessage to parse:", string(*raw))
|
||||
fmt.Println("Map value for XML message:", m.StringIndent())
|
||||
|
||||
// get the values for "netid" or "idnet" key using path == "data.*"
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// This shows what happens if you wildcard the value keys for "idnet" and "netid"
|
||||
values, _ = m.ValuesForPath("data.*.*")
|
||||
fmt.Println("\npath == data.*.* - got an array of values, len(v):", len(values))
|
||||
fmt.Println("(Note: values returned by ValuesForPath are at maximum depth of the tree. So just have values.)")
|
||||
for i, val := range values {
|
||||
fmt.Println("ValuesForPath array member -", i, ":", val)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
// https://groups.google.com/forum/#!searchin/golang-nuts/idnet$20netid/golang-nuts/guM3ZHHqSF0/K1pBpMqQSSwJ
|
||||
// http://play.golang.org/p/BFFDxphKYK
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/clbanning/mxj"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Demo how to re-label a key using mv.NewMap().
|
||||
// Need to normalize from an XML stream the tags "netid" and "idnet".
|
||||
// Solution: make everything "netid".
|
||||
|
||||
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 main() {
|
||||
// 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 := mxj.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))
|
||||
}
|
||||
}
|
|
@ -1,240 +0,0 @@
|
|||
// https://groups.google.com/forum/#!topic/golang-nuts/V83jUKluLnM
|
||||
// http://play.golang.org/p/alWGk4MDBc
|
||||
|
||||
// Here messsages come in one of three forms:
|
||||
// <GetClaimStatusCodesResult>... list of ClaimStatusCodeRecord ...</GetClaimStatusCodeResult>
|
||||
// <GetClaimStatusCodesResult>... one instance of ClaimStatusCodeRecord ...</GetClaimStatusCodeResult>
|
||||
// <GetClaimStatusCodesResult>... empty element ...</GetClaimStatusCodeResult>
|
||||
// ValueForPath
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/clbanning/mxj"
|
||||
)
|
||||
|
||||
var xmlmsg1 = []byte(`<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
|
||||
<s:Body>
|
||||
<GetClaimStatusCodesResponse xmlns="http://tempuri.org/">
|
||||
<GetClaimStatusCodesResult xmlns:a="http://schemas.datacontract.org/2004/07/MRA.Claim.WebService.Domain" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<a:ClaimStatusCodeRecord>
|
||||
<a:Active>true</a:Active>
|
||||
<a:Code>A</a:Code>
|
||||
<a:Description>Initial Claim Review/Screening</a:Description>
|
||||
</a:ClaimStatusCodeRecord>
|
||||
<a:ClaimStatusCodeRecord>
|
||||
<a:Active>true</a:Active>
|
||||
<a:Code>B</a:Code>
|
||||
<a:Description>Initial Contact Made w/ Provider</a:Description>
|
||||
</a:ClaimStatusCodeRecord>
|
||||
</GetClaimStatusCodesResult>
|
||||
</GetClaimStatusCodesResponse>
|
||||
</s:Body>
|
||||
</s:Envelope>
|
||||
`)
|
||||
|
||||
var xmlmsg2 = []byte(`<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
|
||||
<s:Body>
|
||||
<GetClaimStatusCodesResponse xmlns="http://tempuri.org/">
|
||||
<GetClaimStatusCodesResult xmlns:a="http://schemas.datacontract.org/2004/07/MRA.Claim.WebService.Domain" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<a:ClaimStatusCodeRecord>
|
||||
<a:Active>true</a:Active>
|
||||
<a:Code>A</a:Code>
|
||||
<a:Description>Initial Claim Review/Screening</a:Description>
|
||||
</a:ClaimStatusCodeRecord>
|
||||
</GetClaimStatusCodesResult>
|
||||
</GetClaimStatusCodesResponse>
|
||||
</s:Body>
|
||||
</s:Envelope>
|
||||
`)
|
||||
|
||||
var xmlmsg3 = []byte(`<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
|
||||
<s:Body>
|
||||
<GetClaimStatusCodesResponse xmlns="http://tempuri.org/">
|
||||
<GetClaimStatusCodesResult xmlns:a="http://schemas.datacontract.org/2004/07/MRA.Claim.WebService.Domain" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
|
||||
</GetClaimStatusCodesResult>
|
||||
</GetClaimStatusCodesResponse>
|
||||
</s:Body>
|
||||
</s:Envelope>
|
||||
`)
|
||||
|
||||
func main() {
|
||||
xmldata := [][]byte{xmlmsg1, xmlmsg2, xmlmsg3}
|
||||
fullPath(xmldata)
|
||||
partPath1(xmlmsg1)
|
||||
partPath2(xmlmsg1)
|
||||
partPath3(xmlmsg1)
|
||||
partPath4(xmlmsg1)
|
||||
partPath5(xmlmsg1)
|
||||
partPath6(xmlmsg1)
|
||||
}
|
||||
|
||||
func fullPath(xmldata [][]byte) {
|
||||
for i, msg := range xmldata {
|
||||
fmt.Println("\ndoc:", i)
|
||||
fmt.Println(string(msg))
|
||||
// decode the XML
|
||||
m, _ := mxj.NewMapXml(msg)
|
||||
|
||||
// get the value for the key path of interest
|
||||
path := "Envelope.Body.GetClaimStatusCodesResponse.GetClaimStatusCodesResult.ClaimStatusCodeRecord"
|
||||
values, err := m.ValueForPath(path)
|
||||
if err != nil {
|
||||
fmt.Println("err:", err.Error())
|
||||
return
|
||||
}
|
||||
if values == nil {
|
||||
fmt.Println("path:", path)
|
||||
fmt.Println("No ClaimStatusCodesResult code records.")
|
||||
continue
|
||||
}
|
||||
fmt.Println("\nPath:", path)
|
||||
fmt.Println("Number of code records:", len(values))
|
||||
fmt.Println("values:", values, "\n")
|
||||
for _, v := range values {
|
||||
switch v.(type) {
|
||||
case map[string]interface{}:
|
||||
fmt.Println("map[string]interface{}:", v.(map[string]interface{}))
|
||||
case []map[string]interface{}:
|
||||
fmt.Println("[]map[string]interface{}:", v.([]map[string]interface{}))
|
||||
case []interface{}:
|
||||
fmt.Println("[]interface{}:", v.([]interface{}))
|
||||
case interface{}:
|
||||
fmt.Println("interface{}:", v.(interface{}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func partPath1(msg []byte) {
|
||||
fmt.Println("\nmsg:", string(msg))
|
||||
m, _ := mxj.NewMapXml(msg)
|
||||
fmt.Println("m:", m.StringIndent())
|
||||
path := "Envelope.Body.*.*.ClaimStatusCodeRecord"
|
||||
values, err := m.ValueForPath(path)
|
||||
if err != nil {
|
||||
fmt.Println("err:", err.Error())
|
||||
return
|
||||
}
|
||||
if values == nil {
|
||||
fmt.Println("path:", path)
|
||||
fmt.Println("No ClaimStatusCodesResult code records.")
|
||||
return
|
||||
}
|
||||
fmt.Println("\nPath:", path)
|
||||
fmt.Println("Number of code records:", len(values))
|
||||
for n, v := range values {
|
||||
fmt.Printf("\t#%d: %v\n", n, v)
|
||||
}
|
||||
}
|
||||
|
||||
func partPath2(msg []byte) {
|
||||
fmt.Println("\nmsg:", string(msg))
|
||||
m, _ := mxj.NewMapXml(msg)
|
||||
fmt.Println("m:", m.StringIndent())
|
||||
path := "Envelope.Body.*.*.*"
|
||||
values, err := m.ValueForPath(path)
|
||||
if err != nil {
|
||||
fmt.Println("err:", err.Error())
|
||||
return
|
||||
}
|
||||
if values == nil {
|
||||
fmt.Println("path:", path)
|
||||
fmt.Println("No ClaimStatusCodesResult code records.")
|
||||
return
|
||||
}
|
||||
fmt.Println("\nPath:", path)
|
||||
fmt.Println("Number of code records:", len(values))
|
||||
for n, v := range values {
|
||||
fmt.Printf("\t#%d: %v\n", n, v)
|
||||
}
|
||||
}
|
||||
|
||||
func partPath3(msg []byte) {
|
||||
fmt.Println("\nmsg:", string(msg))
|
||||
m, _ := mxj.NewMapXml(msg)
|
||||
fmt.Println("m:", m.StringIndent())
|
||||
path := "*.*.*.*.*"
|
||||
values, err := m.ValueForPath(path)
|
||||
if err != nil {
|
||||
fmt.Println("err:", err.Error())
|
||||
return
|
||||
}
|
||||
if values == nil {
|
||||
fmt.Println("path:", path)
|
||||
fmt.Println("No ClaimStatusCodesResult code records.")
|
||||
return
|
||||
}
|
||||
fmt.Println("\nPath:", path)
|
||||
fmt.Println("Number of code records:", len(values))
|
||||
for n, v := range values {
|
||||
fmt.Printf("\t#%d: %v\n", n, v)
|
||||
}
|
||||
}
|
||||
|
||||
func partPath4(msg []byte) {
|
||||
fmt.Println("\nmsg:", string(msg))
|
||||
m, _ := mxj.NewMapXml(msg)
|
||||
fmt.Println("m:", m.StringIndent())
|
||||
path := "*.*.*.*.*.Description"
|
||||
values, err := m.ValueForPath(path)
|
||||
if err != nil {
|
||||
fmt.Println("err:", err.Error())
|
||||
return
|
||||
}
|
||||
if values == nil {
|
||||
fmt.Println("path:", path)
|
||||
fmt.Println("No ClaimStatusCodesResult code records.")
|
||||
return
|
||||
}
|
||||
fmt.Println("\nPath:", path)
|
||||
fmt.Println("Number of code records:", len(values))
|
||||
for n, v := range values {
|
||||
fmt.Printf("\t#%d: %v\n", n, v)
|
||||
}
|
||||
}
|
||||
|
||||
func partPath5(msg []byte) {
|
||||
fmt.Println("\nmsg:", string(msg))
|
||||
m, _ := mxj.NewMapXml(msg)
|
||||
fmt.Println("m:", m.StringIndent())
|
||||
path := "*.*.*.*.*.*"
|
||||
values, err := m.ValueForPath(path)
|
||||
if err != nil {
|
||||
fmt.Println("err:", err.Error())
|
||||
return
|
||||
}
|
||||
if values == nil {
|
||||
fmt.Println("path:", path)
|
||||
fmt.Println("No ClaimStatusCodesResult code records.")
|
||||
return
|
||||
}
|
||||
fmt.Println("\nPath:", path)
|
||||
fmt.Println("Number of code records:", len(values))
|
||||
for n, v := range values {
|
||||
fmt.Printf("\t#%d: %v\n", n, v)
|
||||
}
|
||||
}
|
||||
|
||||
func partPath6(msg []byte) {
|
||||
fmt.Println("\nmsg:", string(msg))
|
||||
m, _ := mxj.NewMapXml(msg)
|
||||
fmt.Println("m:", m.StringIndent())
|
||||
path := "*.*.*.*.*.*.*"
|
||||
values, err := m.ValueForPath(path)
|
||||
if err != nil {
|
||||
fmt.Println("err:", err.Error())
|
||||
return
|
||||
}
|
||||
if values == nil {
|
||||
fmt.Println("path:", path)
|
||||
fmt.Println("No ClaimStatusCodesResult code records.")
|
||||
return
|
||||
}
|
||||
fmt.Println("\nPath:", path)
|
||||
fmt.Println("Number of code records:", len(values))
|
||||
for n, v := range values {
|
||||
fmt.Printf("\t#%d: %v\n", n, v)
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
// https://groups.google.com/forum/#!topic/golang-nuts/cok6xasvI3w
|
||||
// retrieve 'src' values from 'image' tags
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/clbanning/mxj"
|
||||
)
|
||||
|
||||
var xmldata = []byte(`
|
||||
<doc>
|
||||
<image src="something.png"></image>
|
||||
<something>
|
||||
<image src="something else.jpg"></image>
|
||||
<title>something else</title>
|
||||
</something>
|
||||
<more_stuff>
|
||||
<some_images>
|
||||
<image src="first.gif"></image>
|
||||
<image src="second.gif"></image>
|
||||
</some_images>
|
||||
</more_stuff>
|
||||
</doc>`)
|
||||
|
||||
func main() {
|
||||
fmt.Println("xmldata:", string(xmldata))
|
||||
|
||||
// get all image tag values - []interface{}
|
||||
m, merr := mxj.NewMapXml(xmldata)
|
||||
if merr != nil {
|
||||
fmt.Println("merr:", merr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// grab all values for attribute "src"
|
||||
// Note: attributes are prepended with a hyphen, '-'.
|
||||
sources, err := m.ValuesForKey("-src")
|
||||
if err != nil {
|
||||
fmt.Println("err:", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for _, src := range sources {
|
||||
fmt.Println(src)
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
// https://groups.google.com/forum/#!topic/golang-nuts/-N9Toa6qlu8
|
||||
// shows that you can extract attribute values directly from tag/key path.
|
||||
// NOTE: attribute values are encoded by prepending a hyphen, '-'.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/clbanning/mxj"
|
||||
)
|
||||
|
||||
var xmldata = []byte(`
|
||||
<doc>
|
||||
<some_tag>
|
||||
<geoInfo>
|
||||
<city name="SEATTLE"/>
|
||||
<state name="WA"/>
|
||||
<country name="USA"/>
|
||||
</geoInfo>
|
||||
<geoInfo>
|
||||
<city name="VANCOUVER"/>
|
||||
<state name="BC"/>
|
||||
<country name="CA"/>
|
||||
</geoInfo>
|
||||
<geoInfo>
|
||||
<city name="LONDON"/>
|
||||
<country name="UK"/>
|
||||
</geoInfo>
|
||||
</some_tag>
|
||||
</doc>`)
|
||||
|
||||
func main() {
|
||||
fmt.Println("xmldata:", string(xmldata))
|
||||
|
||||
m, merr := mxj.NewMapXml(xmldata)
|
||||
if merr != nil {
|
||||
fmt.Println("merr:", merr)
|
||||
return
|
||||
}
|
||||
|
||||
// Attributes are keys with prepended hyphen, '-'.
|
||||
values, err := m.ValuesForPath("doc.some_tag.geoInfo.country.-name")
|
||||
if err != nil {
|
||||
fmt.Println("err:", err.Error())
|
||||
}
|
||||
|
||||
for _, v := range values {
|
||||
fmt.Println("v:", v)
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
// gonuts5.go - from https://groups.google.com/forum/#!topic/golang-nuts/MWoYY19of3o
|
||||
// problem is to extract entries from <lst name="list3-1-1-1"></lst> by "int name="
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/clbanning/mxj"
|
||||
)
|
||||
|
||||
var xmlData = []byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<response>
|
||||
<lst name="list1">
|
||||
</lst>
|
||||
<lst name="list2">
|
||||
</lst>
|
||||
<lst name="list3">
|
||||
<int name="docId">1</int>
|
||||
<lst name="list3-1">
|
||||
<lst name="list3-1-1">
|
||||
<lst name="list3-1-1-1">
|
||||
<int name="field1">1</int>
|
||||
<int name="field2">2</int>
|
||||
<int name="field3">3</int>
|
||||
<int name="field4">4</int>
|
||||
<int name="field5">5</int>
|
||||
</lst>
|
||||
</lst>
|
||||
<lst name="list3-1-2">
|
||||
<lst name="list3-1-2-1">
|
||||
<int name="field1">1</int>
|
||||
<int name="field2">2</int>
|
||||
<int name="field3">3</int>
|
||||
<int name="field4">4</int>
|
||||
<int name="field5">5</int>
|
||||
</lst>
|
||||
</lst>
|
||||
</lst>
|
||||
</lst>
|
||||
</response>`)
|
||||
|
||||
func main() {
|
||||
// parse XML into a Map
|
||||
m, merr := mxj.NewMapXml(xmlData)
|
||||
if merr != nil {
|
||||
fmt.Println("merr:", merr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// extract the 'list3-1-1-1' node - there'll be just 1?
|
||||
// NOTE: attribute keys are prepended with '-'
|
||||
lstVal, lerr := m.ValuesForPath("*.*.*.*.*", "-name:list3-1-1-1")
|
||||
if lerr != nil {
|
||||
fmt.Println("ierr:", lerr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// assuming just one value returned - create a new Map
|
||||
mv := mxj.Map(lstVal[0].(map[string]interface{}))
|
||||
|
||||
// extract the 'int' values by 'name' attribute: "-name"
|
||||
// interate over list of 'name' values of interest
|
||||
var names = []string{"field1", "field2", "field3", "field4", "field5"}
|
||||
for _, n := range names {
|
||||
vals, verr := mv.ValuesForKey("int", "-name:"+n)
|
||||
if verr != nil {
|
||||
fmt.Println("verr:", verr.Error(), len(vals))
|
||||
return
|
||||
}
|
||||
|
||||
// values for simple elements have key '#text'
|
||||
// NOTE: there can be only one value for key '#text'
|
||||
fmt.Println(n, ":", vals[0].(map[string]interface{})["#text"])
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
// gonuts5.go - from https://groups.google.com/forum/#!topic/golang-nuts/MWoYY19of3o
|
||||
// problem is to extract entries from <lst name="list3-1-1-1"></lst> by "int name="
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/clbanning/mxj"
|
||||
)
|
||||
|
||||
var xmlData = []byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<response>
|
||||
<lst name="list1">
|
||||
</lst>
|
||||
<lst name="list2">
|
||||
</lst>
|
||||
<lst name="list3">
|
||||
<int name="docId">1</int>
|
||||
<lst name="list3-1">
|
||||
<lst name="list3-1-1">
|
||||
<lst name="list3-1-1-1">
|
||||
<int name="field1">1</int>
|
||||
<int name="field2">2</int>
|
||||
<int name="field3">3</int>
|
||||
<int name="field4">4</int>
|
||||
<int name="field5">5</int>
|
||||
</lst>
|
||||
</lst>
|
||||
<lst name="list3-1-2">
|
||||
<lst name="list3-1-2-1">
|
||||
<int name="field1">1</int>
|
||||
<int name="field2">2</int>
|
||||
<int name="field3">3</int>
|
||||
<int name="field4">4</int>
|
||||
<int name="field5">5</int>
|
||||
</lst>
|
||||
</lst>
|
||||
</lst>
|
||||
</lst>
|
||||
</response>`)
|
||||
|
||||
func main() {
|
||||
// parse XML into a Map
|
||||
m, merr := mxj.NewMapXml(xmlData)
|
||||
if merr != nil {
|
||||
fmt.Println("merr:", merr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// extract the 'list3-1-1-1' node - there'll be just 1?
|
||||
// NOTE: attribute keys are prepended with '-'
|
||||
lstVal, lerr := m.ValuesForPath("*.*.*.*.*", "-name:list3-1-1-1")
|
||||
if lerr != nil {
|
||||
fmt.Println("ierr:", lerr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// assuming just one value returned - create a new Map
|
||||
mv := mxj.Map(lstVal[0].(map[string]interface{}))
|
||||
|
||||
// extract the 'int' values by 'name' attribute: "-name"
|
||||
// interate over list of 'name' values of interest
|
||||
var names = []string{"field1", "field2", "field3", "field4", "field5"}
|
||||
for _, n := range names {
|
||||
vals, verr := mv.ValuesForKey("*", "-name:"+n)
|
||||
if verr != nil {
|
||||
fmt.Println("verr:", verr.Error(), len(vals))
|
||||
return
|
||||
}
|
||||
|
||||
// values for simple elements have key '#text'
|
||||
// NOTE: there can be only one value for key '#text'
|
||||
fmt.Println(n, ":", vals[0].(map[string]interface{})["#text"])
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
/* https://groups.google.com/forum/#!topic/golang-nuts/EMXHB1nJoBA
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "encoding/json"
|
||||
|
||||
func main() {
|
||||
var v struct {
|
||||
DBInstances []struct {
|
||||
Endpoint struct {
|
||||
Address string
|
||||
}
|
||||
}
|
||||
}
|
||||
json.Unmarshal(s, &v)
|
||||
fmt.Println(v.DBInstances[0].Endpoint.Address)
|
||||
}
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "github.com/clbanning/mxj"
|
||||
|
||||
func main() {
|
||||
m, err := mxj.NewMapJson(s)
|
||||
if err != nil {
|
||||
fmt.Println("err:", err.Error())
|
||||
}
|
||||
v, err := m.ValuesForKey("Address")
|
||||
// v, err := m.ValuesForPath("DBInstances[0].Endpoint.Address")
|
||||
if err != nil {
|
||||
fmt.Println("err:", err.Error())
|
||||
}
|
||||
if len(v) > 0 {
|
||||
fmt.Println(v[0].(string))
|
||||
} else {
|
||||
fmt.Println("No value.")
|
||||
}
|
||||
}
|
||||
|
||||
var s = []byte(`{ "DBInstances": [ { "PubliclyAccessible": true, "MasterUsername": "postgres", "LicenseModel": "postgresql-license", "VpcSecurityGroups": [ { "Status": "active", "VpcSecurityGroupId": "sg-e72a4282" } ], "InstanceCreateTime": "2014-06-29T03:52:59.268Z", "OptionGroupMemberships": [ { "Status": "in-sync", "OptionGroupName": "default:postgres-9-3" } ], "PendingModifiedValues": {}, "Engine": "postgres", "MultiAZ": true, "LatestRestorableTime": "2014-06-29T12:00:34Z", "DBSecurityGroups": [ { "Status": "active", "DBSecurityGroupName": "production-dbsecuritygroup-q4f0ugxpjck8" } ], "DBParameterGroups": [ { "DBParameterGroupName": "default.postgres9.3", "ParameterApplyStatus": "in-sync" } ], "AutoMinorVersionUpgrade": true, "PreferredBackupWindow": "06:59-07:29", "DBSubnetGroup": { "Subnets": [ { "SubnetStatus": "Active", "SubnetIdentifier": "subnet-34e5d01c", "SubnetAvailabilityZone": { "Name": "us-east-1b", "ProvisionedIopsCapable": false } }, { "SubnetStatus": "Active", "SubnetIdentifier": "subnet-50759d27", "SubnetAvailabilityZone": { "Name": "us-east-1c", "ProvisionedIopsCapable": false } }, { "SubnetStatus": "Active", "SubnetIdentifier": "subnet-450a1f03", "SubnetAvailabilityZone": { "Name": "us-east-1d", "ProvisionedIopsCapable": false } } ], "DBSubnetGroupName": "default", "VpcId": "vpc-acb86cc9", "DBSubnetGroupDescription": "default", "SubnetGroupStatus": "Complete" }, "SecondaryAvailabilityZone": "us-east-1b", "ReadReplicaDBInstanceIdentifiers": [], "AllocatedStorage": 15, "BackupRetentionPeriod": 1, "DBName": "deis", "PreferredMaintenanceWindow": "fri:05:52-fri:06:22", "Endpoint": { "Port": 5432, "Address": "production.cfk8mskkbkeu.us-east-1.rds.amazonaws.com" }, "DBInstanceStatus": "available", "EngineVersion": "9.3.3", "AvailabilityZone": "us-east-1c", "DBInstanceClass": "db.m1.small", "DBInstanceIdentifier": "production" } ] }`)
|
Binary file not shown.
|
@ -1,299 +0,0 @@
|
|||
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) {
|
||||
x := XmlWriterBufSize
|
||||
XmlWriterBufSize = 0
|
||||
defer func() {
|
||||
XmlWriterBufSize = x
|
||||
}()
|
||||
|
||||
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) {
|
||||
x := XmlWriterBufSize
|
||||
XmlWriterBufSize = 0
|
||||
defer func() {
|
||||
XmlWriterBufSize = x
|
||||
}()
|
||||
|
||||
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
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
{ "this":"is", "a":"test", "file":"for", "files_test.go":"case" }
|
||||
{ "with":"some", "bad":JSON, "in":"it" }
|
|
@ -1,9 +0,0 @@
|
|||
<doc>
|
||||
<some>test</some>
|
||||
<data>for files.go</data>
|
||||
</doc>
|
||||
<msg>
|
||||
<just>some</just>
|
||||
<another>doc</other>
|
||||
<for>test case</for>
|
||||
</msg>
|
|
@ -1,168 +0,0 @@
|
|||
package mxj
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFilesHeader(t *testing.T) {
|
||||
fmt.Println("\n---------------- files_test.go ...\n")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
{ "this":"is", "a":"test", "file":"for", "files_test.go":"case" }
|
||||
{ "with":"just", "two":2, "JSON":"values", "true":true }
|
|
@ -1,9 +0,0 @@
|
|||
<doc>
|
||||
<some>test</some>
|
||||
<data>for files.go</data>
|
||||
</doc>
|
||||
<msg>
|
||||
<just>some</just>
|
||||
<another>doc</another>
|
||||
<for>test case</for>
|
||||
</msg>
|
|
@ -1 +0,0 @@
|
|||
{"a":"test","file":"for","files_test.go":"case","this":"is"}{"JSON":"values","true":true,"two":2,"with":"just"}
|
|
@ -1 +0,0 @@
|
|||
<doc><some>test</some><data>for files.go</data></doc><msg><just>some</just><another>doc</another><for>test case</for></msg>
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"a": "test",
|
||||
"file": "for",
|
||||
"files_test.go": "case",
|
||||
"this": "is"
|
||||
}
|
||||
{
|
||||
"JSON": "values",
|
||||
"true": true,
|
||||
"two": 2,
|
||||
"with": "just"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
<doc>
|
||||
<some>test</some>
|
||||
<data>for files.go</data>
|
||||
</doc><msg>
|
||||
<just>some</just>
|
||||
<another>doc</another>
|
||||
<for>test case</for>
|
||||
</msg>
|
|
@ -1,178 +0,0 @@
|
|||
// 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 (
|
||||
. "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
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
// 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))
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
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 ...\n")
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
|
@ -1,319 +0,0 @@
|
|||
// 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 -----------------------------
|
||||
|
||||
// Parse numeric values as json.Number types - see encoding/json#Number
|
||||
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
|
||||
}
|
|
@ -1,137 +0,0 @@
|
|||
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 ...\n")
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
|
@ -1,620 +0,0 @@
|
|||
// 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 (
|
||||
"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".
|
||||
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
|
||||
// ret := make([]interface{}, 0)
|
||||
// hasKey(m, key, &ret, subKeyMap)
|
||||
// return ret, 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)
|
||||
// hasKey(v, key, ret, subkeys)
|
||||
}
|
||||
case []interface{}:
|
||||
for _, v := range iv.([]interface{}) {
|
||||
hasKey(v, key, ret, cnt, subkeys)
|
||||
// hasKey(v, key, ret, 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".
|
||||
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)
|
||||
// valuesForKeyPath(&ivals, m, keys, subKeyMap)
|
||||
// return ivals, nil
|
||||
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, ":")
|
||||
switch len(vv) {
|
||||
case 2:
|
||||
m[vv[0]] = interface{}(vv[1])
|
||||
case 3:
|
||||
switch vv[3] {
|
||||
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 {
|
||||
if crumbs == "" {
|
||||
crumbs = key
|
||||
} else {
|
||||
crumbs += "." + key
|
||||
}
|
||||
// *basket = append(*basket, crumb)
|
||||
basket[crumbs] = 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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,412 +0,0 @@
|
|||
// 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 ...\n")
|
||||
}
|
||||
|
||||
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 ...\n")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
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"
|
||||
)
|
||||
|
||||
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.
|
||||
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] == "-" {
|
||||
continue
|
||||
}
|
||||
getLeafNodes(path, k, v, l, noattr)
|
||||
}
|
||||
case []interface{}:
|
||||
for i, v := range mv.([]interface{}) {
|
||||
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
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
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)
|
||||
}
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
// 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"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
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), offset...)
|
||||
}
|
||||
|
||||
// writeMap - dumps the map[string]interface{} for examination.
|
||||
// 'offset' is initial indentation count; typically: Write(m).
|
||||
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 += k + " :" + writeMap(v, indent+1)
|
||||
}
|
||||
default:
|
||||
// shouldn't ever be here ...
|
||||
s += fmt.Sprintf("[unknown] %#v", m)
|
||||
}
|
||||
return s
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package mxj
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMxjHeader(t *testing.T) {
|
||||
fmt.Println("\n---------------- mxj_test.go ...\n")
|
||||
}
|
||||
|
||||
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:", m)
|
||||
fmt.Println("TestMap, StringIndent:", m.StringIndent())
|
||||
|
||||
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:", m)
|
||||
fmt.Println("TestMap, StringIndent:", m.StringIndent())
|
||||
|
||||
mm, _ := m.Copy()
|
||||
fmt.Println("TestMap, m.Copy():", mm)
|
||||
}
|
|
@ -1,183 +0,0 @@
|
|||
// 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 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)
|
||||
}
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
package mxj
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewMapHeader(t *testing.T) {
|
||||
fmt.Println("\n---------------- newmap_test.go ...\n")
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
<h2>mxj - to/from maps, XML and JSON</h2>
|
||||
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. If you want the old syntax, use mxj/x2j and mxj/j2x packages.
|
||||
|
||||
<h4>Notices</h4>
|
||||
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 / JSON / struct</h4>
|
||||
<pre>type Map map[string]interface{}</pre>
|
||||
|
||||
Create a `Map` value, 'm', from any `map[string]interface{}` value, 'v':
|
||||
<pre>m := Map(v)</pre>
|
||||
|
||||
Unmarshal / marshal XML as a `Map` value, 'm':
|
||||
<pre>m, err := NewMapXml(xmlValue) // unmarshal
|
||||
xmlValue, err := m.Xml() // marshal</pre>
|
||||
|
||||
Unmarshal XML from an `io.Reader` as a `Map` value, 'm':
|
||||
<pre>m, err := NewMapReader(xmlReader) // repeated calls, as with an os.File Reader, will process stream
|
||||
m, raw, err := NewMapReaderRaw(xmlReader) // 'raw' is the raw XML that was decoded</pre>
|
||||
|
||||
Marshal `Map` value, 'm', to an XML Writer (`io.Writer`):
|
||||
<pre>err := m.XmlWriter(xmlWriter)
|
||||
raw, err := m.XmlWriterRaw(xmlWriter) // 'raw' is the raw XML that was written on xmlWriter</pre>
|
||||
|
||||
Also, for prettified output:
|
||||
<pre>xmlValue, err := m.XmlIndent(prefix, indent, ...)
|
||||
err := m.XmlIndentWriter(xmlWriter, prefix, indent, ...)
|
||||
raw, err := m.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>m, err := NewMapStruct(structVal)
|
||||
err := m.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 := m.PathForKeyShortest(key)
|
||||
values, err := m.ValuesForKey(key, subkeys)
|
||||
values, err := m.ValuesForPath(path, subkeys)
|
||||
count, err := m.UpdateValuesForPath(newVal, path, subkeys)</pre>
|
||||
|
||||
Get everything at once, irrespective of path depth:
|
||||
<pre>leafnodes := m.LeafNodes()
|
||||
leafvalues := m.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 := m.NewMap("oldKey_1:newKey_1", "oldKey_2:newKey_2", ..., "oldKey_N:newKey_N")
|
||||
newXml := newMap.Xml() // for example
|
||||
newJson := 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>
|
||||
|
||||
- Attributes are parsed to `map[string]interface{}` values by prefixing a hyphen, `-`,
|
||||
to the attribute label. (Unless overridden by `PrependAttrWithHyphen(false)`.)
|
||||
- If the element is a simple element and has attributes, the element value
|
||||
is given the key `#text` for its `map[string]interface{}` representation. (See
|
||||
the 'atomFeedString.xml' test data, below.)
|
||||
|
||||
<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.
|
||||
|
||||
<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{}` 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 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 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.
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
// 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 ...\n")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
// 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 (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/fatih/structs"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Create a new Map value from a structure. Error returned if argument is not a structure
|
||||
// or if there is a json.Marshal or json.Unmarshal error.
|
||||
// Only public structure fields are decoded in the Map value. Also, json.Marshal structure encoding rules
|
||||
// are followed for decoding the structure fields.
|
||||
func NewMapStruct(structVal interface{}) (Map, error) {
|
||||
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 {
|
||||
m := map[string]interface{}(mv)
|
||||
j, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
return json.Unmarshal(j, structPtr)
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
package mxj
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStructHeader(t *testing.T) {
|
||||
fmt.Println("\n---------------- struct_test.go ...\n")
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
|
@ -1,249 +0,0 @@
|
|||
// 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
|
||||
|
||||
// updatevalues.go - modify a value based on path and possibly sub-keys
|
||||
|
||||
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".
|
||||
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), ":")
|
||||
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 ok := hasSubKeys(m, subkeys); ok {
|
||||
(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 len(subkeys) == 0 {
|
||||
(m.(map[string]interface{}))[keys0] = value
|
||||
(*cnt)++
|
||||
break
|
||||
}
|
||||
nv := make([]interface{}, 0)
|
||||
var valmodified bool
|
||||
for _, v := range endVal.([]interface{}) {
|
||||
// check entry subkeys
|
||||
if ok := hasSubKeys(v, subkeys); ok {
|
||||
// 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 len(subkeys) == 0 {
|
||||
(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 ok := hasSubKeys(endVal, subkeys); !ok {
|
||||
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
|
||||
}
|
|
@ -1,187 +0,0 @@
|
|||
// modifyvalues_test.go - test keyvalues.go methods
|
||||
|
||||
package mxj
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUVHeader(t *testing.T) {
|
||||
fmt.Println("\n---------------- updatevalues_test.go ...\n")
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
|
@ -1,184 +0,0 @@
|
|||
// 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 (
|
||||
. "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
|
||||
}
|
|
@ -1,838 +0,0 @@
|
|||
// 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
|
||||
|
||||
// xml.go - basically the core of X2j for map[string]interface{} values.
|
||||
// NewMapXml, NewMapXmlReader, mv.Xml, mv.XmlWriter
|
||||
// see x2j and j2x for wrappers to provide end-to-end transformation of XML and JSON messages.
|
||||
|
||||
package mxj
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ------------------- NewMapXml & NewMapXmlReader ... from x2j2 -------------------------
|
||||
|
||||
// If XmlCharsetReader != nil, it will be used to decode the XML, if required.
|
||||
// import (
|
||||
// charset "code.google.com/p/go-charset/charset"
|
||||
// github.com/clbanning/mxj
|
||||
// )
|
||||
// ...
|
||||
// mxj.XmlCharsetReader = charset.NewReader
|
||||
// m, merr := mxj.NewMapXml(xmlValue)
|
||||
var XmlCharsetReader func(charset string, input io.Reader) (io.Reader, error)
|
||||
|
||||
// NewMapXml - convert a XML doc into a Map
|
||||
// (This is analogous to unmarshalling a JSON string to map[string]interface{} using json.Unmarshal().)
|
||||
// If the optional argument 'cast' is 'true', then values will be converted to boolean or float64 if possible.
|
||||
//
|
||||
// Converting XML to JSON is a simple as:
|
||||
// ...
|
||||
// mapVal, merr := mxj.NewMapXml(xmlVal)
|
||||
// if merr != nil {
|
||||
// // handle error
|
||||
// }
|
||||
// jsonVal, jerr := mapVal.Json()
|
||||
// if jerr != nil {
|
||||
// // handle error
|
||||
// }
|
||||
func NewMapXml(xmlVal []byte, cast ...bool) (Map, error) {
|
||||
var r bool
|
||||
if len(cast) == 1 {
|
||||
r = cast[0]
|
||||
}
|
||||
n, err := xmlToTree(xmlVal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := make(map[string]interface{}, 0)
|
||||
m[n.key] = n.treeToMap(r)
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Get next XML doc from an io.Reader as a Map value. Returns Map value.
|
||||
func NewMapXmlReader(xmlReader io.Reader, cast ...bool) (Map, error) {
|
||||
var r bool
|
||||
if len(cast) == 1 {
|
||||
r = cast[0]
|
||||
}
|
||||
|
||||
// build the node tree
|
||||
n, err := xmlReaderToTree(xmlReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create the Map value
|
||||
m := make(map[string]interface{})
|
||||
m[n.key] = n.treeToMap(r)
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// XmlWriterBufSize - set the size of io.Writer for the TeeReader used by NewMapXmlReaderRaw()
|
||||
// and HandleXmlReaderRaw(). This reduces repeated memory allocations and copy() calls in most cases.
|
||||
var XmlWriterBufSize int = 256
|
||||
|
||||
// Get next XML doc from an io.Reader as a Map value. Returns Map value and slice with the raw XML.
|
||||
// NOTES: 1. Due to the implementation of xml.Decoder, the raw XML off the reader is buffered to []byte
|
||||
// using a ByteReader. If the io.Reader is an os.File, there may be significant performance impact.
|
||||
// See the examples - getmetrics1.go through getmetrics4.go - for comparative use cases on a large
|
||||
// data set. If the io.Reader is wrapping a []byte value in-memory, however, such as http.Request.Body
|
||||
// you CAN use it to efficiently unmarshal a XML doc and retrieve the raw XML in a single call.
|
||||
// 2. The 'raw' return value may be larger than the XML text value. To log it, cast it to a string.
|
||||
func NewMapXmlReaderRaw(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, XmlWriterBufSize)
|
||||
wb := bytes.NewBuffer(buf)
|
||||
trdr := myTeeReader(xmlReader, wb) // see code at EOF
|
||||
|
||||
// build the node tree
|
||||
n, err := xmlReaderToTree(trdr)
|
||||
|
||||
// retrieve the raw XML that was decoded
|
||||
b := make([]byte, wb.Len())
|
||||
_, _ = wb.Read(b)
|
||||
|
||||
if err != nil {
|
||||
return nil, b, err
|
||||
}
|
||||
|
||||
// create the Map value
|
||||
m := make(map[string]interface{})
|
||||
m[n.key] = n.treeToMap(r)
|
||||
|
||||
return m, b, nil
|
||||
}
|
||||
|
||||
// xmlReaderToTree() - parse a XML io.Reader to a tree of nodes
|
||||
func xmlReaderToTree(rdr io.Reader) (*node, error) {
|
||||
// parse the Reader
|
||||
p := xml.NewDecoder(rdr)
|
||||
p.CharsetReader = XmlCharsetReader
|
||||
return xmlToTreeParser("", nil, p)
|
||||
}
|
||||
|
||||
// for building the parse tree
|
||||
type node struct {
|
||||
dup bool // is member of a list
|
||||
attr bool // is an attribute
|
||||
key string // XML tag
|
||||
val string // element value
|
||||
nodes []*node
|
||||
}
|
||||
|
||||
// xmlToTree - convert a XML doc into a tree of nodes.
|
||||
func xmlToTree(doc []byte) (*node, error) {
|
||||
// xml.Decoder doesn't properly handle whitespace in some doc
|
||||
// see songTextString.xml test case ...
|
||||
reg, _ := regexp.Compile("[ \t\n\r]*<")
|
||||
doc = reg.ReplaceAll(doc, []byte("<"))
|
||||
|
||||
b := bytes.NewReader(doc)
|
||||
p := xml.NewDecoder(b)
|
||||
p.CharsetReader = XmlCharsetReader
|
||||
n, berr := xmlToTreeParser("", nil, p)
|
||||
if berr != nil {
|
||||
return nil, berr
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// we allow people to drop hyphen when unmarshaling the XML doc.
|
||||
var useHyphen bool = true
|
||||
|
||||
// PrependAttrWithHyphen. Prepend attribute tags with a hyphen.
|
||||
// Default is 'true'.
|
||||
// Note:
|
||||
// If 'false', unmarshaling and marshaling is not symmetric. Attributes will be
|
||||
// marshal'd as <attr_tag>attr</attr_tag> and may be part of a list.
|
||||
func PrependAttrWithHyphen(v bool) {
|
||||
useHyphen = v
|
||||
}
|
||||
|
||||
// Include sequence id with inner tags. - per Sean Murphy, murphysean84@gmail.com.
|
||||
var includeTagSeqNum bool
|
||||
|
||||
// IncludeTagSeqNum - include a "_seq":N key:value pair with each inner tag, denoting
|
||||
// its position when parsed. E.g.,
|
||||
/*
|
||||
<Obj c="la" x="dee" h="da">
|
||||
<IntObj id="3"/>
|
||||
<IntObj1 id="1"/>
|
||||
<IntObj id="2"/>
|
||||
<StrObj>hello</StrObj>
|
||||
</Obj>
|
||||
|
||||
parses as:
|
||||
|
||||
{
|
||||
Obj:{
|
||||
"-c":"la",
|
||||
"-h":"da",
|
||||
"-x":"dee",
|
||||
"intObj":[
|
||||
{
|
||||
"-id"="3",
|
||||
"_seq":"0" // if mxj.Cast is passed, then: "_seq":0
|
||||
},
|
||||
{
|
||||
"-id"="2",
|
||||
"_seq":"2"
|
||||
}],
|
||||
"intObj1":{
|
||||
"-id":"1",
|
||||
"_seq":"1"
|
||||
},
|
||||
"StrObj":{
|
||||
"#text":"hello", // simple element value gets "#text" tag
|
||||
"_seq":"3"
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
func IncludeTagSeqNum(b bool) {
|
||||
includeTagSeqNum = b
|
||||
}
|
||||
|
||||
// xmlToTreeParser - load a 'clean' XML doc into a tree of *node.
|
||||
func xmlToTreeParser(skey string, a []xml.Attr, p *xml.Decoder) (*node, error) {
|
||||
n := new(node)
|
||||
n.nodes = make([]*node, 0)
|
||||
var seq int // for includeTagSeqNum
|
||||
|
||||
if skey != "" {
|
||||
n.key = skey
|
||||
if len(a) > 0 {
|
||||
for _, v := range a {
|
||||
na := new(node)
|
||||
na.attr = true
|
||||
if useHyphen {
|
||||
na.key = `-` + v.Name.Local
|
||||
} else {
|
||||
na.key = v.Name.Local
|
||||
}
|
||||
na.val = v.Value
|
||||
n.nodes = append(n.nodes, na)
|
||||
}
|
||||
}
|
||||
}
|
||||
for {
|
||||
t, err := p.Token()
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
return nil, errors.New("xml.Decoder.Token() - " + err.Error())
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
switch t.(type) {
|
||||
case xml.StartElement:
|
||||
tt := t.(xml.StartElement)
|
||||
// handle root
|
||||
if n.key == "" {
|
||||
n.key = tt.Name.Local
|
||||
if len(tt.Attr) > 0 {
|
||||
for _, v := range tt.Attr {
|
||||
na := new(node)
|
||||
na.attr = true
|
||||
if useHyphen {
|
||||
na.key = `-` + v.Name.Local
|
||||
} else {
|
||||
na.key = v.Name.Local
|
||||
}
|
||||
na.val = v.Value
|
||||
n.nodes = append(n.nodes, na)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nn, nnerr := xmlToTreeParser(tt.Name.Local, tt.Attr, p)
|
||||
if nnerr != nil {
|
||||
return nil, nnerr
|
||||
}
|
||||
n.nodes = append(n.nodes, nn)
|
||||
if includeTagSeqNum { // 2014.11.09
|
||||
sn := &node{false, false, "_seq", strconv.Itoa(seq), nil}
|
||||
nn.nodes = append(nn.nodes, sn)
|
||||
seq++
|
||||
}
|
||||
}
|
||||
case xml.EndElement:
|
||||
// scan n.nodes for duplicate n.key values
|
||||
n.markDuplicateKeys()
|
||||
return n, nil
|
||||
case xml.CharData:
|
||||
tt := string(t.(xml.CharData))
|
||||
// clean up possible noise
|
||||
tt = strings.Trim(tt, "\t\r\b\n ")
|
||||
if len(n.nodes) > 0 && len(tt) > 0 {
|
||||
// if len(n.nodes) > 0 {
|
||||
nn := new(node)
|
||||
nn.key = "#text"
|
||||
nn.val = tt
|
||||
n.nodes = append(n.nodes, nn)
|
||||
} else {
|
||||
n.val = tt
|
||||
}
|
||||
if includeTagSeqNum { // 2014.11.09
|
||||
if len(n.nodes) == 0 { // treat like a simple element with attributes
|
||||
nn := new(node)
|
||||
nn.key = "#text"
|
||||
nn.val = tt
|
||||
n.nodes = append(n.nodes, nn)
|
||||
}
|
||||
sn := &node{false, false, "_seq", strconv.Itoa(seq), nil}
|
||||
n.nodes = append(n.nodes, sn)
|
||||
seq++
|
||||
}
|
||||
default:
|
||||
// noop
|
||||
}
|
||||
}
|
||||
// Logically we can't get here, but provide an error message anyway.
|
||||
return nil, fmt.Errorf("Unknown parse error in xmlToTree() for: %s", n.key)
|
||||
}
|
||||
|
||||
// (*node)markDuplicateKeys - set node.dup flag for loading map[string]interface{}.
|
||||
func (n *node) markDuplicateKeys() {
|
||||
l := len(n.nodes)
|
||||
for i := 0; i < l; i++ {
|
||||
if n.nodes[i].dup {
|
||||
continue
|
||||
}
|
||||
for j := i + 1; j < l; j++ {
|
||||
if n.nodes[i].key == n.nodes[j].key {
|
||||
n.nodes[i].dup = true
|
||||
n.nodes[j].dup = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// (*node)treeToMap - convert a tree of nodes into a map[string]interface{}.
|
||||
// (Parses to map that is structurally the same as from json.Unmarshal().)
|
||||
// Note: root is not instantiated; call with: "m[n.key] = n.treeToMap(cast)".
|
||||
func (n *node) treeToMap(r bool) interface{} {
|
||||
if len(n.nodes) == 0 {
|
||||
return cast(n.val, r)
|
||||
}
|
||||
|
||||
m := make(map[string]interface{}, 0)
|
||||
for _, v := range n.nodes {
|
||||
// 2014.11.9 - may have to back out
|
||||
if includeTagSeqNum {
|
||||
if len(v.nodes) == 1 {
|
||||
m[v.key] = cast(v.val, r)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// just a value
|
||||
if !v.dup && len(v.nodes) == 0 {
|
||||
m[v.key] = cast(v.val, r)
|
||||
continue
|
||||
}
|
||||
// a list of values
|
||||
if v.dup {
|
||||
var a []interface{}
|
||||
if vv, ok := m[v.key]; ok {
|
||||
a = vv.([]interface{})
|
||||
} else {
|
||||
a = make([]interface{}, 0)
|
||||
}
|
||||
a = append(a, v.treeToMap(r))
|
||||
m[v.key] = interface{}(a)
|
||||
continue
|
||||
}
|
||||
|
||||
// it's a unique key
|
||||
m[v.key] = v.treeToMap(r)
|
||||
}
|
||||
|
||||
return interface{}(m)
|
||||
}
|
||||
|
||||
// cast - try to cast string values to bool or float64
|
||||
func cast(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
|
||||
// but be more strick - only allow TRUE, True, true, FALSE, False, false
|
||||
if s != "t" && s != "T" && s != "f" && s != "F" {
|
||||
if b, err := strconv.ParseBool(s); err == nil {
|
||||
return interface{}(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
return interface{}(s)
|
||||
}
|
||||
|
||||
// ------------------ END: NewMapXml & NewMapXmlReader -------------------------
|
||||
|
||||
// ------------------ mv.Xml & mv.XmlWriter - from j2x ------------------------
|
||||
|
||||
const (
|
||||
DefaultRootTag = "doc"
|
||||
)
|
||||
|
||||
var useGoXmlEmptyElemSyntax bool
|
||||
|
||||
// XmlGoEmptyElemSyntax() - <tag ...></tag> rather than <tag .../>.
|
||||
// Go's encoding/xml package marshals empty XML elements as <tag ...></tag>. By default this package
|
||||
// encodes empty elements as <tag .../>. If you're marshaling Map values that include structures
|
||||
// (which are passed to xml.Marshal for encoding), this will let you conform to the standard package.
|
||||
//
|
||||
// Alternatively, you can replace the encoding/xml/marshal.go file in the standard libary with the
|
||||
// patched version in the "xml_marshal" folder in this package. Then use xml.SetUseNullEndTag(true)
|
||||
// to have all XML encoding use <tag .../> for empty elements.
|
||||
func XmlGoEmptyElemSyntax() {
|
||||
useGoXmlEmptyElemSyntax = true
|
||||
}
|
||||
|
||||
// XmlDefaultEmptyElemSyntax() - <tag .../> rather than <tag ...></tag>.
|
||||
// Return XML encoding for empty elements to the default package setting.
|
||||
// Reverses effect of XmlGoEmptyElemSyntax().
|
||||
func XmlDefaultEmptyElemSyntax() {
|
||||
useGoXmlEmptyElemSyntax = false
|
||||
}
|
||||
|
||||
// Encode a Map as XML. The companion of NewMapXml().
|
||||
// The following rules apply.
|
||||
// - The key label "#text" is treated as the value for a simple element with attributes.
|
||||
// - Map keys that begin with a hyphen, '-', are interpreted as attributes.
|
||||
// It is an error if the attribute doesn't have a []byte, string, number, or boolean value.
|
||||
// - Map 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 "/>".
|
||||
// - 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>".
|
||||
// - To encode empty elements in a syntax consistent with encoding/xml call UseGoXmlEmptyElementSyntax().
|
||||
func (mv Map) Xml(rootTag ...string) ([]byte, error) {
|
||||
m := map[string]interface{}(mv)
|
||||
var err error
|
||||
s := new(string)
|
||||
p := new(pretty) // just a stub
|
||||
|
||||
if len(m) == 1 && len(rootTag) == 0 {
|
||||
for key, value := range m {
|
||||
// if it an array, see if all values are map[string]interface{}
|
||||
// we force a new root tag if we'll end up with no key:value in the list
|
||||
// so: key:[string_val, bool:true] --> <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 = mapToXmlIndent(false, s, DefaultRootTag, m, p)
|
||||
goto done
|
||||
}
|
||||
}
|
||||
}
|
||||
err = mapToXmlIndent(false, s, key, value, p)
|
||||
}
|
||||
} else if len(rootTag) == 1 {
|
||||
err = mapToXmlIndent(false, s, rootTag[0], m, p)
|
||||
} else {
|
||||
err = mapToXmlIndent(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.
|
||||
|
||||
// Writes the Map as XML on the Writer.
|
||||
// See Xml() for encoding rules.
|
||||
func (mv Map) XmlWriter(xmlWriter io.Writer, rootTag ...string) error {
|
||||
x, err := mv.Xml(rootTag...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = xmlWriter.Write(x)
|
||||
return err
|
||||
}
|
||||
|
||||
// Writes the Map as XML on the Writer. []byte is the raw XML that was written.
|
||||
// See Xml() for encoding rules.
|
||||
func (mv Map) XmlWriterRaw(xmlWriter io.Writer, rootTag ...string) ([]byte, error) {
|
||||
x, err := mv.Xml(rootTag...)
|
||||
if err != nil {
|
||||
return x, err
|
||||
}
|
||||
|
||||
_, err = xmlWriter.Write(x)
|
||||
return x, err
|
||||
}
|
||||
|
||||
// Writes the Map as pretty XML on the Writer.
|
||||
// See Xml() for encoding rules.
|
||||
func (mv Map) XmlIndentWriter(xmlWriter io.Writer, prefix, indent string, rootTag ...string) error {
|
||||
x, err := mv.XmlIndent(prefix, indent, rootTag...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = xmlWriter.Write(x)
|
||||
return err
|
||||
}
|
||||
|
||||
// Writes the Map as pretty XML on the Writer. []byte is the raw XML that was written.
|
||||
// See Xml() for encoding rules.
|
||||
func (mv Map) XmlIndentWriterRaw(xmlWriter io.Writer, prefix, indent string, rootTag ...string) ([]byte, error) {
|
||||
x, err := mv.XmlIndent(prefix, indent, rootTag...)
|
||||
if err != nil {
|
||||
return x, err
|
||||
}
|
||||
|
||||
_, err = xmlWriter.Write(x)
|
||||
return x, err
|
||||
}
|
||||
|
||||
// -------------------- END: mv.Xml & mv.XmlWriter -------------------------------
|
||||
|
||||
// -------------- Handle XML stream by processing Map value --------------------
|
||||
|
||||
// Default poll delay to keep Handler from spinning on an open stream
|
||||
// like sitting on os.Stdin waiting for imput.
|
||||
var xhandlerPollInterval = time.Duration(1e6)
|
||||
|
||||
// Bulk process XML using handlers that process a Map value.
|
||||
// 'rdr' is an io.Reader for XML (stream)
|
||||
// 'mapHandler' is the Map processor. 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 HandleXmlReader(xmlReader io.Reader, mapHandler func(Map) bool, errHandler func(error) bool) error {
|
||||
var n int
|
||||
for {
|
||||
m, merr := NewMapXmlReader(xmlReader)
|
||||
n++
|
||||
|
||||
// handle error condition with errhandler
|
||||
if merr != nil && merr != io.EOF {
|
||||
merr = fmt.Errorf("[xmlReader: %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(xhandlerPollInterval)
|
||||
}
|
||||
|
||||
if merr == io.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bulk process XML using handlers that process a Map value and the raw XML.
|
||||
// 'rdr' is an io.Reader for XML (stream)
|
||||
// 'mapHandler' is the Map and raw XML - []byte - processor. Return of 'false' stops io.Reader processing.
|
||||
// 'errHandler' is the error and raw XML 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'.
|
||||
// See NewMapXmlReaderRaw for comment on performance associated with retrieving raw XML from a Reader.
|
||||
func HandleXmlReaderRaw(xmlReader io.Reader, mapHandler func(Map, []byte) bool, errHandler func(error, []byte) bool) error {
|
||||
var n int
|
||||
for {
|
||||
m, raw, merr := NewMapXmlReaderRaw(xmlReader)
|
||||
n++
|
||||
|
||||
// handle error condition with errhandler
|
||||
if merr != nil && merr != io.EOF {
|
||||
merr = fmt.Errorf("[xmlReader: %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(xhandlerPollInterval)
|
||||
}
|
||||
|
||||
if merr == io.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ----------------- END: Handle XML stream by processing Map value --------------
|
||||
|
||||
// -------- a hack of io.TeeReader ... need one that's an io.ByteReader for xml.NewDecoder() ----------
|
||||
|
||||
// This is a clone of io.TeeReader with the additional method t.ReadByte().
|
||||
// Thus, this TeeReader is also an io.ByteReader.
|
||||
// This is necessary because xml.NewDecoder uses a ByteReader not a Reader. It appears to have been written
|
||||
// with bufio.Reader or bytes.Reader in mind ... not a generic io.Reader, which doesn't have to have ReadByte()..
|
||||
// If NewDecoder is passed a Reader that does not satisfy ByteReader() it wraps the Reader with
|
||||
// bufio.NewReader and uses ReadByte rather than Read that runs the TeeReader pipe logic.
|
||||
|
||||
type teeReader struct {
|
||||
r io.Reader
|
||||
w io.Writer
|
||||
b []byte
|
||||
}
|
||||
|
||||
func myTeeReader(r io.Reader, w io.Writer) io.Reader {
|
||||
b := make([]byte, 1)
|
||||
return &teeReader{r, w, b}
|
||||
}
|
||||
|
||||
// need for io.Reader - but we don't use it ...
|
||||
func (t *teeReader) Read(p []byte) (n int, err error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (t *teeReader) ReadByte() (c byte, err error) {
|
||||
n, err := t.r.Read(t.b)
|
||||
if n > 0 {
|
||||
if _, err := t.w.Write(t.b[:1]); err != nil {
|
||||
return t.b[0], err
|
||||
}
|
||||
}
|
||||
return t.b[0], err
|
||||
}
|
||||
|
||||
// ----------------------- END: io.TeeReader hack -----------------------------------
|
||||
|
||||
// ---------------------- XmlIndent - from j2x package ----------------------------
|
||||
|
||||
// Encode a map[string]interface{} as a pretty XML string.
|
||||
// See Xml for encoding rules.
|
||||
func (mv Map) XmlIndent(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 = mapToXmlIndent(true, s, DefaultRootTag, m, p)
|
||||
} else {
|
||||
err = mapToXmlIndent(true, s, key, value, p)
|
||||
}
|
||||
}
|
||||
} else if len(rootTag) == 1 {
|
||||
err = mapToXmlIndent(true, s, rootTag[0], m, p)
|
||||
} else {
|
||||
err = mapToXmlIndent(true, s, DefaultRootTag, m, p)
|
||||
}
|
||||
return []byte(*s), err
|
||||
}
|
||||
|
||||
type pretty struct {
|
||||
indent string
|
||||
cnt int
|
||||
padding string
|
||||
mapDepth int
|
||||
start int
|
||||
}
|
||||
|
||||
func (p *pretty) Indent() {
|
||||
p.padding += p.indent
|
||||
p.cnt++
|
||||
}
|
||||
|
||||
func (p *pretty) Outdent() {
|
||||
if p.cnt > 0 {
|
||||
p.padding = p.padding[:len(p.padding)-len(p.indent)]
|
||||
p.cnt--
|
||||
}
|
||||
}
|
||||
|
||||
// where the work actually happens
|
||||
// returns an error if an attribute is not atomic
|
||||
func mapToXmlIndent(doIndent bool, s *string, key string, value interface{}, pp *pretty) error {
|
||||
var endTag bool
|
||||
var isSimple bool
|
||||
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
|
||||
}
|
||||
*s += `<` + key
|
||||
}
|
||||
switch value.(type) {
|
||||
case map[string]interface{}:
|
||||
vv := value.(map[string]interface{})
|
||||
lenvv := len(vv)
|
||||
// scan out attributes - keys have prepended hyphen, '-'
|
||||
var cntAttr int
|
||||
for k, v := range vv {
|
||||
if k[:1] == "-" {
|
||||
switch v.(type) {
|
||||
case string, float64, bool, int, int32, int64, float32:
|
||||
*s += ` ` + k[1:] + `="` + fmt.Sprintf("%v", v) + `"`
|
||||
cntAttr++
|
||||
case []byte: // allow standard xml pkg []byte transform, as below
|
||||
*s += ` ` + k[1:] + `="` + fmt.Sprintf("%v", string(v.([]byte))) + `"`
|
||||
cntAttr++
|
||||
default:
|
||||
return fmt.Errorf("invalid attribute value for: %s", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
// only attributes?
|
||||
if cntAttr == lenvv {
|
||||
break
|
||||
}
|
||||
// simple element? Note: '#text" is an invalid XML tag.
|
||||
if v, ok := vv["#text"]; ok {
|
||||
if cntAttr+1 < lenvv {
|
||||
return errors.New("#text key occurs with other non-attribute keys")
|
||||
}
|
||||
*s += ">" + fmt.Sprintf("%v", v)
|
||||
endTag = true
|
||||
break
|
||||
}
|
||||
// close tag with possible attributes
|
||||
*s += ">"
|
||||
if doIndent {
|
||||
*s += "\n"
|
||||
}
|
||||
// something more complex
|
||||
p.mapDepth++
|
||||
var i int
|
||||
for k, v := range vv {
|
||||
if k[:1] == "-" {
|
||||
continue
|
||||
}
|
||||
switch v.(type) {
|
||||
case []interface{}:
|
||||
default:
|
||||
if i == 0 && doIndent {
|
||||
p.Indent()
|
||||
}
|
||||
}
|
||||
i++
|
||||
mapToXmlIndent(doIndent, s, k, v, p)
|
||||
switch v.(type) {
|
||||
case []interface{}: // handled in []interface{} case
|
||||
default:
|
||||
if doIndent {
|
||||
p.Outdent()
|
||||
}
|
||||
}
|
||||
i--
|
||||
}
|
||||
p.mapDepth--
|
||||
endTag = true
|
||||
case []interface{}:
|
||||
for _, v := range value.([]interface{}) {
|
||||
if doIndent {
|
||||
p.Indent()
|
||||
}
|
||||
mapToXmlIndent(doIndent, s, key, v, p)
|
||||
if doIndent {
|
||||
p.Outdent()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case nil:
|
||||
// terminate the tag
|
||||
*s += "<" + key
|
||||
break
|
||||
default: // handle anything - even goofy stuff
|
||||
switch value.(type) {
|
||||
case string, float64, bool, int, int32, int64, float32:
|
||||
*s += ">" + fmt.Sprintf("%v", value)
|
||||
case []byte: // NOTE: byte is just an alias for uint8
|
||||
// similar to how xml.Marshal handles []byte structure members
|
||||
*s += ">" + string(value.([]byte))
|
||||
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 {
|
||||
*s += string(v)
|
||||
}
|
||||
}
|
||||
isSimple = true
|
||||
endTag = true
|
||||
}
|
||||
|
||||
if endTag {
|
||||
if doIndent {
|
||||
if !isSimple {
|
||||
// if p.mapDepth == 0 {
|
||||
// p.Outdent()
|
||||
// }
|
||||
*s += p.padding
|
||||
}
|
||||
}
|
||||
switch value.(type) {
|
||||
case map[string]interface{}, []byte, string, float64, bool, int, int32, int64, float32:
|
||||
*s += `</` + key + ">"
|
||||
}
|
||||
} else if useGoXmlEmptyElemSyntax {
|
||||
*s += "></" + key + ">"
|
||||
} else {
|
||||
*s += "/>"
|
||||
}
|
||||
if doIndent {
|
||||
if p.cnt > p.start {
|
||||
*s += "\n"
|
||||
}
|
||||
p.Outdent()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
The mxj package terminates empty elements using '/>' rather than '<tag ...></tag>'.
|
||||
|
||||
The Xml(), XmlIndent(), XmlWriter() marshals Map values that can have structures or any other Go type
|
||||
that xml.Marshal() can encode. If you want to have xml.Marshal() encode empty elements in a manner
|
||||
consist with maputil, then you need to hack the src/pkg/encoding/xml/marshal.go file to support that
|
||||
convention.
|
||||
|
||||
The marshal.go.v1_2 file in this repo does that for Go v1.2; use it in place of the standard library
|
||||
marshal.go file.
|
||||
|
||||
The example_test.go.v1_2 file extends the package example_test.go file in the src/pkg/encoding/xml
|
||||
directory to provide an example of the SetUseNullEndTag() function that you'll see in the new godoc
|
||||
documentation for encoding/xml. xml.SetUseNullEndTag(true) causes xml.Marshal() to encode empty XML
|
||||
elements as <tag .../>, just like this package.
|
||||
|
||||
With the new marshal.go, you can then force all marshaling that uses encoding/xml to use mxj's <tag .../>
|
||||
syntax rather than the Go standard <tag ...></tag> syntax.
|
||||
|
||||
NOTE:
|
||||
If you install this patch then only use either
|
||||
|
||||
- xml.SetUseNullEndTag()
|
||||
or
|
||||
- mxj.XmlGoEmptyElemSyntax()
|
||||
|
||||
to have consistent encoding of empty XML elements.
|
211
Godeps/_workspace/src/github.com/clbanning/mxj/xml_marshal/example_test.go.v1_2
generated
vendored
211
Godeps/_workspace/src/github.com/clbanning/mxj/xml_marshal/example_test.go.v1_2
generated
vendored
|
@ -1,211 +0,0 @@
|
|||
// Copyright 2012 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.
|
||||
|
||||
package xml_test
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func ExampleMarshalIndent() {
|
||||
type Address struct {
|
||||
City, State string
|
||||
}
|
||||
type Person struct {
|
||||
XMLName xml.Name `xml:"person"`
|
||||
Id int `xml:"id,attr"`
|
||||
FirstName string `xml:"name>first"`
|
||||
LastName string `xml:"name>last"`
|
||||
Age int `xml:"age"`
|
||||
Height float32 `xml:"height,omitempty"`
|
||||
Married bool
|
||||
Address
|
||||
Comment string `xml:",comment"`
|
||||
}
|
||||
|
||||
v := &Person{Id: 13, FirstName: "John", LastName: "Doe", Age: 42}
|
||||
v.Comment = " Need more details. "
|
||||
v.Address = Address{"Hanga Roa", "Easter Island"}
|
||||
|
||||
output, err := xml.MarshalIndent(v, " ", " ")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
}
|
||||
|
||||
os.Stdout.Write(output)
|
||||
// Output:
|
||||
// <person id="13">
|
||||
// <name>
|
||||
// <first>John</first>
|
||||
// <last>Doe</last>
|
||||
// </name>
|
||||
// <age>42</age>
|
||||
// <Married>false</Married>
|
||||
// <City>Hanga Roa</City>
|
||||
// <State>Easter Island</State>
|
||||
// <!-- Need more details. -->
|
||||
// </person>
|
||||
}
|
||||
|
||||
func ExampleEncoder() {
|
||||
type Address struct {
|
||||
City, State string
|
||||
}
|
||||
type Person struct {
|
||||
XMLName xml.Name `xml:"person"`
|
||||
Id int `xml:"id,attr"`
|
||||
FirstName string `xml:"name>first"`
|
||||
LastName string `xml:"name>last"`
|
||||
Age int `xml:"age"`
|
||||
Height float32 `xml:"height,omitempty"`
|
||||
Married bool
|
||||
Address
|
||||
Comment string `xml:",comment"`
|
||||
}
|
||||
|
||||
v := &Person{Id: 13, FirstName: "John", LastName: "Doe", Age: 42}
|
||||
v.Comment = " Need more details. "
|
||||
v.Address = Address{"Hanga Roa", "Easter Island"}
|
||||
|
||||
enc := xml.NewEncoder(os.Stdout)
|
||||
enc.Indent(" ", " ")
|
||||
if err := enc.Encode(v); err != nil {
|
||||
fmt.Printf("error: %v\n", err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// <person id="13">
|
||||
// <name>
|
||||
// <first>John</first>
|
||||
// <last>Doe</last>
|
||||
// </name>
|
||||
// <age>42</age>
|
||||
// <Married>false</Married>
|
||||
// <City>Hanga Roa</City>
|
||||
// <State>Easter Island</State>
|
||||
// <!-- Need more details. -->
|
||||
// </person>
|
||||
}
|
||||
|
||||
// This example demonstrates unmarshaling an XML excerpt into a value with
|
||||
// some preset fields. Note that the Phone field isn't modified and that
|
||||
// the XML <Company> element is ignored. Also, the Groups field is assigned
|
||||
// considering the element path provided in its tag.
|
||||
func ExampleUnmarshal() {
|
||||
type Email struct {
|
||||
Where string `xml:"where,attr"`
|
||||
Addr string
|
||||
}
|
||||
type Address struct {
|
||||
City, State string
|
||||
}
|
||||
type Result struct {
|
||||
XMLName xml.Name `xml:"Person"`
|
||||
Name string `xml:"FullName"`
|
||||
Phone string
|
||||
Email []Email
|
||||
Groups []string `xml:"Group>Value"`
|
||||
Address
|
||||
}
|
||||
v := Result{Name: "none", Phone: "none"}
|
||||
|
||||
data := `
|
||||
<Person>
|
||||
<FullName>Grace R. Emlin</FullName>
|
||||
<Company>Example Inc.</Company>
|
||||
<Email where="home">
|
||||
<Addr>gre@example.com</Addr>
|
||||
</Email>
|
||||
<Email where='work'>
|
||||
<Addr>gre@work.com</Addr>
|
||||
</Email>
|
||||
<Group>
|
||||
<Value>Friends</Value>
|
||||
<Value>Squash</Value>
|
||||
</Group>
|
||||
<City>Hanga Roa</City>
|
||||
<State>Easter Island</State>
|
||||
</Person>
|
||||
`
|
||||
err := xml.Unmarshal([]byte(data), &v)
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("XMLName: %#v\n", v.XMLName)
|
||||
fmt.Printf("Name: %q\n", v.Name)
|
||||
fmt.Printf("Phone: %q\n", v.Phone)
|
||||
fmt.Printf("Email: %v\n", v.Email)
|
||||
fmt.Printf("Groups: %v\n", v.Groups)
|
||||
fmt.Printf("Address: %v\n", v.Address)
|
||||
// Output:
|
||||
// XMLName: xml.Name{Space:"", Local:"Person"}
|
||||
// Name: "Grace R. Emlin"
|
||||
// Phone: "none"
|
||||
// Email: [{home gre@example.com} {work gre@work.com}]
|
||||
// Groups: [Friends Squash]
|
||||
// Address: {Hanga Roa Easter Island}
|
||||
}
|
||||
|
||||
// This example demonstrates how SetUseNullEndTag changes the end tag syntax for Marshal.
|
||||
func ExampleSetUseNullEndTag() {
|
||||
type NullValStruct struct {
|
||||
I int
|
||||
B []byte
|
||||
S string
|
||||
}
|
||||
s := new(NullValStruct)
|
||||
|
||||
v, err := xml.Marshal(s)
|
||||
if err != nil {
|
||||
fmt.Println("err:", err.Error())
|
||||
} else {
|
||||
fmt.Println("s:", string(v))
|
||||
}
|
||||
|
||||
xml.SetUseNullEndTag(true)
|
||||
v, err = xml.Marshal(s)
|
||||
if err != nil {
|
||||
fmt.Println("err:", err.Error())
|
||||
} else {
|
||||
fmt.Println("s:", string(v))
|
||||
}
|
||||
|
||||
type NewStruct struct {
|
||||
NVS NullValStruct
|
||||
S string
|
||||
F float64
|
||||
}
|
||||
ss := new(NewStruct)
|
||||
|
||||
v, err = xml.Marshal(ss)
|
||||
if err != nil {
|
||||
fmt.Println("err:", err.Error())
|
||||
} else {
|
||||
fmt.Println("ss:", string(v))
|
||||
}
|
||||
|
||||
v, err = xml.MarshalIndent(ss," "," ")
|
||||
if err != nil {
|
||||
fmt.Println("err:", err.Error())
|
||||
} else {
|
||||
fmt.Println("ss indent:\n",string(v))
|
||||
}
|
||||
// Output:
|
||||
// s: <NullValStruct><I>0</I><B></B><S></S></NullValStruct>
|
||||
// s: <NullValStruct><I>0</I><B/><S/></NullValStruct>
|
||||
// ss: <NewStruct><NVS><I>0</I><B/><S/></NVS><S/><F>0</F></NewStruct>
|
||||
// ss indent:
|
||||
// <NewStruct>
|
||||
// <NVS>
|
||||
// <I>0</I>
|
||||
// <B/>
|
||||
// <S/>
|
||||
// </NVS>
|
||||
// <S/>
|
||||
// <F>0</F>
|
||||
// </NewStruct>
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,207 +0,0 @@
|
|||
package mxj
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestXmlHeader(t *testing.T) {
|
||||
fmt.Println("\n---------------- xml_test.go ...\n")
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
fmt.Println("NewMapXml, x :", string(x))
|
||||
fmt.Println("NewMapXml, mv:", mv)
|
||||
}
|
||||
|
||||
func TestAttrHyphenFalse(t *testing.T) {
|
||||
PrependAttrWithHyphen(false)
|
||||
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())
|
||||
}
|
||||
|
||||
fmt.Println("AttrHyphenFalse, x :", string(x))
|
||||
fmt.Println("AttrHyphenFalse, mv:", mv)
|
||||
PrependAttrWithHyphen(true)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fmt.Println("NewMapXmlError, x :", string(x))
|
||||
fmt.Println("NewMapXmlError, merr:", merr.Error())
|
||||
|
||||
x = []byte(`<root2><newtag>something more</newtag><list><item>1<item>2</item></list></root2>`)
|
||||
m, merr = NewMapJson(x)
|
||||
if merr == nil {
|
||||
t.Fatal("NewMapXmlError, m:", m)
|
||||
}
|
||||
|
||||
fmt.Println("NewMapXmlError, x :", string(x))
|
||||
fmt.Println("NewMapXmlError, merr:", merr.Error())
|
||||
}
|
||||
|
||||
func TestNewMapXmlReader(t *testing.T) {
|
||||
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 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
|
||||
}
|
||||
*/
|
|
@ -1,23 +0,0 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
|
@ -1,11 +0,0 @@
|
|||
language: go
|
||||
go: 1.3
|
||||
before_install:
|
||||
- go get github.com/axw/gocov/gocov
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get code.google.com/p/go.tools/cmd/cover
|
||||
script:
|
||||
- $HOME/gopath/bin/goveralls -repotoken $COVERALLS_TOKEN
|
||||
env:
|
||||
global:
|
||||
- secure: hkc+92KPmMFqIH9n4yWdnH1JpZjahmOyDJwpTh8Yl0JieJNG0XEXpOqNao27eA0cLF+UHdyjFeGcPUJKNmgE46AoQjtovt+ICjCXKR2yF6S2kKJcUOz/Vd6boZF7qHV06jjxyxOebpID5iSoW6UfFr001bFxpd3jaSLFTzSHWRQ=
|
|
@ -1,21 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Fatih Arslan
|
||||
|
||||
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.
|
|
@ -1,159 +0,0 @@
|
|||
# Structs [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/fatih/structs) [![Build Status](http://img.shields.io/travis/fatih/structs.svg?style=flat-square)](https://travis-ci.org/fatih/structs) [![Coverage Status](http://img.shields.io/coveralls/fatih/structs.svg?style=flat-square)](https://coveralls.io/r/fatih/structs)
|
||||
|
||||
Structs contains various utilities to work with Go (Golang) structs. It was
|
||||
initially used by me to convert a struct into a `map[string]interface{}`. With
|
||||
time I've added other utilities for structs. It's basically a high level
|
||||
package based on primitives from the reflect package. Feel free to add new
|
||||
functions or improve the existing code.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get github.com/fatih/structs
|
||||
```
|
||||
|
||||
## Usage and Examples
|
||||
|
||||
Just like the standard lib `strings`, `bytes` and co packages, `structs` has
|
||||
many global functions to manipulate or organize your struct data. Lets define
|
||||
and declare a struct:
|
||||
|
||||
```go
|
||||
type Server struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
ID int
|
||||
Enabled bool
|
||||
users []string // not exported
|
||||
http.Server // embedded
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
Name: "gopher",
|
||||
ID: 123456,
|
||||
Enabled: true,
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// Convert a struct to a map[string]interface{}
|
||||
// => {"Name":"gopher", "ID":123456, "Enabled":true}
|
||||
m := structs.Map(server)
|
||||
|
||||
// Convert the values of a struct to a []interface{}
|
||||
// => ["gopher", 123456, true]
|
||||
v := structs.Values(server)
|
||||
|
||||
// Convert the values of a struct to a []*Field
|
||||
// (see "Field methods" for more info about fields)
|
||||
f := structs.Fields(server)
|
||||
|
||||
// Return the struct name => "Server"
|
||||
n := structs.Name(server)
|
||||
|
||||
// Check if any field of a struct is initialized or not.
|
||||
h := structs.HasZero(server)
|
||||
|
||||
// Check if all fields of a struct is initialized or not.
|
||||
z := structs.IsZero(server)
|
||||
|
||||
// Check if server is a struct or a pointer to struct
|
||||
i := structs.IsStruct(server)
|
||||
```
|
||||
|
||||
### Struct methods
|
||||
|
||||
The structs functions can be also used as independent methods by creating a new
|
||||
`*structs.Struct`. This is handy if you want to have more control over the
|
||||
structs (such as retrieving a single Field).
|
||||
|
||||
```go
|
||||
// Create a new struct type:
|
||||
s := structs.New(server)
|
||||
|
||||
m := s.Map() // Get a map[string]interface{}
|
||||
v := s.Values() // Get a []interface{}
|
||||
f := s.Fields() // Get a []*Field
|
||||
f := s.Field(name) // Get a *Field based on the given field name
|
||||
f, ok := s.FieldsOk(name) // Get a *Field based on the given field name
|
||||
n := s.Name() // Get the struct name
|
||||
h := s.HasZero() // Check if any field is initialized
|
||||
z := s.IsZero() // Check if all fields are initialized
|
||||
```
|
||||
|
||||
### Field methods
|
||||
|
||||
We can easily examine a single Field for more detail. Below you can see how we
|
||||
get and interact with various field methods:
|
||||
|
||||
|
||||
```go
|
||||
s := structs.New(server)
|
||||
|
||||
// Get the Field struct for the "Name" field
|
||||
name := s.Field("Name")
|
||||
|
||||
// Get the underlying value, value => "gopher"
|
||||
value := name.Value().(string)
|
||||
|
||||
// Set the field's value
|
||||
name.Set("another gopher")
|
||||
|
||||
// Get the field's kind, kind => "string"
|
||||
name.Kind()
|
||||
|
||||
// Check if the field is exported or not
|
||||
if name.IsExported() {
|
||||
fmt.Println("Name field is exported")
|
||||
}
|
||||
|
||||
// Check if the value is a zero value, such as "" for string, 0 for int
|
||||
if !name.IsZero() {
|
||||
fmt.Println("Name is initialized")
|
||||
}
|
||||
|
||||
// Check if the field is an anonymous (embedded) field
|
||||
if !name.IsEmbedded() {
|
||||
fmt.Println("Name is not an embedded field")
|
||||
}
|
||||
|
||||
// Get the Field's tag value for tag name "json", tag value => "name,omitempty"
|
||||
tagValue := name.Tag("json")
|
||||
```
|
||||
|
||||
Nested structs are supported too:
|
||||
|
||||
```go
|
||||
addrField := s.Field("Server").Field("Addr")
|
||||
|
||||
// Get the value for addr
|
||||
a := addrField.Value().(string)
|
||||
|
||||
// Or get all fields
|
||||
httpServer := s.Field("Server").Fields()
|
||||
```
|
||||
|
||||
We can also get a slice of Fields from the Struct type to iterate over all
|
||||
fields. This is handy if you wish to examine all fields:
|
||||
|
||||
```go
|
||||
// Convert the fields of a struct to a []*Field
|
||||
fields := s.Fields()
|
||||
|
||||
for _, f := range fields {
|
||||
fmt.Printf("field name: %+v\n", f.Name())
|
||||
|
||||
if f.IsExported() {
|
||||
fmt.Printf("value : %+v\n", f.Value())
|
||||
fmt.Printf("is zero : %+v\n", f.IsZero())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Credits
|
||||
|
||||
* [Fatih Arslan](https://github.com/fatih)
|
||||
* [Cihangir Savas](https://github.com/cihangir)
|
||||
|
||||
## License
|
||||
|
||||
The MIT License (MIT) - see LICENSE.md for more details
|
|
@ -1,126 +0,0 @@
|
|||
package structs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var (
|
||||
errNotExported = errors.New("field is not exported")
|
||||
errNotSettable = errors.New("field is not settable")
|
||||
)
|
||||
|
||||
// Field represents a single struct field that encapsulates high level
|
||||
// functions around the field.
|
||||
type Field struct {
|
||||
value reflect.Value
|
||||
field reflect.StructField
|
||||
defaultTag string
|
||||
}
|
||||
|
||||
// Tag returns the value associated with key in the tag string. If there is no
|
||||
// such key in the tag, Tag returns the empty string.
|
||||
func (f *Field) Tag(key string) string {
|
||||
return f.field.Tag.Get(key)
|
||||
}
|
||||
|
||||
// Value returns the underlying value of of the field. It panics if the field
|
||||
// is not exported.
|
||||
func (f *Field) Value() interface{} {
|
||||
return f.value.Interface()
|
||||
}
|
||||
|
||||
// IsEmbedded returns true if the given field is an anonymous field (embedded)
|
||||
func (f *Field) IsEmbedded() bool {
|
||||
return f.field.Anonymous
|
||||
}
|
||||
|
||||
// IsExported returns true if the given field is exported.
|
||||
func (f *Field) IsExported() bool {
|
||||
return f.field.PkgPath == ""
|
||||
}
|
||||
|
||||
// IsZero returns true if the given field is not initalized (has a zero value).
|
||||
// It panics if the field is not exported.
|
||||
func (f *Field) IsZero() bool {
|
||||
zero := reflect.Zero(f.value.Type()).Interface()
|
||||
current := f.Value()
|
||||
|
||||
return reflect.DeepEqual(current, zero)
|
||||
}
|
||||
|
||||
// Name returns the name of the given field
|
||||
func (f *Field) Name() string {
|
||||
return f.field.Name
|
||||
}
|
||||
|
||||
// Kind returns the fields kind, such as "string", "map", "bool", etc ..
|
||||
func (f *Field) Kind() reflect.Kind {
|
||||
return f.value.Kind()
|
||||
}
|
||||
|
||||
// Set sets the field to given value v. It retuns an error if the field is not
|
||||
// settable (not addresable or not exported) or if the given value's type
|
||||
// doesn't match the fields type.
|
||||
func (f *Field) Set(val interface{}) error {
|
||||
// we can't set unexported fields, so be sure this field is exported
|
||||
if !f.IsExported() {
|
||||
return errNotExported
|
||||
}
|
||||
|
||||
// do we get here? not sure...
|
||||
if !f.value.CanSet() {
|
||||
return errNotSettable
|
||||
}
|
||||
|
||||
given := reflect.ValueOf(val)
|
||||
|
||||
if f.value.Kind() != given.Kind() {
|
||||
return fmt.Errorf("wrong kind. got: %s want: %s", given.Kind(), f.value.Kind())
|
||||
}
|
||||
|
||||
f.value.Set(given)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fields returns a slice of Fields. This is particular handy to get the fields
|
||||
// of a nested struct . A struct tag with the content of "-" ignores the
|
||||
// checking of that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field *http.Request `structs:"-"`
|
||||
//
|
||||
// It panics if field is not exported or if field's kind is not struct
|
||||
func (f *Field) Fields() []*Field {
|
||||
return getFields(f.value, f.defaultTag)
|
||||
}
|
||||
|
||||
// Field returns the field from a nested struct. It panics if the nested struct
|
||||
// is not exported or if the field was not found.
|
||||
func (f *Field) Field(name string) *Field {
|
||||
field, ok := f.FieldOk(name)
|
||||
if !ok {
|
||||
panic("field not found")
|
||||
}
|
||||
|
||||
return field
|
||||
}
|
||||
|
||||
// Field returns the field from a nested struct. The boolean returns true if
|
||||
// the field was found. It panics if the nested struct is not exported or if
|
||||
// the field was not found.
|
||||
func (f *Field) FieldOk(name string) (*Field, bool) {
|
||||
v := strctVal(f.value.Interface())
|
||||
t := v.Type()
|
||||
|
||||
field, ok := t.FieldByName(name)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &Field{
|
||||
field: field,
|
||||
value: v.FieldByName(name),
|
||||
}, true
|
||||
}
|
|
@ -1,324 +0,0 @@
|
|||
package structs
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// A test struct that defines all cases
|
||||
type Foo struct {
|
||||
A string
|
||||
B int `structs:"y"`
|
||||
C bool `json:"c"`
|
||||
d string // not exported
|
||||
E *Baz
|
||||
x string `xml:"x"` // not exported, with tag
|
||||
Y []string
|
||||
Z map[string]interface{}
|
||||
*Bar // embedded
|
||||
}
|
||||
|
||||
type Baz struct {
|
||||
A string
|
||||
B int
|
||||
}
|
||||
|
||||
type Bar struct {
|
||||
E string
|
||||
F int
|
||||
g []string
|
||||
}
|
||||
|
||||
func newStruct() *Struct {
|
||||
b := &Bar{
|
||||
E: "example",
|
||||
F: 2,
|
||||
g: []string{"zeynep", "fatih"},
|
||||
}
|
||||
|
||||
// B and x is not initialized for testing
|
||||
f := &Foo{
|
||||
A: "gopher",
|
||||
C: true,
|
||||
d: "small",
|
||||
E: nil,
|
||||
Y: []string{"example"},
|
||||
Z: nil,
|
||||
}
|
||||
f.Bar = b
|
||||
|
||||
return New(f)
|
||||
}
|
||||
|
||||
func TestField_Set(t *testing.T) {
|
||||
s := newStruct()
|
||||
|
||||
f := s.Field("A")
|
||||
err := f.Set("fatih")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if f.Value().(string) != "fatih" {
|
||||
t.Errorf("Setted value is wrong: %s want: %s", f.Value().(string), "fatih")
|
||||
}
|
||||
|
||||
f = s.Field("Y")
|
||||
err = f.Set([]string{"override", "with", "this"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
sliceLen := len(f.Value().([]string))
|
||||
if sliceLen != 3 {
|
||||
t.Errorf("Setted values slice length is wrong: %d, want: %d", sliceLen, 3)
|
||||
}
|
||||
|
||||
f = s.Field("C")
|
||||
err = f.Set(false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if f.Value().(bool) {
|
||||
t.Errorf("Setted value is wrong: %s want: %s", f.Value().(bool), false)
|
||||
}
|
||||
|
||||
// let's pass a different type
|
||||
f = s.Field("A")
|
||||
err = f.Set(123) // Field A is of type string, but we are going to pass an integer
|
||||
if err == nil {
|
||||
t.Error("Setting a field's value with a different type than the field's type should return an error")
|
||||
}
|
||||
|
||||
// old value should be still there :)
|
||||
if f.Value().(string) != "fatih" {
|
||||
t.Errorf("Setted value is wrong: %s want: %s", f.Value().(string), "fatih")
|
||||
}
|
||||
|
||||
// let's access an unexported field, which should give an error
|
||||
f = s.Field("d")
|
||||
err = f.Set("large")
|
||||
if err != errNotExported {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// let's set a pointer to struct
|
||||
b := &Bar{
|
||||
E: "gopher",
|
||||
F: 2,
|
||||
}
|
||||
|
||||
f = s.Field("Bar")
|
||||
err = f.Set(b)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
baz := &Baz{
|
||||
A: "helloWorld",
|
||||
B: 42,
|
||||
}
|
||||
|
||||
f = s.Field("E")
|
||||
err = f.Set(baz)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
ba := s.Field("E").Value().(*Baz)
|
||||
|
||||
if ba.A != "helloWorld" {
|
||||
t.Errorf("could not set baz. Got: %s Want: helloWorld", ba.A)
|
||||
}
|
||||
}
|
||||
|
||||
func TestField(t *testing.T) {
|
||||
s := newStruct()
|
||||
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err == nil {
|
||||
t.Error("Retrieveing a non existing field from the struct should panic")
|
||||
}
|
||||
}()
|
||||
|
||||
_ = s.Field("no-field")
|
||||
}
|
||||
|
||||
func TestField_Kind(t *testing.T) {
|
||||
s := newStruct()
|
||||
|
||||
f := s.Field("A")
|
||||
if f.Kind() != reflect.String {
|
||||
t.Errorf("Field A has wrong kind: %s want: %s", f.Kind(), reflect.String)
|
||||
}
|
||||
|
||||
f = s.Field("B")
|
||||
if f.Kind() != reflect.Int {
|
||||
t.Errorf("Field B has wrong kind: %s want: %s", f.Kind(), reflect.Int)
|
||||
}
|
||||
|
||||
// unexported
|
||||
f = s.Field("d")
|
||||
if f.Kind() != reflect.String {
|
||||
t.Errorf("Field d has wrong kind: %s want: %s", f.Kind(), reflect.String)
|
||||
}
|
||||
}
|
||||
|
||||
func TestField_Tag(t *testing.T) {
|
||||
s := newStruct()
|
||||
|
||||
v := s.Field("B").Tag("json")
|
||||
if v != "" {
|
||||
t.Errorf("Field's tag value of a non existing tag should return empty, got: %s", v)
|
||||
}
|
||||
|
||||
v = s.Field("C").Tag("json")
|
||||
if v != "c" {
|
||||
t.Errorf("Field's tag value of the existing field C should return 'c', got: %s", v)
|
||||
}
|
||||
|
||||
v = s.Field("d").Tag("json")
|
||||
if v != "" {
|
||||
t.Errorf("Field's tag value of a non exported field should return empty, got: %s", v)
|
||||
}
|
||||
|
||||
v = s.Field("x").Tag("xml")
|
||||
if v != "x" {
|
||||
t.Errorf("Field's tag value of a non exported field with a tag should return 'x', got: %s", v)
|
||||
}
|
||||
|
||||
v = s.Field("A").Tag("json")
|
||||
if v != "" {
|
||||
t.Errorf("Field's tag value of a existing field without a tag should return empty, got: %s", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestField_Value(t *testing.T) {
|
||||
s := newStruct()
|
||||
|
||||
v := s.Field("A").Value()
|
||||
val, ok := v.(string)
|
||||
if !ok {
|
||||
t.Errorf("Field's value of a A should be string")
|
||||
}
|
||||
|
||||
if val != "gopher" {
|
||||
t.Errorf("Field's value of a existing tag should return 'gopher', got: %s", val)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err == nil {
|
||||
t.Error("Value of a non exported field from the field should panic")
|
||||
}
|
||||
}()
|
||||
|
||||
// should panic
|
||||
_ = s.Field("d").Value()
|
||||
}
|
||||
|
||||
func TestField_IsEmbedded(t *testing.T) {
|
||||
s := newStruct()
|
||||
|
||||
if !s.Field("Bar").IsEmbedded() {
|
||||
t.Errorf("Fields 'Bar' field is an embedded field")
|
||||
}
|
||||
|
||||
if s.Field("d").IsEmbedded() {
|
||||
t.Errorf("Fields 'd' field is not an embedded field")
|
||||
}
|
||||
}
|
||||
|
||||
func TestField_IsExported(t *testing.T) {
|
||||
s := newStruct()
|
||||
|
||||
if !s.Field("Bar").IsExported() {
|
||||
t.Errorf("Fields 'Bar' field is an exported field")
|
||||
}
|
||||
|
||||
if !s.Field("A").IsExported() {
|
||||
t.Errorf("Fields 'A' field is an exported field")
|
||||
}
|
||||
|
||||
if s.Field("d").IsExported() {
|
||||
t.Errorf("Fields 'd' field is not an exported field")
|
||||
}
|
||||
}
|
||||
|
||||
func TestField_IsZero(t *testing.T) {
|
||||
s := newStruct()
|
||||
|
||||
if s.Field("A").IsZero() {
|
||||
t.Errorf("Fields 'A' field is an initialized field")
|
||||
}
|
||||
|
||||
if !s.Field("B").IsZero() {
|
||||
t.Errorf("Fields 'B' field is not an initialized field")
|
||||
}
|
||||
}
|
||||
|
||||
func TestField_Name(t *testing.T) {
|
||||
s := newStruct()
|
||||
|
||||
if s.Field("A").Name() != "A" {
|
||||
t.Errorf("Fields 'A' field should have the name 'A'")
|
||||
}
|
||||
}
|
||||
|
||||
func TestField_Field(t *testing.T) {
|
||||
s := newStruct()
|
||||
|
||||
e := s.Field("Bar").Field("E")
|
||||
|
||||
val, ok := e.Value().(string)
|
||||
if !ok {
|
||||
t.Error("The value of the field 'e' inside 'Bar' struct should be string")
|
||||
}
|
||||
|
||||
if val != "example" {
|
||||
t.Errorf("The value of 'e' should be 'example, got: %s", val)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err == nil {
|
||||
t.Error("Field of a non existing nested struct should panic")
|
||||
}
|
||||
}()
|
||||
|
||||
_ = s.Field("Bar").Field("e")
|
||||
}
|
||||
|
||||
func TestField_Fields(t *testing.T) {
|
||||
s := newStruct()
|
||||
fields := s.Field("Bar").Fields()
|
||||
|
||||
if len(fields) != 3 {
|
||||
t.Errorf("We expect 3 fields in embedded struct, was: %d", len(fields))
|
||||
}
|
||||
}
|
||||
|
||||
func TestField_FieldOk(t *testing.T) {
|
||||
s := newStruct()
|
||||
|
||||
b, ok := s.FieldOk("Bar")
|
||||
if !ok {
|
||||
t.Error("The field 'Bar' should exists.")
|
||||
}
|
||||
|
||||
e, ok := b.FieldOk("E")
|
||||
if !ok {
|
||||
t.Error("The field 'E' should exists.")
|
||||
}
|
||||
|
||||
val, ok := e.Value().(string)
|
||||
if !ok {
|
||||
t.Error("The value of the field 'e' inside 'Bar' struct should be string")
|
||||
}
|
||||
|
||||
if val != "example" {
|
||||
t.Errorf("The value of 'e' should be 'example, got: %s", val)
|
||||
}
|
||||
}
|
|
@ -1,422 +0,0 @@
|
|||
// Package structs contains various utilities functions to work with structs.
|
||||
package structs
|
||||
|
||||
import "reflect"
|
||||
|
||||
var (
|
||||
// DefaultTagName is the default tag name for struct fields which provides
|
||||
// a more granular to tweak certain structs. Lookup the necessary functions
|
||||
// for more info.
|
||||
DefaultTagName = "structs" // struct's field default tag name
|
||||
)
|
||||
|
||||
// Struct encapsulates a struct type to provide several high level functions
|
||||
// around the struct.
|
||||
type Struct struct {
|
||||
raw interface{}
|
||||
value reflect.Value
|
||||
TagName string
|
||||
}
|
||||
|
||||
// New returns a new *Struct with the struct s. It panics if the s's kind is
|
||||
// not struct.
|
||||
func New(s interface{}) *Struct {
|
||||
return &Struct{
|
||||
raw: s,
|
||||
value: strctVal(s),
|
||||
TagName: DefaultTagName,
|
||||
}
|
||||
}
|
||||
|
||||
// Map converts the given struct to a map[string]interface{}, where the keys
|
||||
// of the map are the field names and the values of the map the associated
|
||||
// values of the fields. The default key string is the struct field name but
|
||||
// can be changed in the struct field's tag value. The "structs" key in the
|
||||
// struct's field tag value is the key name. Example:
|
||||
//
|
||||
// // Field appears in map as key "myName".
|
||||
// Name string `structs:"myName"`
|
||||
//
|
||||
// A tag value with the content of "-" ignores that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// A tag value with the option of "omitnested" stops iterating further if the type
|
||||
// is a struct. Example:
|
||||
//
|
||||
// // Field is not processed further by this package.
|
||||
// Field time.Time `structs:"myName,omitnested"`
|
||||
// Field *http.Request `structs:",omitnested"`
|
||||
//
|
||||
// A tag value with the option of "omitempty" ignores that particular field if
|
||||
// the field value is empty. Example:
|
||||
//
|
||||
// // Field appears in map as key "myName", but the field is
|
||||
// // skipped if empty.
|
||||
// Field string `structs:"myName,omitempty"`
|
||||
//
|
||||
// // Field appears in map as key "Field" (the default), but
|
||||
// // the field is skipped if empty.
|
||||
// Field string `structs:",omitempty"`
|
||||
//
|
||||
// Note that only exported fields of a struct can be accessed, non exported
|
||||
// fields will be neglected.
|
||||
func (s *Struct) Map() map[string]interface{} {
|
||||
out := make(map[string]interface{})
|
||||
|
||||
fields := s.structFields()
|
||||
|
||||
for _, field := range fields {
|
||||
name := field.Name
|
||||
val := s.value.FieldByName(name)
|
||||
|
||||
var finalVal interface{}
|
||||
|
||||
tagName, tagOpts := parseTag(field.Tag.Get(s.TagName))
|
||||
if tagName != "" {
|
||||
name = tagName
|
||||
}
|
||||
|
||||
// if the value is a zero value and the field is marked as omitempty do
|
||||
// not include
|
||||
if tagOpts.Has("omitempty") {
|
||||
zero := reflect.Zero(val.Type()).Interface()
|
||||
current := val.Interface()
|
||||
|
||||
if reflect.DeepEqual(current, zero) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||
// look out for embedded structs, and convert them to a
|
||||
// map[string]interface{} too
|
||||
finalVal = Map(val.Interface())
|
||||
} else {
|
||||
finalVal = val.Interface()
|
||||
}
|
||||
|
||||
out[name] = finalVal
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// Values converts the given s struct's field values to a []interface{}. A
|
||||
// struct tag with the content of "-" ignores the that particular field.
|
||||
// Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field int `structs:"-"`
|
||||
//
|
||||
// A value with the option of "omitnested" stops iterating further if the type
|
||||
// is a struct. Example:
|
||||
//
|
||||
// // Fields is not processed further by this package.
|
||||
// Field time.Time `structs:",omitnested"`
|
||||
// Field *http.Request `structs:",omitnested"`
|
||||
//
|
||||
// A tag value with the option of "omitempty" ignores that particular field and
|
||||
// is not added to the values if the field value is empty. Example:
|
||||
//
|
||||
// // Field is skipped if empty
|
||||
// Field string `structs:",omitempty"`
|
||||
//
|
||||
// Note that only exported fields of a struct can be accessed, non exported
|
||||
// fields will be neglected.
|
||||
func (s *Struct) Values() []interface{} {
|
||||
fields := s.structFields()
|
||||
|
||||
var t []interface{}
|
||||
|
||||
for _, field := range fields {
|
||||
val := s.value.FieldByName(field.Name)
|
||||
|
||||
_, tagOpts := parseTag(field.Tag.Get(s.TagName))
|
||||
|
||||
// if the value is a zero value and the field is marked as omitempty do
|
||||
// not include
|
||||
if tagOpts.Has("omitempty") {
|
||||
zero := reflect.Zero(val.Type()).Interface()
|
||||
current := val.Interface()
|
||||
|
||||
if reflect.DeepEqual(current, zero) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||
// look out for embedded structs, and convert them to a
|
||||
// []interface{} to be added to the final values slice
|
||||
for _, embeddedVal := range Values(val.Interface()) {
|
||||
t = append(t, embeddedVal)
|
||||
}
|
||||
} else {
|
||||
t = append(t, val.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// Fields returns a slice of Fields. A struct tag with the content of "-"
|
||||
// ignores the checking of that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// It panics if s's kind is not struct.
|
||||
func (s *Struct) Fields() []*Field {
|
||||
return getFields(s.value, s.TagName)
|
||||
}
|
||||
|
||||
func getFields(v reflect.Value, tagName string) []*Field {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
t := v.Type()
|
||||
|
||||
var fields []*Field
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
|
||||
if tag := field.Tag.Get(tagName); tag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
f := &Field{
|
||||
field: field,
|
||||
value: v.FieldByName(field.Name),
|
||||
}
|
||||
|
||||
fields = append(fields, f)
|
||||
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
// Field returns a new Field struct that provides several high level functions
|
||||
// around a single struct field entity. It panics if the field is not found.
|
||||
func (s *Struct) Field(name string) *Field {
|
||||
f, ok := s.FieldOk(name)
|
||||
if !ok {
|
||||
panic("field not found")
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// Field returns a new Field struct that provides several high level functions
|
||||
// around a single struct field entity. The boolean returns true if the field
|
||||
// was found.
|
||||
func (s *Struct) FieldOk(name string) (*Field, bool) {
|
||||
t := s.value.Type()
|
||||
|
||||
field, ok := t.FieldByName(name)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &Field{
|
||||
field: field,
|
||||
value: s.value.FieldByName(name),
|
||||
defaultTag: s.TagName,
|
||||
}, true
|
||||
}
|
||||
|
||||
// IsZero returns true if all fields in a struct is a zero value (not
|
||||
// initialized) A struct tag with the content of "-" ignores the checking of
|
||||
// that particular field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// A value with the option of "omitnested" stops iterating further if the type
|
||||
// is a struct. Example:
|
||||
//
|
||||
// // Field is not processed further by this package.
|
||||
// Field time.Time `structs:"myName,omitnested"`
|
||||
// Field *http.Request `structs:",omitnested"`
|
||||
//
|
||||
// Note that only exported fields of a struct can be accessed, non exported
|
||||
// fields will be neglected. It panics if s's kind is not struct.
|
||||
func (s *Struct) IsZero() bool {
|
||||
fields := s.structFields()
|
||||
|
||||
for _, field := range fields {
|
||||
val := s.value.FieldByName(field.Name)
|
||||
|
||||
_, tagOpts := parseTag(field.Tag.Get(s.TagName))
|
||||
|
||||
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||
ok := IsZero(val.Interface())
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// zero value of the given field, such as "" for string, 0 for int
|
||||
zero := reflect.Zero(val.Type()).Interface()
|
||||
|
||||
// current value of the given field
|
||||
current := val.Interface()
|
||||
|
||||
if !reflect.DeepEqual(current, zero) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// HasZero returns true if a field in a struct is not initialized (zero value).
|
||||
// A struct tag with the content of "-" ignores the checking of that particular
|
||||
// field. Example:
|
||||
//
|
||||
// // Field is ignored by this package.
|
||||
// Field bool `structs:"-"`
|
||||
//
|
||||
// A value with the option of "omitnested" stops iterating further if the type
|
||||
// is a struct. Example:
|
||||
//
|
||||
// // Field is not processed further by this package.
|
||||
// Field time.Time `structs:"myName,omitnested"`
|
||||
// Field *http.Request `structs:",omitnested"`
|
||||
//
|
||||
// Note that only exported fields of a struct can be accessed, non exported
|
||||
// fields will be neglected. It panics if s's kind is not struct.
|
||||
func (s *Struct) HasZero() bool {
|
||||
fields := s.structFields()
|
||||
|
||||
for _, field := range fields {
|
||||
val := s.value.FieldByName(field.Name)
|
||||
|
||||
_, tagOpts := parseTag(field.Tag.Get(s.TagName))
|
||||
|
||||
if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") {
|
||||
ok := HasZero(val.Interface())
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// zero value of the given field, such as "" for string, 0 for int
|
||||
zero := reflect.Zero(val.Type()).Interface()
|
||||
|
||||
// current value of the given field
|
||||
current := val.Interface()
|
||||
|
||||
if reflect.DeepEqual(current, zero) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Name returns the structs's type name within its package. For more info refer
|
||||
// to Name() function.
|
||||
func (s *Struct) Name() string {
|
||||
return s.value.Type().Name()
|
||||
}
|
||||
|
||||
// structFields returns the exported struct fields for a given s struct. This
|
||||
// is a convenient helper method to avoid duplicate code in some of the
|
||||
// functions.
|
||||
func (s *Struct) structFields() []reflect.StructField {
|
||||
t := s.value.Type()
|
||||
|
||||
var f []reflect.StructField
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
// we can't access the value of unexported fields
|
||||
if field.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// don't check if it's omitted
|
||||
if tag := field.Tag.Get(s.TagName); tag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
f = append(f, field)
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func strctVal(s interface{}) reflect.Value {
|
||||
v := reflect.ValueOf(s)
|
||||
|
||||
// if pointer get the underlying element≤
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
if v.Kind() != reflect.Struct {
|
||||
panic("not struct")
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Map converts the given struct to a map[string]interface{}. For more info
|
||||
// refer to Struct types Map() method. It panics if s's kind is not struct.
|
||||
func Map(s interface{}) map[string]interface{} {
|
||||
return New(s).Map()
|
||||
}
|
||||
|
||||
// Values converts the given struct to a []interface{}. For more info refer to
|
||||
// Struct types Values() method. It panics if s's kind is not struct.
|
||||
func Values(s interface{}) []interface{} {
|
||||
return New(s).Values()
|
||||
}
|
||||
|
||||
// Fields returns a slice of *Field. For more info refer to Struct types
|
||||
// Fields() method. It panics if s's kind is not struct.
|
||||
func Fields(s interface{}) []*Field {
|
||||
return New(s).Fields()
|
||||
}
|
||||
|
||||
// IsZero returns true if all fields is equal to a zero value. For more info
|
||||
// refer to Struct types IsZero() method. It panics if s's kind is not struct.
|
||||
func IsZero(s interface{}) bool {
|
||||
return New(s).IsZero()
|
||||
}
|
||||
|
||||
// HasZero returns true if any field is equal to a zero value. For more info
|
||||
// refer to Struct types HasZero() method. It panics if s's kind is not struct.
|
||||
func HasZero(s interface{}) bool {
|
||||
return New(s).HasZero()
|
||||
}
|
||||
|
||||
// IsStruct returns true if the given variable is a struct or a pointer to
|
||||
// struct.
|
||||
func IsStruct(s interface{}) bool {
|
||||
v := reflect.ValueOf(s)
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
// uninitialized zero value of a struct
|
||||
if v.Kind() == reflect.Invalid {
|
||||
return false
|
||||
}
|
||||
|
||||
return v.Kind() == reflect.Struct
|
||||
}
|
||||
|
||||
// Name returns the structs's type name within its package. It returns an
|
||||
// empty string for unnamed types. It panics if s's kind is not struct.
|
||||
func Name(s interface{}) string {
|
||||
return New(s).Name()
|
||||
}
|
|
@ -1,351 +0,0 @@
|
|||
package structs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ExampleNew() {
|
||||
type Server struct {
|
||||
Name string
|
||||
ID int32
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
Name: "Arslan",
|
||||
ID: 123456,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
s := New(server)
|
||||
|
||||
fmt.Printf("Name : %v\n", s.Name())
|
||||
fmt.Printf("Values : %v\n", s.Values())
|
||||
fmt.Printf("Value of ID : %v\n", s.Field("ID").Value())
|
||||
// Output:
|
||||
// Name : Server
|
||||
// Values : [Arslan 123456 true]
|
||||
// Value of ID : 123456
|
||||
|
||||
}
|
||||
|
||||
func ExampleMap() {
|
||||
type Server struct {
|
||||
Name string
|
||||
ID int32
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
Name: "Arslan",
|
||||
ID: 123456,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
m := Map(s)
|
||||
|
||||
fmt.Printf("%#v\n", m["Name"])
|
||||
fmt.Printf("%#v\n", m["ID"])
|
||||
fmt.Printf("%#v\n", m["Enabled"])
|
||||
// Output:
|
||||
// "Arslan"
|
||||
// 123456
|
||||
// true
|
||||
|
||||
}
|
||||
|
||||
func ExampleMap_tags() {
|
||||
// Custom tags can change the map keys instead of using the fields name
|
||||
type Server struct {
|
||||
Name string `structs:"server_name"`
|
||||
ID int32 `structs:"server_id"`
|
||||
Enabled bool `structs:"enabled"`
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
Name: "Zeynep",
|
||||
ID: 789012,
|
||||
}
|
||||
|
||||
m := Map(s)
|
||||
|
||||
// access them by the custom tags defined above
|
||||
fmt.Printf("%#v\n", m["server_name"])
|
||||
fmt.Printf("%#v\n", m["server_id"])
|
||||
fmt.Printf("%#v\n", m["enabled"])
|
||||
// Output:
|
||||
// "Zeynep"
|
||||
// 789012
|
||||
// false
|
||||
|
||||
}
|
||||
|
||||
func ExampleMap_nested() {
|
||||
// By default field with struct types are processed too. We can stop
|
||||
// processing them via "omitnested" tag option.
|
||||
type Server struct {
|
||||
Name string `structs:"server_name"`
|
||||
ID int32 `structs:"server_id"`
|
||||
Time time.Time `structs:"time,omitnested"` // do not convert to map[string]interface{}
|
||||
}
|
||||
|
||||
const shortForm = "2006-Jan-02"
|
||||
t, _ := time.Parse("2006-Jan-02", "2013-Feb-03")
|
||||
|
||||
s := &Server{
|
||||
Name: "Zeynep",
|
||||
ID: 789012,
|
||||
Time: t,
|
||||
}
|
||||
|
||||
m := Map(s)
|
||||
|
||||
// access them by the custom tags defined above
|
||||
fmt.Printf("%v\n", m["server_name"])
|
||||
fmt.Printf("%v\n", m["server_id"])
|
||||
fmt.Printf("%v\n", m["time"].(time.Time))
|
||||
// Output:
|
||||
// Zeynep
|
||||
// 789012
|
||||
// 2013-02-03 00:00:00 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleMap_omitEmpty() {
|
||||
// By default field with struct types of zero values are processed too. We
|
||||
// can stop processing them via "omitempty" tag option.
|
||||
type Server struct {
|
||||
Name string `structs:",omitempty"`
|
||||
ID int32 `structs:"server_id,omitempty"`
|
||||
Location string
|
||||
}
|
||||
|
||||
// Only add location
|
||||
s := &Server{
|
||||
Location: "Tokyo",
|
||||
}
|
||||
|
||||
m := Map(s)
|
||||
|
||||
// map contains only the Location field
|
||||
fmt.Printf("%v\n", m)
|
||||
// Output:
|
||||
// map[Location:Tokyo]
|
||||
}
|
||||
|
||||
func ExampleValues() {
|
||||
type Server struct {
|
||||
Name string
|
||||
ID int32
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
Name: "Fatih",
|
||||
ID: 135790,
|
||||
Enabled: false,
|
||||
}
|
||||
|
||||
m := Values(s)
|
||||
|
||||
fmt.Printf("Values: %+v\n", m)
|
||||
// Output:
|
||||
// Values: [Fatih 135790 false]
|
||||
}
|
||||
|
||||
func ExampleValues_omitEmpty() {
|
||||
// By default field with struct types of zero values are processed too. We
|
||||
// can stop processing them via "omitempty" tag option.
|
||||
type Server struct {
|
||||
Name string `structs:",omitempty"`
|
||||
ID int32 `structs:"server_id,omitempty"`
|
||||
Location string
|
||||
}
|
||||
|
||||
// Only add location
|
||||
s := &Server{
|
||||
Location: "Ankara",
|
||||
}
|
||||
|
||||
m := Values(s)
|
||||
|
||||
// values contains only the Location field
|
||||
fmt.Printf("Values: %+v\n", m)
|
||||
// Output:
|
||||
// Values: [Ankara]
|
||||
}
|
||||
|
||||
func ExampleValues_tags() {
|
||||
type Location struct {
|
||||
City string
|
||||
Country string
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
Name string
|
||||
ID int32
|
||||
Enabled bool
|
||||
Location Location `structs:"-"` // values from location are not included anymore
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
Name: "Fatih",
|
||||
ID: 135790,
|
||||
Enabled: false,
|
||||
Location: Location{City: "Ankara", Country: "Turkey"},
|
||||
}
|
||||
|
||||
// Let get all values from the struct s. Note that we don't include values
|
||||
// from the Location field
|
||||
m := Values(s)
|
||||
|
||||
fmt.Printf("Values: %+v\n", m)
|
||||
// Output:
|
||||
// Values: [Fatih 135790 false]
|
||||
}
|
||||
|
||||
func ExampleFields() {
|
||||
type Access struct {
|
||||
Name string
|
||||
LastAccessed time.Time
|
||||
Number int
|
||||
}
|
||||
|
||||
s := &Access{
|
||||
Name: "Fatih",
|
||||
LastAccessed: time.Now(),
|
||||
Number: 1234567,
|
||||
}
|
||||
|
||||
fields := Fields(s)
|
||||
|
||||
for i, field := range fields {
|
||||
fmt.Printf("[%d] %+v\n", i, field.Name())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// [0] Name
|
||||
// [1] LastAccessed
|
||||
// [2] Number
|
||||
}
|
||||
|
||||
func ExampleFields_nested() {
|
||||
type Person struct {
|
||||
Name string
|
||||
Number int
|
||||
}
|
||||
|
||||
type Access struct {
|
||||
Person Person
|
||||
HasPermission bool
|
||||
LastAccessed time.Time
|
||||
}
|
||||
|
||||
s := &Access{
|
||||
Person: Person{Name: "fatih", Number: 1234567},
|
||||
LastAccessed: time.Now(),
|
||||
HasPermission: true,
|
||||
}
|
||||
|
||||
// Let's get all fields from the struct s.
|
||||
fields := Fields(s)
|
||||
|
||||
for _, field := range fields {
|
||||
if field.Name() == "Person" {
|
||||
fmt.Printf("Access.Person.Name: %+v\n", field.Field("Name").Value())
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Access.Person.Name: fatih
|
||||
}
|
||||
|
||||
func ExampleField() {
|
||||
type Person struct {
|
||||
Name string
|
||||
Number int
|
||||
}
|
||||
|
||||
type Access struct {
|
||||
Person Person
|
||||
HasPermission bool
|
||||
LastAccessed time.Time
|
||||
}
|
||||
|
||||
access := &Access{
|
||||
Person: Person{Name: "fatih", Number: 1234567},
|
||||
LastAccessed: time.Now(),
|
||||
HasPermission: true,
|
||||
}
|
||||
|
||||
// Create a new Struct type
|
||||
s := New(access)
|
||||
|
||||
// Get the Field type for "Person" field
|
||||
p := s.Field("Person")
|
||||
|
||||
// Get the underlying "Name field" and print the value of it
|
||||
name := p.Field("Name")
|
||||
|
||||
fmt.Printf("Value of Person.Access.Name: %+v\n", name.Value())
|
||||
|
||||
// Output:
|
||||
// Value of Person.Access.Name: fatih
|
||||
|
||||
}
|
||||
|
||||
func ExampleIsZero() {
|
||||
type Server struct {
|
||||
Name string
|
||||
ID int32
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
// Nothing is initalized
|
||||
a := &Server{}
|
||||
isZeroA := IsZero(a)
|
||||
|
||||
// Name and Enabled is initialized, but not ID
|
||||
b := &Server{
|
||||
Name: "Golang",
|
||||
Enabled: true,
|
||||
}
|
||||
isZeroB := IsZero(b)
|
||||
|
||||
fmt.Printf("%#v\n", isZeroA)
|
||||
fmt.Printf("%#v\n", isZeroB)
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleHasZero() {
|
||||
// Let's define an Access struct. Note that the "Enabled" field is not
|
||||
// going to be checked because we added the "structs" tag to the field.
|
||||
type Access struct {
|
||||
Name string
|
||||
LastAccessed time.Time
|
||||
Number int
|
||||
Enabled bool `structs:"-"`
|
||||
}
|
||||
|
||||
// Name and Number is not initialized.
|
||||
a := &Access{
|
||||
LastAccessed: time.Now(),
|
||||
}
|
||||
hasZeroA := HasZero(a)
|
||||
|
||||
// Name and Number is initialized.
|
||||
b := &Access{
|
||||
Name: "Fatih",
|
||||
LastAccessed: time.Now(),
|
||||
Number: 12345,
|
||||
}
|
||||
hasZeroB := HasZero(b)
|
||||
|
||||
fmt.Printf("%#v\n", hasZeroA)
|
||||
fmt.Printf("%#v\n", hasZeroB)
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
}
|
|
@ -1,847 +0,0 @@
|
|||
package structs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMapNonStruct(t *testing.T) {
|
||||
foo := []string{"foo"}
|
||||
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err == nil {
|
||||
t.Error("Passing a non struct into Map should panic")
|
||||
}
|
||||
}()
|
||||
|
||||
// this should panic. We are going to recover and and test it
|
||||
_ = Map(foo)
|
||||
}
|
||||
|
||||
func TestStructIndexes(t *testing.T) {
|
||||
type C struct {
|
||||
something int
|
||||
Props map[string]interface{}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
fmt.Printf("err %+v\n", err)
|
||||
t.Error("Using mixed indexes should not panic")
|
||||
}
|
||||
}()
|
||||
|
||||
// They should not panic
|
||||
_ = Map(&C{})
|
||||
_ = Fields(&C{})
|
||||
_ = Values(&C{})
|
||||
_ = IsZero(&C{})
|
||||
_ = HasZero(&C{})
|
||||
}
|
||||
|
||||
func TestMap(t *testing.T) {
|
||||
var T = struct {
|
||||
A string
|
||||
B int
|
||||
C bool
|
||||
}{
|
||||
A: "a-value",
|
||||
B: 2,
|
||||
C: true,
|
||||
}
|
||||
|
||||
a := Map(T)
|
||||
|
||||
if typ := reflect.TypeOf(a).Kind(); typ != reflect.Map {
|
||||
t.Errorf("Map should return a map type, got: %v", typ)
|
||||
}
|
||||
|
||||
// we have three fields
|
||||
if len(a) != 3 {
|
||||
t.Errorf("Map should return a map of len 3, got: %d", len(a))
|
||||
}
|
||||
|
||||
inMap := func(val interface{}) bool {
|
||||
for _, v := range a {
|
||||
if reflect.DeepEqual(v, val) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
for _, val := range []interface{}{"a-value", 2, true} {
|
||||
if !inMap(val) {
|
||||
t.Errorf("Map should have the value %v", val)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMap_Tag(t *testing.T) {
|
||||
var T = struct {
|
||||
A string `structs:"x"`
|
||||
B int `structs:"y"`
|
||||
C bool `structs:"z"`
|
||||
}{
|
||||
A: "a-value",
|
||||
B: 2,
|
||||
C: true,
|
||||
}
|
||||
|
||||
a := Map(T)
|
||||
|
||||
inMap := func(key interface{}) bool {
|
||||
for k := range a {
|
||||
if reflect.DeepEqual(k, key) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for _, key := range []string{"x", "y", "z"} {
|
||||
if !inMap(key) {
|
||||
t.Errorf("Map should have the key %v", key)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMap_CustomTag(t *testing.T) {
|
||||
var T = struct {
|
||||
A string `dd:"x"`
|
||||
B int `dd:"y"`
|
||||
C bool `dd:"z"`
|
||||
}{
|
||||
A: "a-value",
|
||||
B: 2,
|
||||
C: true,
|
||||
}
|
||||
|
||||
s := New(T)
|
||||
s.TagName = "dd"
|
||||
|
||||
a := s.Map()
|
||||
|
||||
inMap := func(key interface{}) bool {
|
||||
for k := range a {
|
||||
if reflect.DeepEqual(k, key) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for _, key := range []string{"x", "y", "z"} {
|
||||
if !inMap(key) {
|
||||
t.Errorf("Map should have the key %v", key)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMap_MultipleCustomTag(t *testing.T) {
|
||||
var A = struct {
|
||||
X string `aa:"ax"`
|
||||
}{"a_value"}
|
||||
|
||||
aStruct := New(A)
|
||||
aStruct.TagName = "aa"
|
||||
|
||||
var B = struct {
|
||||
X string `bb:"bx"`
|
||||
}{"b_value"}
|
||||
|
||||
bStruct := New(B)
|
||||
bStruct.TagName = "bb"
|
||||
|
||||
a, b := aStruct.Map(), bStruct.Map()
|
||||
if !reflect.DeepEqual(a, map[string]interface{}{"ax": "a_value"}) {
|
||||
t.Error("Map should have field ax with value a_value")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(b, map[string]interface{}{"bx": "b_value"}) {
|
||||
t.Error("Map should have field bx with value b_value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMap_OmitEmpty(t *testing.T) {
|
||||
type A struct {
|
||||
Name string
|
||||
Value string `structs:",omitempty"`
|
||||
Time time.Time `structs:",omitempty"`
|
||||
}
|
||||
a := A{}
|
||||
|
||||
m := Map(a)
|
||||
|
||||
_, ok := m["Value"].(map[string]interface{})
|
||||
if ok {
|
||||
t.Error("Map should not contain the Value field that is tagged as omitempty")
|
||||
}
|
||||
|
||||
_, ok = m["Time"].(map[string]interface{})
|
||||
if ok {
|
||||
t.Error("Map should not contain the Time field that is tagged as omitempty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMap_OmitNested(t *testing.T) {
|
||||
type A struct {
|
||||
Name string
|
||||
Value string
|
||||
Time time.Time `structs:",omitnested"`
|
||||
}
|
||||
a := A{Time: time.Now()}
|
||||
|
||||
type B struct {
|
||||
Desc string
|
||||
A A
|
||||
}
|
||||
b := &B{A: a}
|
||||
|
||||
m := Map(b)
|
||||
|
||||
in, ok := m["A"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Error("Map nested structs is not available in the map")
|
||||
}
|
||||
|
||||
// should not happen
|
||||
if _, ok := in["Time"].(map[string]interface{}); ok {
|
||||
t.Error("Map nested struct should omit recursiving parsing of Time")
|
||||
}
|
||||
|
||||
if _, ok := in["Time"].(time.Time); !ok {
|
||||
t.Error("Map nested struct should stop parsing of Time at is current value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMap_Nested(t *testing.T) {
|
||||
type A struct {
|
||||
Name string
|
||||
}
|
||||
a := &A{Name: "example"}
|
||||
|
||||
type B struct {
|
||||
A *A
|
||||
}
|
||||
b := &B{A: a}
|
||||
|
||||
m := Map(b)
|
||||
|
||||
if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map {
|
||||
t.Errorf("Map should return a map type, got: %v", typ)
|
||||
}
|
||||
|
||||
in, ok := m["A"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Error("Map nested structs is not available in the map")
|
||||
}
|
||||
|
||||
if name := in["Name"].(string); name != "example" {
|
||||
t.Errorf("Map nested struct's name field should give example, got: %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMap_Anonymous(t *testing.T) {
|
||||
type A struct {
|
||||
Name string
|
||||
}
|
||||
a := &A{Name: "example"}
|
||||
|
||||
type B struct {
|
||||
*A
|
||||
}
|
||||
b := &B{}
|
||||
b.A = a
|
||||
|
||||
m := Map(b)
|
||||
|
||||
if typ := reflect.TypeOf(m).Kind(); typ != reflect.Map {
|
||||
t.Errorf("Map should return a map type, got: %v", typ)
|
||||
}
|
||||
|
||||
in, ok := m["A"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Error("Embedded structs is not available in the map")
|
||||
}
|
||||
|
||||
if name := in["Name"].(string); name != "example" {
|
||||
t.Errorf("Embedded A struct's Name field should give example, got: %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStruct(t *testing.T) {
|
||||
var T = struct{}{}
|
||||
|
||||
if !IsStruct(T) {
|
||||
t.Errorf("T should be a struct, got: %T", T)
|
||||
}
|
||||
|
||||
if !IsStruct(&T) {
|
||||
t.Errorf("T should be a struct, got: %T", T)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestValues(t *testing.T) {
|
||||
var T = struct {
|
||||
A string
|
||||
B int
|
||||
C bool
|
||||
}{
|
||||
A: "a-value",
|
||||
B: 2,
|
||||
C: true,
|
||||
}
|
||||
|
||||
s := Values(T)
|
||||
|
||||
if typ := reflect.TypeOf(s).Kind(); typ != reflect.Slice {
|
||||
t.Errorf("Values should return a slice type, got: %v", typ)
|
||||
}
|
||||
|
||||
inSlice := func(val interface{}) bool {
|
||||
for _, v := range s {
|
||||
if reflect.DeepEqual(v, val) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for _, val := range []interface{}{"a-value", 2, true} {
|
||||
if !inSlice(val) {
|
||||
t.Errorf("Values should have the value %v", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValues_OmitEmpty(t *testing.T) {
|
||||
type A struct {
|
||||
Name string
|
||||
Value int `structs:",omitempty"`
|
||||
}
|
||||
|
||||
a := A{Name: "example"}
|
||||
s := Values(a)
|
||||
|
||||
if len(s) != 1 {
|
||||
t.Errorf("Values of omitted empty fields should be not counted")
|
||||
}
|
||||
|
||||
if s[0].(string) != "example" {
|
||||
t.Errorf("Values of omitted empty fields should left the value example")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValues_OmitNested(t *testing.T) {
|
||||
type A struct {
|
||||
Name string
|
||||
Value int
|
||||
}
|
||||
|
||||
a := A{
|
||||
Name: "example",
|
||||
Value: 123,
|
||||
}
|
||||
|
||||
type B struct {
|
||||
A A `structs:",omitnested"`
|
||||
C int
|
||||
}
|
||||
b := &B{A: a, C: 123}
|
||||
|
||||
s := Values(b)
|
||||
|
||||
if len(s) != 2 {
|
||||
t.Errorf("Values of omitted nested struct should be not counted")
|
||||
}
|
||||
|
||||
inSlice := func(val interface{}) bool {
|
||||
for _, v := range s {
|
||||
if reflect.DeepEqual(v, val) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for _, val := range []interface{}{123, a} {
|
||||
if !inSlice(val) {
|
||||
t.Errorf("Values should have the value %v", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValues_Nested(t *testing.T) {
|
||||
type A struct {
|
||||
Name string
|
||||
}
|
||||
a := A{Name: "example"}
|
||||
|
||||
type B struct {
|
||||
A A
|
||||
C int
|
||||
}
|
||||
b := &B{A: a, C: 123}
|
||||
|
||||
s := Values(b)
|
||||
|
||||
inSlice := func(val interface{}) bool {
|
||||
for _, v := range s {
|
||||
if reflect.DeepEqual(v, val) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for _, val := range []interface{}{"example", 123} {
|
||||
if !inSlice(val) {
|
||||
t.Errorf("Values should have the value %v", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValues_Anonymous(t *testing.T) {
|
||||
type A struct {
|
||||
Name string
|
||||
}
|
||||
a := A{Name: "example"}
|
||||
|
||||
type B struct {
|
||||
A
|
||||
C int
|
||||
}
|
||||
b := &B{C: 123}
|
||||
b.A = a
|
||||
|
||||
s := Values(b)
|
||||
|
||||
inSlice := func(val interface{}) bool {
|
||||
for _, v := range s {
|
||||
if reflect.DeepEqual(v, val) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for _, val := range []interface{}{"example", 123} {
|
||||
if !inSlice(val) {
|
||||
t.Errorf("Values should have the value %v", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFields(t *testing.T) {
|
||||
var T = struct {
|
||||
A string
|
||||
B int
|
||||
C bool
|
||||
}{
|
||||
A: "a-value",
|
||||
B: 2,
|
||||
C: true,
|
||||
}
|
||||
|
||||
s := Fields(T)
|
||||
|
||||
if len(s) != 3 {
|
||||
t.Errorf("Fields should return a slice of len 3, got: %d", len(s))
|
||||
}
|
||||
|
||||
inSlice := func(val string) bool {
|
||||
for _, v := range s {
|
||||
if reflect.DeepEqual(v.Name(), val) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for _, val := range []string{"A", "B", "C"} {
|
||||
if !inSlice(val) {
|
||||
t.Errorf("Fields should have the value %v", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFields_OmitNested(t *testing.T) {
|
||||
type A struct {
|
||||
Name string
|
||||
Enabled bool
|
||||
}
|
||||
a := A{Name: "example"}
|
||||
|
||||
type B struct {
|
||||
A A
|
||||
C int
|
||||
Value string `structs:"-"`
|
||||
Number int
|
||||
}
|
||||
b := &B{A: a, C: 123}
|
||||
|
||||
s := Fields(b)
|
||||
|
||||
if len(s) != 3 {
|
||||
t.Errorf("Fields should omit nested struct. Expecting 2 got: %d", len(s))
|
||||
}
|
||||
|
||||
inSlice := func(val interface{}) bool {
|
||||
for _, v := range s {
|
||||
if reflect.DeepEqual(v.Name(), val) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for _, val := range []interface{}{"A", "C"} {
|
||||
if !inSlice(val) {
|
||||
t.Errorf("Fields should have the value %v", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFields_Anonymous(t *testing.T) {
|
||||
type A struct {
|
||||
Name string
|
||||
}
|
||||
a := A{Name: "example"}
|
||||
|
||||
type B struct {
|
||||
A
|
||||
C int
|
||||
}
|
||||
b := &B{C: 123}
|
||||
b.A = a
|
||||
|
||||
s := Fields(b)
|
||||
|
||||
inSlice := func(val interface{}) bool {
|
||||
for _, v := range s {
|
||||
if reflect.DeepEqual(v.Name(), val) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for _, val := range []interface{}{"A", "C"} {
|
||||
if !inSlice(val) {
|
||||
t.Errorf("Fields should have the value %v", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsZero(t *testing.T) {
|
||||
var T = struct {
|
||||
A string
|
||||
B int
|
||||
C bool `structs:"-"`
|
||||
D []string
|
||||
}{}
|
||||
|
||||
ok := IsZero(T)
|
||||
if !ok {
|
||||
t.Error("IsZero should return true because none of the fields are initialized.")
|
||||
}
|
||||
|
||||
var X = struct {
|
||||
A string
|
||||
F *bool
|
||||
}{
|
||||
A: "a-value",
|
||||
}
|
||||
|
||||
ok = IsZero(X)
|
||||
if ok {
|
||||
t.Error("IsZero should return false because A is initialized")
|
||||
}
|
||||
|
||||
var Y = struct {
|
||||
A string
|
||||
B int
|
||||
}{
|
||||
A: "a-value",
|
||||
B: 123,
|
||||
}
|
||||
|
||||
ok = IsZero(Y)
|
||||
if ok {
|
||||
t.Error("IsZero should return false because A and B is initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsZero_OmitNested(t *testing.T) {
|
||||
type A struct {
|
||||
Name string
|
||||
D string
|
||||
}
|
||||
a := A{Name: "example"}
|
||||
|
||||
type B struct {
|
||||
A A `structs:",omitnested"`
|
||||
C int
|
||||
}
|
||||
b := &B{A: a, C: 123}
|
||||
|
||||
ok := IsZero(b)
|
||||
if ok {
|
||||
t.Error("IsZero should return false because A, B and C are initialized")
|
||||
}
|
||||
|
||||
aZero := A{}
|
||||
bZero := &B{A: aZero}
|
||||
|
||||
ok = IsZero(bZero)
|
||||
if !ok {
|
||||
t.Error("IsZero should return true because neither A nor B is initialized")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestIsZero_Nested(t *testing.T) {
|
||||
type A struct {
|
||||
Name string
|
||||
D string
|
||||
}
|
||||
a := A{Name: "example"}
|
||||
|
||||
type B struct {
|
||||
A A
|
||||
C int
|
||||
}
|
||||
b := &B{A: a, C: 123}
|
||||
|
||||
ok := IsZero(b)
|
||||
if ok {
|
||||
t.Error("IsZero should return false because A, B and C are initialized")
|
||||
}
|
||||
|
||||
aZero := A{}
|
||||
bZero := &B{A: aZero}
|
||||
|
||||
ok = IsZero(bZero)
|
||||
if !ok {
|
||||
t.Error("IsZero should return true because neither A nor B is initialized")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestIsZero_Anonymous(t *testing.T) {
|
||||
type A struct {
|
||||
Name string
|
||||
D string
|
||||
}
|
||||
a := A{Name: "example"}
|
||||
|
||||
type B struct {
|
||||
A
|
||||
C int
|
||||
}
|
||||
b := &B{C: 123}
|
||||
b.A = a
|
||||
|
||||
ok := IsZero(b)
|
||||
if ok {
|
||||
t.Error("IsZero should return false because A, B and C are initialized")
|
||||
}
|
||||
|
||||
aZero := A{}
|
||||
bZero := &B{}
|
||||
bZero.A = aZero
|
||||
|
||||
ok = IsZero(bZero)
|
||||
if !ok {
|
||||
t.Error("IsZero should return true because neither A nor B is initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasZero(t *testing.T) {
|
||||
var T = struct {
|
||||
A string
|
||||
B int
|
||||
C bool `structs:"-"`
|
||||
D []string
|
||||
}{
|
||||
A: "a-value",
|
||||
B: 2,
|
||||
}
|
||||
|
||||
ok := HasZero(T)
|
||||
if !ok {
|
||||
t.Error("HasZero should return true because A and B are initialized.")
|
||||
}
|
||||
|
||||
var X = struct {
|
||||
A string
|
||||
F *bool
|
||||
}{
|
||||
A: "a-value",
|
||||
}
|
||||
|
||||
ok = HasZero(X)
|
||||
if !ok {
|
||||
t.Error("HasZero should return true because A is initialized")
|
||||
}
|
||||
|
||||
var Y = struct {
|
||||
A string
|
||||
B int
|
||||
}{
|
||||
A: "a-value",
|
||||
B: 123,
|
||||
}
|
||||
|
||||
ok = HasZero(Y)
|
||||
if ok {
|
||||
t.Error("HasZero should return false because A and B is initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasZero_OmitNested(t *testing.T) {
|
||||
type A struct {
|
||||
Name string
|
||||
D string
|
||||
}
|
||||
a := A{Name: "example"}
|
||||
|
||||
type B struct {
|
||||
A A `structs:",omitnested"`
|
||||
C int
|
||||
}
|
||||
b := &B{A: a, C: 123}
|
||||
|
||||
// Because the Field A inside B is omitted HasZero should return false
|
||||
// because it will stop iterating deeper andnot going to lookup for D
|
||||
ok := HasZero(b)
|
||||
if ok {
|
||||
t.Error("HasZero should return false because A and C are initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasZero_Nested(t *testing.T) {
|
||||
type A struct {
|
||||
Name string
|
||||
D string
|
||||
}
|
||||
a := A{Name: "example"}
|
||||
|
||||
type B struct {
|
||||
A A
|
||||
C int
|
||||
}
|
||||
b := &B{A: a, C: 123}
|
||||
|
||||
ok := HasZero(b)
|
||||
if !ok {
|
||||
t.Error("HasZero should return true because D is not initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasZero_Anonymous(t *testing.T) {
|
||||
type A struct {
|
||||
Name string
|
||||
D string
|
||||
}
|
||||
a := A{Name: "example"}
|
||||
|
||||
type B struct {
|
||||
A
|
||||
C int
|
||||
}
|
||||
b := &B{C: 123}
|
||||
b.A = a
|
||||
|
||||
ok := HasZero(b)
|
||||
if !ok {
|
||||
t.Error("HasZero should return false because D is not initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestName(t *testing.T) {
|
||||
type Foo struct {
|
||||
A string
|
||||
B bool
|
||||
}
|
||||
f := &Foo{}
|
||||
|
||||
n := Name(f)
|
||||
if n != "Foo" {
|
||||
t.Errorf("Name should return Foo, got: %s", n)
|
||||
}
|
||||
|
||||
unnamed := struct{ Name string }{Name: "Cihangir"}
|
||||
m := Name(unnamed)
|
||||
if m != "" {
|
||||
t.Errorf("Name should return empty string for unnamed struct, got: %s", n)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err == nil {
|
||||
t.Error("Name should panic if a non struct is passed")
|
||||
}
|
||||
}()
|
||||
|
||||
Name([]string{})
|
||||
}
|
||||
|
||||
func TestNestedNilPointer(t *testing.T) {
|
||||
type Collar struct {
|
||||
Engraving string
|
||||
}
|
||||
|
||||
type Dog struct {
|
||||
Name string
|
||||
Collar *Collar
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
Name string
|
||||
Dog *Dog
|
||||
}
|
||||
|
||||
person := &Person{
|
||||
Name: "John",
|
||||
}
|
||||
|
||||
personWithDog := &Person{
|
||||
Name: "Ron",
|
||||
Dog: &Dog{
|
||||
Name: "Rover",
|
||||
},
|
||||
}
|
||||
|
||||
personWithDogWithCollar := &Person{
|
||||
Name: "Kon",
|
||||
Dog: &Dog{
|
||||
Name: "Ruffles",
|
||||
Collar: &Collar{
|
||||
Engraving: "If lost, call Kon",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
fmt.Printf("err %+v\n", err)
|
||||
t.Error("Internal nil pointer should not panic")
|
||||
}
|
||||
}()
|
||||
|
||||
_ = Map(person) // Panics
|
||||
_ = Map(personWithDog) // Panics
|
||||
_ = Map(personWithDogWithCollar) // Doesn't panic
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
package structs
|
||||
|
||||
import "strings"
|
||||
|
||||
// tagOptions contains a slice of tag options
|
||||
type tagOptions []string
|
||||
|
||||
// Has returns true if the given optiton is available in tagOptions
|
||||
func (t tagOptions) Has(opt string) bool {
|
||||
for _, tagOpt := range t {
|
||||
if tagOpt == opt {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// parseTag splits a struct field's tag into its name and a list of options
|
||||
// which comes after a name. A tag is in the form of: "name,option1,option2".
|
||||
// The name can be neglectected.
|
||||
func parseTag(tag string) (string, tagOptions) {
|
||||
// tag is one of followings:
|
||||
// ""
|
||||
// "name"
|
||||
// "name,opt"
|
||||
// "name,opt,opt2"
|
||||
// ",opt"
|
||||
|
||||
res := strings.Split(tag, ",")
|
||||
return res[0], res[1:]
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package structs
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseTag_Name(t *testing.T) {
|
||||
tags := []struct {
|
||||
tag string
|
||||
has bool
|
||||
}{
|
||||
{"", false},
|
||||
{"name", true},
|
||||
{"name,opt", true},
|
||||
{"name , opt, opt2", false}, // has a single whitespace
|
||||
{", opt, opt2", false},
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
name, _ := parseTag(tag.tag)
|
||||
|
||||
if (name != "name") && tag.has {
|
||||
t.Errorf("Parse tag should return name: %#v", tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTag_Opts(t *testing.T) {
|
||||
tags := []struct {
|
||||
opts string
|
||||
has bool
|
||||
}{
|
||||
{"name", false},
|
||||
{"name,opt", true},
|
||||
{"name , opt, opt2", false}, // has a single whitespace
|
||||
{",opt, opt2", true},
|
||||
{", opt3, opt4", false},
|
||||
}
|
||||
|
||||
// search for "opt"
|
||||
for _, tag := range tags {
|
||||
_, opts := parseTag(tag.opts)
|
||||
|
||||
if opts.Has("opt") != tag.has {
|
||||
t.Errorf("Tag opts should have opt: %#v", tag)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,8 +3,8 @@
|
|||
package check_test
|
||||
|
||||
import (
|
||||
. "gopkg.in/check.v1"
|
||||
"time"
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
var benchmarkS = Suite(&BenchmarkS{})
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package check_test
|
||||
|
||||
import (
|
||||
. "gopkg.in/check.v1"
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
var _ = Suite(&PrinterS{})
|
||||
|
@ -9,101 +9,96 @@ var _ = Suite(&PrinterS{})
|
|||
type PrinterS struct{}
|
||||
|
||||
func (s *PrinterS) TestCountSuite(c *C) {
|
||||
suitesRun += 1
|
||||
suitesRun += 1
|
||||
}
|
||||
|
||||
var printTestFuncLine int
|
||||
|
||||
func init() {
|
||||
printTestFuncLine = getMyLine() + 3
|
||||
printTestFuncLine = getMyLine() + 3
|
||||
}
|
||||
|
||||
func printTestFunc() {
|
||||
println(1) // Comment1
|
||||
if 2 == 2 { // Comment2
|
||||
println(3) // Comment3
|
||||
}
|
||||
switch 5 {
|
||||
case 6:
|
||||
println(6) // Comment6
|
||||
println(7)
|
||||
}
|
||||
switch interface{}(9).(type) { // Comment9
|
||||
case int:
|
||||
println(10)
|
||||
println(11)
|
||||
}
|
||||
select {
|
||||
case <-(chan bool)(nil):
|
||||
println(14)
|
||||
println(15)
|
||||
default:
|
||||
println(16)
|
||||
println(17)
|
||||
}
|
||||
println(19,
|
||||
20)
|
||||
_ = func() {
|
||||
println(21)
|
||||
println(22)
|
||||
}
|
||||
println(24, func() {
|
||||
println(25)
|
||||
})
|
||||
// Leading comment
|
||||
// with multiple lines.
|
||||
println(29) // Comment29
|
||||
println(1) // Comment1
|
||||
if 2 == 2 { // Comment2
|
||||
println(3) // Comment3
|
||||
}
|
||||
switch 5 {
|
||||
case 6: println(6) // Comment6
|
||||
println(7)
|
||||
}
|
||||
switch interface{}(9).(type) {// Comment9
|
||||
case int: println(10)
|
||||
println(11)
|
||||
}
|
||||
select {
|
||||
case <-(chan bool)(nil): println(14)
|
||||
println(15)
|
||||
default: println(16)
|
||||
println(17)
|
||||
}
|
||||
println(19,
|
||||
20)
|
||||
_ = func() { println(21)
|
||||
println(22)
|
||||
}
|
||||
println(24, func() {
|
||||
println(25)
|
||||
})
|
||||
// Leading comment
|
||||
// with multiple lines.
|
||||
println(29) // Comment29
|
||||
}
|
||||
|
||||
var printLineTests = []struct {
|
||||
line int
|
||||
output string
|
||||
line int
|
||||
output string
|
||||
}{
|
||||
{1, "println(1) // Comment1"},
|
||||
{2, "if 2 == 2 { // Comment2\n ...\n}"},
|
||||
{3, "println(3) // Comment3"},
|
||||
{5, "switch 5 {\n...\n}"},
|
||||
{6, "case 6:\n println(6) // Comment6\n ..."},
|
||||
{7, "println(7)"},
|
||||
{9, "switch interface{}(9).(type) { // Comment9\n...\n}"},
|
||||
{10, "case int:\n println(10)\n ..."},
|
||||
{14, "case <-(chan bool)(nil):\n println(14)\n ..."},
|
||||
{15, "println(15)"},
|
||||
{16, "default:\n println(16)\n ..."},
|
||||
{17, "println(17)"},
|
||||
{19, "println(19,\n 20)"},
|
||||
{20, "println(19,\n 20)"},
|
||||
{21, "_ = func() {\n println(21)\n println(22)\n}"},
|
||||
{22, "println(22)"},
|
||||
{24, "println(24, func() {\n println(25)\n})"},
|
||||
{25, "println(25)"},
|
||||
{26, "println(24, func() {\n println(25)\n})"},
|
||||
{29, "// Leading comment\n// with multiple lines.\nprintln(29) // Comment29"},
|
||||
{1, "println(1) // Comment1"},
|
||||
{2, "if 2 == 2 { // Comment2\n ...\n}"},
|
||||
{3, "println(3) // Comment3"},
|
||||
{5, "switch 5 {\n...\n}"},
|
||||
{6, "case 6:\n println(6) // Comment6\n ..."},
|
||||
{7, "println(7)"},
|
||||
{9, "switch interface{}(9).(type) { // Comment9\n...\n}"},
|
||||
{10, "case int:\n println(10)\n ..."},
|
||||
{14, "case <-(chan bool)(nil):\n println(14)\n ..."},
|
||||
{15, "println(15)"},
|
||||
{16, "default:\n println(16)\n ..."},
|
||||
{17, "println(17)"},
|
||||
{19, "println(19,\n 20)"},
|
||||
{20, "println(19,\n 20)"},
|
||||
{21, "_ = func() {\n println(21)\n println(22)\n}"},
|
||||
{22, "println(22)"},
|
||||
{24, "println(24, func() {\n println(25)\n})"},
|
||||
{25, "println(25)"},
|
||||
{26, "println(24, func() {\n println(25)\n})"},
|
||||
{29, "// Leading comment\n// with multiple lines.\nprintln(29) // Comment29"},
|
||||
}
|
||||
|
||||
func (s *PrinterS) TestPrintLine(c *C) {
|
||||
for _, test := range printLineTests {
|
||||
output, err := PrintLine("printer_test.go", printTestFuncLine+test.line)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(output, Equals, test.output)
|
||||
}
|
||||
for _, test := range printLineTests {
|
||||
output, err := PrintLine("printer_test.go", printTestFuncLine+test.line)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(output, Equals, test.output)
|
||||
}
|
||||
}
|
||||
|
||||
var indentTests = []struct {
|
||||
in, out string
|
||||
in, out string
|
||||
}{
|
||||
{"", ""},
|
||||
{"\n", "\n"},
|
||||
{"a", ">>>a"},
|
||||
{"a\n", ">>>a\n"},
|
||||
{"a\nb", ">>>a\n>>>b"},
|
||||
{" ", ">>> "},
|
||||
{"", ""},
|
||||
{"\n", "\n"},
|
||||
{"a", ">>>a"},
|
||||
{"a\n", ">>>a\n"},
|
||||
{"a\nb", ">>>a\n>>>b"},
|
||||
{" ", ">>> "},
|
||||
}
|
||||
|
||||
func (s *PrinterS) TestIndent(c *C) {
|
||||
for _, test := range indentTests {
|
||||
out := Indent(test.in, ">>>")
|
||||
c.Assert(out, Equals, test.out)
|
||||
}
|
||||
for _, test := range indentTests {
|
||||
out := Indent(test.in, ">>>")
|
||||
c.Assert(out, Equals, test.out)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -400,7 +400,7 @@ func (s *RunS) TestStreamModeWithMiss(c *C) {
|
|||
// -----------------------------------------------------------------------
|
||||
// Verify that that the keep work dir request indeed does so.
|
||||
|
||||
type WorkDirSuite struct{}
|
||||
type WorkDirSuite struct {}
|
||||
|
||||
func (s *WorkDirSuite) Test(c *C) {
|
||||
c.MkDir()
|
||||
|
@ -411,7 +411,7 @@ func (s *RunS) TestKeepWorkDir(c *C) {
|
|||
runConf := RunConf{Output: &output, Verbose: true, KeepWorkDir: true}
|
||||
result := Run(&WorkDirSuite{}, &runConf)
|
||||
|
||||
c.Assert(result.String(), Matches, ".*\nWORK="+result.WorkDir)
|
||||
c.Assert(result.String(), Matches, ".*\nWORK=" + result.WorkDir)
|
||||
|
||||
stat, err := os.Stat(result.WorkDir)
|
||||
c.Assert(err, IsNil)
|
||||
|
|
Loading…
Reference in New Issue