mirror of https://github.com/minio/minio.git
Merge pull request #438 from harshavardhana/pr_out_remove_mxj_update_gopkg_in_check_v1
This commit is contained in:
commit
9e5911cc62
|
@ -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,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,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{})
|
||||
|
|
|
@ -24,27 +24,22 @@ func printTestFunc() {
|
|||
println(3) // Comment3
|
||||
}
|
||||
switch 5 {
|
||||
case 6:
|
||||
println(6) // Comment6
|
||||
case 6: println(6) // Comment6
|
||||
println(7)
|
||||
}
|
||||
switch interface{}(9).(type) {// Comment9
|
||||
case int:
|
||||
println(10)
|
||||
case int: println(10)
|
||||
println(11)
|
||||
}
|
||||
select {
|
||||
case <-(chan bool)(nil):
|
||||
println(14)
|
||||
case <-(chan bool)(nil): println(14)
|
||||
println(15)
|
||||
default:
|
||||
println(16)
|
||||
default: println(16)
|
||||
println(17)
|
||||
}
|
||||
println(19,
|
||||
20)
|
||||
_ = func() {
|
||||
println(21)
|
||||
_ = func() { println(21)
|
||||
println(22)
|
||||
}
|
||||
println(24, func() {
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"ImportPath": "github.com/minio-io/minio/pkg/api",
|
||||
"GoVersion": "go1.4",
|
||||
"Packages": [
|
||||
"./..."
|
||||
],
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/gorilla/context",
|
||||
"Rev": "50c25fb3b2b3b3cc724e9b6ac75fb44b3bccd0da"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gorilla/mux",
|
||||
"Rev": "e444e69cbd2e2e3e0749a2f3c717cec491552bbf"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/minio-io/erasure",
|
||||
"Rev": "3cece1a107115563682604b1430418e28f65dd80"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/minio-io/iodine",
|
||||
"Rev": "55cc4d4256c68fbd6f0775f1a25e37e6a2f6457e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/stretchr/objx",
|
||||
"Rev": "cbeaeb16a013161a98496fad62933b1d21786672"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/stretchr/testify/assert",
|
||||
"Rev": "e4ec8152c15fc46bd5056ce65997a07c7d415325"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/stretchr/testify/mock",
|
||||
"Rev": "e4ec8152c15fc46bd5056ce65997a07c7d415325"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/check.v1",
|
||||
"Rev": "64131543e7896d5bcc6bd5a76287eb75ea96c673"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
This directory tree is generated automatically by godep.
|
||||
|
||||
Please do not edit.
|
||||
|
||||
See https://github.com/tools/godep for more information.
|
|
@ -0,0 +1,2 @@
|
|||
/pkg
|
||||
/bin
|
7
pkg/api/Godeps/_workspace/src/github.com/gorilla/context/.travis.yml
generated
vendored
Normal file
7
pkg/api/Godeps/_workspace/src/github.com/gorilla/context/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.0
|
||||
- 1.1
|
||||
- 1.2
|
||||
- tip
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,7 @@
|
|||
context
|
||||
=======
|
||||
[![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context)
|
||||
|
||||
gorilla/context is a general purpose registry for global request variables.
|
||||
|
||||
Read the full documentation here: http://www.gorillatoolkit.org/pkg/context
|
143
pkg/api/Godeps/_workspace/src/github.com/gorilla/context/context.go
generated
vendored
Normal file
143
pkg/api/Godeps/_workspace/src/github.com/gorilla/context/context.go
generated
vendored
Normal file
|
@ -0,0 +1,143 @@
|
|||
// Copyright 2012 The Gorilla 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 context
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
mutex sync.RWMutex
|
||||
data = make(map[*http.Request]map[interface{}]interface{})
|
||||
datat = make(map[*http.Request]int64)
|
||||
)
|
||||
|
||||
// Set stores a value for a given key in a given request.
|
||||
func Set(r *http.Request, key, val interface{}) {
|
||||
mutex.Lock()
|
||||
if data[r] == nil {
|
||||
data[r] = make(map[interface{}]interface{})
|
||||
datat[r] = time.Now().Unix()
|
||||
}
|
||||
data[r][key] = val
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
// Get returns a value stored for a given key in a given request.
|
||||
func Get(r *http.Request, key interface{}) interface{} {
|
||||
mutex.RLock()
|
||||
if ctx := data[r]; ctx != nil {
|
||||
value := ctx[key]
|
||||
mutex.RUnlock()
|
||||
return value
|
||||
}
|
||||
mutex.RUnlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOk returns stored value and presence state like multi-value return of map access.
|
||||
func GetOk(r *http.Request, key interface{}) (interface{}, bool) {
|
||||
mutex.RLock()
|
||||
if _, ok := data[r]; ok {
|
||||
value, ok := data[r][key]
|
||||
mutex.RUnlock()
|
||||
return value, ok
|
||||
}
|
||||
mutex.RUnlock()
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// GetAll returns all stored values for the request as a map. Nil is returned for invalid requests.
|
||||
func GetAll(r *http.Request) map[interface{}]interface{} {
|
||||
mutex.RLock()
|
||||
if context, ok := data[r]; ok {
|
||||
result := make(map[interface{}]interface{}, len(context))
|
||||
for k, v := range context {
|
||||
result[k] = v
|
||||
}
|
||||
mutex.RUnlock()
|
||||
return result
|
||||
}
|
||||
mutex.RUnlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAllOk returns all stored values for the request as a map and a boolean value that indicates if
|
||||
// the request was registered.
|
||||
func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) {
|
||||
mutex.RLock()
|
||||
context, ok := data[r]
|
||||
result := make(map[interface{}]interface{}, len(context))
|
||||
for k, v := range context {
|
||||
result[k] = v
|
||||
}
|
||||
mutex.RUnlock()
|
||||
return result, ok
|
||||
}
|
||||
|
||||
// Delete removes a value stored for a given key in a given request.
|
||||
func Delete(r *http.Request, key interface{}) {
|
||||
mutex.Lock()
|
||||
if data[r] != nil {
|
||||
delete(data[r], key)
|
||||
}
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
// Clear removes all values stored for a given request.
|
||||
//
|
||||
// This is usually called by a handler wrapper to clean up request
|
||||
// variables at the end of a request lifetime. See ClearHandler().
|
||||
func Clear(r *http.Request) {
|
||||
mutex.Lock()
|
||||
clear(r)
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
// clear is Clear without the lock.
|
||||
func clear(r *http.Request) {
|
||||
delete(data, r)
|
||||
delete(datat, r)
|
||||
}
|
||||
|
||||
// Purge removes request data stored for longer than maxAge, in seconds.
|
||||
// It returns the amount of requests removed.
|
||||
//
|
||||
// If maxAge <= 0, all request data is removed.
|
||||
//
|
||||
// This is only used for sanity check: in case context cleaning was not
|
||||
// properly set some request data can be kept forever, consuming an increasing
|
||||
// amount of memory. In case this is detected, Purge() must be called
|
||||
// periodically until the problem is fixed.
|
||||
func Purge(maxAge int) int {
|
||||
mutex.Lock()
|
||||
count := 0
|
||||
if maxAge <= 0 {
|
||||
count = len(data)
|
||||
data = make(map[*http.Request]map[interface{}]interface{})
|
||||
datat = make(map[*http.Request]int64)
|
||||
} else {
|
||||
min := time.Now().Unix() - int64(maxAge)
|
||||
for r := range data {
|
||||
if datat[r] < min {
|
||||
clear(r)
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
mutex.Unlock()
|
||||
return count
|
||||
}
|
||||
|
||||
// ClearHandler wraps an http.Handler and clears request values at the end
|
||||
// of a request lifetime.
|
||||
func ClearHandler(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer Clear(r)
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
161
pkg/api/Godeps/_workspace/src/github.com/gorilla/context/context_test.go
generated
vendored
Normal file
161
pkg/api/Godeps/_workspace/src/github.com/gorilla/context/context_test.go
generated
vendored
Normal file
|
@ -0,0 +1,161 @@
|
|||
// Copyright 2012 The Gorilla 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 context
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type keyType int
|
||||
|
||||
const (
|
||||
key1 keyType = iota
|
||||
key2
|
||||
)
|
||||
|
||||
func TestContext(t *testing.T) {
|
||||
assertEqual := func(val interface{}, exp interface{}) {
|
||||
if val != exp {
|
||||
t.Errorf("Expected %v, got %v.", exp, val)
|
||||
}
|
||||
}
|
||||
|
||||
r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||
emptyR, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||
|
||||
// Get()
|
||||
assertEqual(Get(r, key1), nil)
|
||||
|
||||
// Set()
|
||||
Set(r, key1, "1")
|
||||
assertEqual(Get(r, key1), "1")
|
||||
assertEqual(len(data[r]), 1)
|
||||
|
||||
Set(r, key2, "2")
|
||||
assertEqual(Get(r, key2), "2")
|
||||
assertEqual(len(data[r]), 2)
|
||||
|
||||
//GetOk
|
||||
value, ok := GetOk(r, key1)
|
||||
assertEqual(value, "1")
|
||||
assertEqual(ok, true)
|
||||
|
||||
value, ok = GetOk(r, "not exists")
|
||||
assertEqual(value, nil)
|
||||
assertEqual(ok, false)
|
||||
|
||||
Set(r, "nil value", nil)
|
||||
value, ok = GetOk(r, "nil value")
|
||||
assertEqual(value, nil)
|
||||
assertEqual(ok, true)
|
||||
|
||||
// GetAll()
|
||||
values := GetAll(r)
|
||||
assertEqual(len(values), 3)
|
||||
|
||||
// GetAll() for empty request
|
||||
values = GetAll(emptyR)
|
||||
if values != nil {
|
||||
t.Error("GetAll didn't return nil value for invalid request")
|
||||
}
|
||||
|
||||
// GetAllOk()
|
||||
values, ok = GetAllOk(r)
|
||||
assertEqual(len(values), 3)
|
||||
assertEqual(ok, true)
|
||||
|
||||
// GetAllOk() for empty request
|
||||
values, ok = GetAllOk(emptyR)
|
||||
assertEqual(value, nil)
|
||||
assertEqual(ok, false)
|
||||
|
||||
// Delete()
|
||||
Delete(r, key1)
|
||||
assertEqual(Get(r, key1), nil)
|
||||
assertEqual(len(data[r]), 2)
|
||||
|
||||
Delete(r, key2)
|
||||
assertEqual(Get(r, key2), nil)
|
||||
assertEqual(len(data[r]), 1)
|
||||
|
||||
// Clear()
|
||||
Clear(r)
|
||||
assertEqual(len(data), 0)
|
||||
}
|
||||
|
||||
func parallelReader(r *http.Request, key string, iterations int, wait, done chan struct{}) {
|
||||
<-wait
|
||||
for i := 0; i < iterations; i++ {
|
||||
Get(r, key)
|
||||
}
|
||||
done <- struct{}{}
|
||||
|
||||
}
|
||||
|
||||
func parallelWriter(r *http.Request, key, value string, iterations int, wait, done chan struct{}) {
|
||||
<-wait
|
||||
for i := 0; i < iterations; i++ {
|
||||
Set(r, key, value)
|
||||
}
|
||||
done <- struct{}{}
|
||||
|
||||
}
|
||||
|
||||
func benchmarkMutex(b *testing.B, numReaders, numWriters, iterations int) {
|
||||
|
||||
b.StopTimer()
|
||||
r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||
done := make(chan struct{})
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
wait := make(chan struct{})
|
||||
|
||||
for i := 0; i < numReaders; i++ {
|
||||
go parallelReader(r, "test", iterations, wait, done)
|
||||
}
|
||||
|
||||
for i := 0; i < numWriters; i++ {
|
||||
go parallelWriter(r, "test", "123", iterations, wait, done)
|
||||
}
|
||||
|
||||
close(wait)
|
||||
|
||||
for i := 0; i < numReaders+numWriters; i++ {
|
||||
<-done
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkMutexSameReadWrite1(b *testing.B) {
|
||||
benchmarkMutex(b, 1, 1, 32)
|
||||
}
|
||||
func BenchmarkMutexSameReadWrite2(b *testing.B) {
|
||||
benchmarkMutex(b, 2, 2, 32)
|
||||
}
|
||||
func BenchmarkMutexSameReadWrite4(b *testing.B) {
|
||||
benchmarkMutex(b, 4, 4, 32)
|
||||
}
|
||||
func BenchmarkMutex1(b *testing.B) {
|
||||
benchmarkMutex(b, 2, 8, 32)
|
||||
}
|
||||
func BenchmarkMutex2(b *testing.B) {
|
||||
benchmarkMutex(b, 16, 4, 64)
|
||||
}
|
||||
func BenchmarkMutex3(b *testing.B) {
|
||||
benchmarkMutex(b, 1, 2, 128)
|
||||
}
|
||||
func BenchmarkMutex4(b *testing.B) {
|
||||
benchmarkMutex(b, 128, 32, 256)
|
||||
}
|
||||
func BenchmarkMutex5(b *testing.B) {
|
||||
benchmarkMutex(b, 1024, 2048, 64)
|
||||
}
|
||||
func BenchmarkMutex6(b *testing.B) {
|
||||
benchmarkMutex(b, 2048, 1024, 512)
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
// Copyright 2012 The Gorilla 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 context stores values shared during a request lifetime.
|
||||
|
||||
For example, a router can set variables extracted from the URL and later
|
||||
application handlers can access those values, or it can be used to store
|
||||
sessions values to be saved at the end of a request. There are several
|
||||
others common uses.
|
||||
|
||||
The idea was posted by Brad Fitzpatrick to the go-nuts mailing list:
|
||||
|
||||
http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53
|
||||
|
||||
Here's the basic usage: first define the keys that you will need. The key
|
||||
type is interface{} so a key can be of any type that supports equality.
|
||||
Here we define a key using a custom int type to avoid name collisions:
|
||||
|
||||
package foo
|
||||
|
||||
import (
|
||||
"github.com/gorilla/context"
|
||||
)
|
||||
|
||||
type key int
|
||||
|
||||
const MyKey key = 0
|
||||
|
||||
Then set a variable. Variables are bound to an http.Request object, so you
|
||||
need a request instance to set a value:
|
||||
|
||||
context.Set(r, MyKey, "bar")
|
||||
|
||||
The application can later access the variable using the same key you provided:
|
||||
|
||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// val is "bar".
|
||||
val := context.Get(r, foo.MyKey)
|
||||
|
||||
// returns ("bar", true)
|
||||
val, ok := context.GetOk(r, foo.MyKey)
|
||||
// ...
|
||||
}
|
||||
|
||||
And that's all about the basic usage. We discuss some other ideas below.
|
||||
|
||||
Any type can be stored in the context. To enforce a given type, make the key
|
||||
private and wrap Get() and Set() to accept and return values of a specific
|
||||
type:
|
||||
|
||||
type key int
|
||||
|
||||
const mykey key = 0
|
||||
|
||||
// GetMyKey returns a value for this package from the request values.
|
||||
func GetMyKey(r *http.Request) SomeType {
|
||||
if rv := context.Get(r, mykey); rv != nil {
|
||||
return rv.(SomeType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetMyKey sets a value for this package in the request values.
|
||||
func SetMyKey(r *http.Request, val SomeType) {
|
||||
context.Set(r, mykey, val)
|
||||
}
|
||||
|
||||
Variables must be cleared at the end of a request, to remove all values
|
||||
that were stored. This can be done in an http.Handler, after a request was
|
||||
served. Just call Clear() passing the request:
|
||||
|
||||
context.Clear(r)
|
||||
|
||||
...or use ClearHandler(), which conveniently wraps an http.Handler to clear
|
||||
variables at the end of a request lifetime.
|
||||
|
||||
The Routers from the packages gorilla/mux and gorilla/pat call Clear()
|
||||
so if you are using either of them you don't need to clear the context manually.
|
||||
*/
|
||||
package context
|
|
@ -0,0 +1,7 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.0
|
||||
- 1.1
|
||||
- 1.2
|
||||
- tip
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,7 @@
|
|||
mux
|
||||
===
|
||||
[![Build Status](https://travis-ci.org/gorilla/mux.png?branch=master)](https://travis-ci.org/gorilla/mux)
|
||||
|
||||
gorilla/mux is a powerful URL router and dispatcher.
|
||||
|
||||
Read the full documentation here: http://www.gorillatoolkit.org/pkg/mux
|
21
pkg/api/Godeps/_workspace/src/github.com/gorilla/mux/bench_test.go
generated
vendored
Normal file
21
pkg/api/Godeps/_workspace/src/github.com/gorilla/mux/bench_test.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
// Copyright 2012 The Gorilla 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 mux
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkMux(b *testing.B) {
|
||||
router := new(Router)
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {}
|
||||
router.HandleFunc("/v1/{v1}", handler)
|
||||
|
||||
request, _ := http.NewRequest("GET", "/v1/anything", nil)
|
||||
for i := 0; i < b.N; i++ {
|
||||
router.ServeHTTP(nil, request)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
// Copyright 2012 The Gorilla 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 gorilla/mux implements a request router and dispatcher.
|
||||
|
||||
The name mux stands for "HTTP request multiplexer". Like the standard
|
||||
http.ServeMux, mux.Router matches incoming requests against a list of
|
||||
registered routes and calls a handler for the route that matches the URL
|
||||
or other conditions. The main features are:
|
||||
|
||||
* Requests can be matched based on URL host, path, path prefix, schemes,
|
||||
header and query values, HTTP methods or using custom matchers.
|
||||
* URL hosts and paths can have variables with an optional regular
|
||||
expression.
|
||||
* Registered URLs can be built, or "reversed", which helps maintaining
|
||||
references to resources.
|
||||
* Routes can be used as subrouters: nested routes are only tested if the
|
||||
parent route matches. This is useful to define groups of routes that
|
||||
share common conditions like a host, a path prefix or other repeated
|
||||
attributes. As a bonus, this optimizes request matching.
|
||||
* It implements the http.Handler interface so it is compatible with the
|
||||
standard http.ServeMux.
|
||||
|
||||
Let's start registering a couple of URL paths and handlers:
|
||||
|
||||
func main() {
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/", HomeHandler)
|
||||
r.HandleFunc("/products", ProductsHandler)
|
||||
r.HandleFunc("/articles", ArticlesHandler)
|
||||
http.Handle("/", r)
|
||||
}
|
||||
|
||||
Here we register three routes mapping URL paths to handlers. This is
|
||||
equivalent to how http.HandleFunc() works: if an incoming request URL matches
|
||||
one of the paths, the corresponding handler is called passing
|
||||
(http.ResponseWriter, *http.Request) as parameters.
|
||||
|
||||
Paths can have variables. They are defined using the format {name} or
|
||||
{name:pattern}. If a regular expression pattern is not defined, the matched
|
||||
variable will be anything until the next slash. For example:
|
||||
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/products/{key}", ProductHandler)
|
||||
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
|
||||
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
|
||||
|
||||
The names are used to create a map of route variables which can be retrieved
|
||||
calling mux.Vars():
|
||||
|
||||
vars := mux.Vars(request)
|
||||
category := vars["category"]
|
||||
|
||||
And this is all you need to know about the basic usage. More advanced options
|
||||
are explained below.
|
||||
|
||||
Routes can also be restricted to a domain or subdomain. Just define a host
|
||||
pattern to be matched. They can also have variables:
|
||||
|
||||
r := mux.NewRouter()
|
||||
// Only matches if domain is "www.domain.com".
|
||||
r.Host("www.domain.com")
|
||||
// Matches a dynamic subdomain.
|
||||
r.Host("{subdomain:[a-z]+}.domain.com")
|
||||
|
||||
There are several other matchers that can be added. To match path prefixes:
|
||||
|
||||
r.PathPrefix("/products/")
|
||||
|
||||
...or HTTP methods:
|
||||
|
||||
r.Methods("GET", "POST")
|
||||
|
||||
...or URL schemes:
|
||||
|
||||
r.Schemes("https")
|
||||
|
||||
...or header values:
|
||||
|
||||
r.Headers("X-Requested-With", "XMLHttpRequest")
|
||||
|
||||
...or query values:
|
||||
|
||||
r.Queries("key", "value")
|
||||
|
||||
...or to use a custom matcher function:
|
||||
|
||||
r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
|
||||
return r.ProtoMajor == 0
|
||||
})
|
||||
|
||||
...and finally, it is possible to combine several matchers in a single route:
|
||||
|
||||
r.HandleFunc("/products", ProductsHandler).
|
||||
Host("www.domain.com").
|
||||
Methods("GET").
|
||||
Schemes("http")
|
||||
|
||||
Setting the same matching conditions again and again can be boring, so we have
|
||||
a way to group several routes that share the same requirements.
|
||||
We call it "subrouting".
|
||||
|
||||
For example, let's say we have several URLs that should only match when the
|
||||
host is "www.domain.com". Create a route for that host and get a "subrouter"
|
||||
from it:
|
||||
|
||||
r := mux.NewRouter()
|
||||
s := r.Host("www.domain.com").Subrouter()
|
||||
|
||||
Then register routes in the subrouter:
|
||||
|
||||
s.HandleFunc("/products/", ProductsHandler)
|
||||
s.HandleFunc("/products/{key}", ProductHandler)
|
||||
s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
|
||||
|
||||
The three URL paths we registered above will only be tested if the domain is
|
||||
"www.domain.com", because the subrouter is tested first. This is not
|
||||
only convenient, but also optimizes request matching. You can create
|
||||
subrouters combining any attribute matchers accepted by a route.
|
||||
|
||||
Subrouters can be used to create domain or path "namespaces": you define
|
||||
subrouters in a central place and then parts of the app can register its
|
||||
paths relatively to a given subrouter.
|
||||
|
||||
There's one more thing about subroutes. When a subrouter has a path prefix,
|
||||
the inner routes use it as base for their paths:
|
||||
|
||||
r := mux.NewRouter()
|
||||
s := r.PathPrefix("/products").Subrouter()
|
||||
// "/products/"
|
||||
s.HandleFunc("/", ProductsHandler)
|
||||
// "/products/{key}/"
|
||||
s.HandleFunc("/{key}/", ProductHandler)
|
||||
// "/products/{key}/details"
|
||||
s.HandleFunc("/{key}/details", ProductDetailsHandler)
|
||||
|
||||
Now let's see how to build registered URLs.
|
||||
|
||||
Routes can be named. All routes that define a name can have their URLs built,
|
||||
or "reversed". We define a name calling Name() on a route. For example:
|
||||
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
||||
Name("article")
|
||||
|
||||
To build a URL, get the route and call the URL() method, passing a sequence of
|
||||
key/value pairs for the route variables. For the previous route, we would do:
|
||||
|
||||
url, err := r.Get("article").URL("category", "technology", "id", "42")
|
||||
|
||||
...and the result will be a url.URL with the following path:
|
||||
|
||||
"/articles/technology/42"
|
||||
|
||||
This also works for host variables:
|
||||
|
||||
r := mux.NewRouter()
|
||||
r.Host("{subdomain}.domain.com").
|
||||
Path("/articles/{category}/{id:[0-9]+}").
|
||||
HandlerFunc(ArticleHandler).
|
||||
Name("article")
|
||||
|
||||
// url.String() will be "http://news.domain.com/articles/technology/42"
|
||||
url, err := r.Get("article").URL("subdomain", "news",
|
||||
"category", "technology",
|
||||
"id", "42")
|
||||
|
||||
All variables defined in the route are required, and their values must
|
||||
conform to the corresponding patterns. These requirements guarantee that a
|
||||
generated URL will always match a registered route -- the only exception is
|
||||
for explicitly defined "build-only" routes which never match.
|
||||
|
||||
There's also a way to build only the URL host or path for a route:
|
||||
use the methods URLHost() or URLPath() instead. For the previous route,
|
||||
we would do:
|
||||
|
||||
// "http://news.domain.com/"
|
||||
host, err := r.Get("article").URLHost("subdomain", "news")
|
||||
|
||||
// "/articles/technology/42"
|
||||
path, err := r.Get("article").URLPath("category", "technology", "id", "42")
|
||||
|
||||
And if you use subrouters, host and path defined separately can be built
|
||||
as well:
|
||||
|
||||
r := mux.NewRouter()
|
||||
s := r.Host("{subdomain}.domain.com").Subrouter()
|
||||
s.Path("/articles/{category}/{id:[0-9]+}").
|
||||
HandlerFunc(ArticleHandler).
|
||||
Name("article")
|
||||
|
||||
// "http://news.domain.com/articles/technology/42"
|
||||
url, err := r.Get("article").URL("subdomain", "news",
|
||||
"category", "technology",
|
||||
"id", "42")
|
||||
*/
|
||||
package mux
|
|
@ -0,0 +1,353 @@
|
|||
// Copyright 2012 The Gorilla 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 mux
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/gorilla/context"
|
||||
)
|
||||
|
||||
// NewRouter returns a new router instance.
|
||||
func NewRouter() *Router {
|
||||
return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}
|
||||
}
|
||||
|
||||
// Router registers routes to be matched and dispatches a handler.
|
||||
//
|
||||
// It implements the http.Handler interface, so it can be registered to serve
|
||||
// requests:
|
||||
//
|
||||
// var router = mux.NewRouter()
|
||||
//
|
||||
// func main() {
|
||||
// http.Handle("/", router)
|
||||
// }
|
||||
//
|
||||
// Or, for Google App Engine, register it in a init() function:
|
||||
//
|
||||
// func init() {
|
||||
// http.Handle("/", router)
|
||||
// }
|
||||
//
|
||||
// This will send all incoming requests to the router.
|
||||
type Router struct {
|
||||
// Configurable Handler to be used when no route matches.
|
||||
NotFoundHandler http.Handler
|
||||
// Parent route, if this is a subrouter.
|
||||
parent parentRoute
|
||||
// Routes to be matched, in order.
|
||||
routes []*Route
|
||||
// Routes by name for URL building.
|
||||
namedRoutes map[string]*Route
|
||||
// See Router.StrictSlash(). This defines the flag for new routes.
|
||||
strictSlash bool
|
||||
// If true, do not clear the request context after handling the request
|
||||
KeepContext bool
|
||||
}
|
||||
|
||||
// Match matches registered routes against the request.
|
||||
func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
|
||||
for _, route := range r.routes {
|
||||
if route.Match(req, match) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ServeHTTP dispatches the handler registered in the matched route.
|
||||
//
|
||||
// When there is a match, the route variables can be retrieved calling
|
||||
// mux.Vars(request).
|
||||
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Clean path to canonical form and redirect.
|
||||
if p := cleanPath(req.URL.Path); p != req.URL.Path {
|
||||
|
||||
// Added 3 lines (Philip Schlump) - It was droping the query string and #whatever from query.
|
||||
// This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue:
|
||||
// http://code.google.com/p/go/issues/detail?id=5252
|
||||
url := *req.URL
|
||||
url.Path = p
|
||||
p = url.String()
|
||||
|
||||
w.Header().Set("Location", p)
|
||||
w.WriteHeader(http.StatusMovedPermanently)
|
||||
return
|
||||
}
|
||||
var match RouteMatch
|
||||
var handler http.Handler
|
||||
if r.Match(req, &match) {
|
||||
handler = match.Handler
|
||||
setVars(req, match.Vars)
|
||||
setCurrentRoute(req, match.Route)
|
||||
}
|
||||
if handler == nil {
|
||||
handler = r.NotFoundHandler
|
||||
if handler == nil {
|
||||
handler = http.NotFoundHandler()
|
||||
}
|
||||
}
|
||||
if !r.KeepContext {
|
||||
defer context.Clear(req)
|
||||
}
|
||||
handler.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
// Get returns a route registered with the given name.
|
||||
func (r *Router) Get(name string) *Route {
|
||||
return r.getNamedRoutes()[name]
|
||||
}
|
||||
|
||||
// GetRoute returns a route registered with the given name. This method
|
||||
// was renamed to Get() and remains here for backwards compatibility.
|
||||
func (r *Router) GetRoute(name string) *Route {
|
||||
return r.getNamedRoutes()[name]
|
||||
}
|
||||
|
||||
// StrictSlash defines the trailing slash behavior for new routes. The initial
|
||||
// value is false.
|
||||
//
|
||||
// When true, if the route path is "/path/", accessing "/path" will redirect
|
||||
// to the former and vice versa. In other words, your application will always
|
||||
// see the path as specified in the route.
|
||||
//
|
||||
// When false, if the route path is "/path", accessing "/path/" will not match
|
||||
// this route and vice versa.
|
||||
//
|
||||
// Special case: when a route sets a path prefix using the PathPrefix() method,
|
||||
// strict slash is ignored for that route because the redirect behavior can't
|
||||
// be determined from a prefix alone. However, any subrouters created from that
|
||||
// route inherit the original StrictSlash setting.
|
||||
func (r *Router) StrictSlash(value bool) *Router {
|
||||
r.strictSlash = value
|
||||
return r
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// parentRoute
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// getNamedRoutes returns the map where named routes are registered.
|
||||
func (r *Router) getNamedRoutes() map[string]*Route {
|
||||
if r.namedRoutes == nil {
|
||||
if r.parent != nil {
|
||||
r.namedRoutes = r.parent.getNamedRoutes()
|
||||
} else {
|
||||
r.namedRoutes = make(map[string]*Route)
|
||||
}
|
||||
}
|
||||
return r.namedRoutes
|
||||
}
|
||||
|
||||
// getRegexpGroup returns regexp definitions from the parent route, if any.
|
||||
func (r *Router) getRegexpGroup() *routeRegexpGroup {
|
||||
if r.parent != nil {
|
||||
return r.parent.getRegexpGroup()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Route factories
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// NewRoute registers an empty route.
|
||||
func (r *Router) NewRoute() *Route {
|
||||
route := &Route{parent: r, strictSlash: r.strictSlash}
|
||||
r.routes = append(r.routes, route)
|
||||
return route
|
||||
}
|
||||
|
||||
// Handle registers a new route with a matcher for the URL path.
|
||||
// See Route.Path() and Route.Handler().
|
||||
func (r *Router) Handle(path string, handler http.Handler) *Route {
|
||||
return r.NewRoute().Path(path).Handler(handler)
|
||||
}
|
||||
|
||||
// HandleFunc registers a new route with a matcher for the URL path.
|
||||
// See Route.Path() and Route.HandlerFunc().
|
||||
func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
|
||||
*http.Request)) *Route {
|
||||
return r.NewRoute().Path(path).HandlerFunc(f)
|
||||
}
|
||||
|
||||
// Headers registers a new route with a matcher for request header values.
|
||||
// See Route.Headers().
|
||||
func (r *Router) Headers(pairs ...string) *Route {
|
||||
return r.NewRoute().Headers(pairs...)
|
||||
}
|
||||
|
||||
// Host registers a new route with a matcher for the URL host.
|
||||
// See Route.Host().
|
||||
func (r *Router) Host(tpl string) *Route {
|
||||
return r.NewRoute().Host(tpl)
|
||||
}
|
||||
|
||||
// MatcherFunc registers a new route with a custom matcher function.
|
||||
// See Route.MatcherFunc().
|
||||
func (r *Router) MatcherFunc(f MatcherFunc) *Route {
|
||||
return r.NewRoute().MatcherFunc(f)
|
||||
}
|
||||
|
||||
// Methods registers a new route with a matcher for HTTP methods.
|
||||
// See Route.Methods().
|
||||
func (r *Router) Methods(methods ...string) *Route {
|
||||
return r.NewRoute().Methods(methods...)
|
||||
}
|
||||
|
||||
// Path registers a new route with a matcher for the URL path.
|
||||
// See Route.Path().
|
||||
func (r *Router) Path(tpl string) *Route {
|
||||
return r.NewRoute().Path(tpl)
|
||||
}
|
||||
|
||||
// PathPrefix registers a new route with a matcher for the URL path prefix.
|
||||
// See Route.PathPrefix().
|
||||
func (r *Router) PathPrefix(tpl string) *Route {
|
||||
return r.NewRoute().PathPrefix(tpl)
|
||||
}
|
||||
|
||||
// Queries registers a new route with a matcher for URL query values.
|
||||
// See Route.Queries().
|
||||
func (r *Router) Queries(pairs ...string) *Route {
|
||||
return r.NewRoute().Queries(pairs...)
|
||||
}
|
||||
|
||||
// Schemes registers a new route with a matcher for URL schemes.
|
||||
// See Route.Schemes().
|
||||
func (r *Router) Schemes(schemes ...string) *Route {
|
||||
return r.NewRoute().Schemes(schemes...)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Context
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// RouteMatch stores information about a matched route.
|
||||
type RouteMatch struct {
|
||||
Route *Route
|
||||
Handler http.Handler
|
||||
Vars map[string]string
|
||||
}
|
||||
|
||||
type contextKey int
|
||||
|
||||
const (
|
||||
varsKey contextKey = iota
|
||||
routeKey
|
||||
)
|
||||
|
||||
// Vars returns the route variables for the current request, if any.
|
||||
func Vars(r *http.Request) map[string]string {
|
||||
if rv := context.Get(r, varsKey); rv != nil {
|
||||
return rv.(map[string]string)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CurrentRoute returns the matched route for the current request, if any.
|
||||
func CurrentRoute(r *http.Request) *Route {
|
||||
if rv := context.Get(r, routeKey); rv != nil {
|
||||
return rv.(*Route)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setVars(r *http.Request, val interface{}) {
|
||||
context.Set(r, varsKey, val)
|
||||
}
|
||||
|
||||
func setCurrentRoute(r *http.Request, val interface{}) {
|
||||
context.Set(r, routeKey, val)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// cleanPath returns the canonical path for p, eliminating . and .. elements.
|
||||
// Borrowed from the net/http package.
|
||||
func cleanPath(p string) string {
|
||||
if p == "" {
|
||||
return "/"
|
||||
}
|
||||
if p[0] != '/' {
|
||||
p = "/" + p
|
||||
}
|
||||
np := path.Clean(p)
|
||||
// path.Clean removes trailing slash except for root;
|
||||
// put the trailing slash back if necessary.
|
||||
if p[len(p)-1] == '/' && np != "/" {
|
||||
np += "/"
|
||||
}
|
||||
return np
|
||||
}
|
||||
|
||||
// uniqueVars returns an error if two slices contain duplicated strings.
|
||||
func uniqueVars(s1, s2 []string) error {
|
||||
for _, v1 := range s1 {
|
||||
for _, v2 := range s2 {
|
||||
if v1 == v2 {
|
||||
return fmt.Errorf("mux: duplicated route variable %q", v2)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mapFromPairs converts variadic string parameters to a string map.
|
||||
func mapFromPairs(pairs ...string) (map[string]string, error) {
|
||||
length := len(pairs)
|
||||
if length%2 != 0 {
|
||||
return nil, fmt.Errorf(
|
||||
"mux: number of parameters must be multiple of 2, got %v", pairs)
|
||||
}
|
||||
m := make(map[string]string, length/2)
|
||||
for i := 0; i < length; i += 2 {
|
||||
m[pairs[i]] = pairs[i+1]
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// matchInArray returns true if the given string value is in the array.
|
||||
func matchInArray(arr []string, value string) bool {
|
||||
for _, v := range arr {
|
||||
if v == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// matchMap returns true if the given key/value pairs exist in a given map.
|
||||
func matchMap(toCheck map[string]string, toMatch map[string][]string,
|
||||
canonicalKey bool) bool {
|
||||
for k, v := range toCheck {
|
||||
// Check if key exists.
|
||||
if canonicalKey {
|
||||
k = http.CanonicalHeaderKey(k)
|
||||
}
|
||||
if values := toMatch[k]; values == nil {
|
||||
return false
|
||||
} else if v != "" {
|
||||
// If value was defined as an empty string we only check that the
|
||||
// key exists. Otherwise we also check for equality.
|
||||
valueExists := false
|
||||
for _, value := range values {
|
||||
if v == value {
|
||||
valueExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !valueExists {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,943 @@
|
|||
// Copyright 2012 The Gorilla 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 mux
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/context"
|
||||
)
|
||||
|
||||
type routeTest struct {
|
||||
title string // title of the test
|
||||
route *Route // the route being tested
|
||||
request *http.Request // a request to test the route
|
||||
vars map[string]string // the expected vars of the match
|
||||
host string // the expected host of the match
|
||||
path string // the expected path of the match
|
||||
shouldMatch bool // whether the request is expected to match the route at all
|
||||
shouldRedirect bool // whether the request should result in a redirect
|
||||
}
|
||||
|
||||
func TestHost(t *testing.T) {
|
||||
// newRequestHost a new request with a method, url, and host header
|
||||
newRequestHost := func(method, url, host string) *http.Request {
|
||||
req, err := http.NewRequest(method, url, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
req.Host = host
|
||||
return req
|
||||
}
|
||||
|
||||
tests := []routeTest{
|
||||
{
|
||||
title: "Host route match",
|
||||
route: new(Route).Host("aaa.bbb.ccc"),
|
||||
request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
|
||||
vars: map[string]string{},
|
||||
host: "aaa.bbb.ccc",
|
||||
path: "",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Host route, wrong host in request URL",
|
||||
route: new(Route).Host("aaa.bbb.ccc"),
|
||||
request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
|
||||
vars: map[string]string{},
|
||||
host: "aaa.bbb.ccc",
|
||||
path: "",
|
||||
shouldMatch: false,
|
||||
},
|
||||
{
|
||||
title: "Host route with port, match",
|
||||
route: new(Route).Host("aaa.bbb.ccc:1234"),
|
||||
request: newRequest("GET", "http://aaa.bbb.ccc:1234/111/222/333"),
|
||||
vars: map[string]string{},
|
||||
host: "aaa.bbb.ccc:1234",
|
||||
path: "",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Host route with port, wrong port in request URL",
|
||||
route: new(Route).Host("aaa.bbb.ccc:1234"),
|
||||
request: newRequest("GET", "http://aaa.bbb.ccc:9999/111/222/333"),
|
||||
vars: map[string]string{},
|
||||
host: "aaa.bbb.ccc:1234",
|
||||
path: "",
|
||||
shouldMatch: false,
|
||||
},
|
||||
{
|
||||
title: "Host route, match with host in request header",
|
||||
route: new(Route).Host("aaa.bbb.ccc"),
|
||||
request: newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc"),
|
||||
vars: map[string]string{},
|
||||
host: "aaa.bbb.ccc",
|
||||
path: "",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Host route, wrong host in request header",
|
||||
route: new(Route).Host("aaa.bbb.ccc"),
|
||||
request: newRequestHost("GET", "/111/222/333", "aaa.222.ccc"),
|
||||
vars: map[string]string{},
|
||||
host: "aaa.bbb.ccc",
|
||||
path: "",
|
||||
shouldMatch: false,
|
||||
},
|
||||
// BUG {new(Route).Host("aaa.bbb.ccc:1234"), newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:1234"), map[string]string{}, "aaa.bbb.ccc:1234", "", true},
|
||||
{
|
||||
title: "Host route with port, wrong host in request header",
|
||||
route: new(Route).Host("aaa.bbb.ccc:1234"),
|
||||
request: newRequestHost("GET", "/111/222/333", "aaa.bbb.ccc:9999"),
|
||||
vars: map[string]string{},
|
||||
host: "aaa.bbb.ccc:1234",
|
||||
path: "",
|
||||
shouldMatch: false,
|
||||
},
|
||||
{
|
||||
title: "Host route with pattern, match",
|
||||
route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
|
||||
request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
|
||||
vars: map[string]string{"v1": "bbb"},
|
||||
host: "aaa.bbb.ccc",
|
||||
path: "",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Host route with pattern, wrong host in request URL",
|
||||
route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc"),
|
||||
request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
|
||||
vars: map[string]string{"v1": "bbb"},
|
||||
host: "aaa.bbb.ccc",
|
||||
path: "",
|
||||
shouldMatch: false,
|
||||
},
|
||||
{
|
||||
title: "Host route with multiple patterns, match",
|
||||
route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"),
|
||||
request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
|
||||
vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"},
|
||||
host: "aaa.bbb.ccc",
|
||||
path: "",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Host route with multiple patterns, wrong host in request URL",
|
||||
route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}"),
|
||||
request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
|
||||
vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc"},
|
||||
host: "aaa.bbb.ccc",
|
||||
path: "",
|
||||
shouldMatch: false,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
testRoute(t, test)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPath(t *testing.T) {
|
||||
tests := []routeTest{
|
||||
{
|
||||
title: "Path route, match",
|
||||
route: new(Route).Path("/111/222/333"),
|
||||
request: newRequest("GET", "http://localhost/111/222/333"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "/111/222/333",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Path route, match with trailing slash in request and path",
|
||||
route: new(Route).Path("/111/"),
|
||||
request: newRequest("GET", "http://localhost/111/"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "/111/",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Path route, do not match with trailing slash in path",
|
||||
route: new(Route).Path("/111/"),
|
||||
request: newRequest("GET", "http://localhost/111"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "/111",
|
||||
shouldMatch: false,
|
||||
},
|
||||
{
|
||||
title: "Path route, do not match with trailing slash in request",
|
||||
route: new(Route).Path("/111"),
|
||||
request: newRequest("GET", "http://localhost/111/"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "/111/",
|
||||
shouldMatch: false,
|
||||
},
|
||||
{
|
||||
title: "Path route, wrong path in request in request URL",
|
||||
route: new(Route).Path("/111/222/333"),
|
||||
request: newRequest("GET", "http://localhost/1/2/3"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "/111/222/333",
|
||||
shouldMatch: false,
|
||||
},
|
||||
{
|
||||
title: "Path route with pattern, match",
|
||||
route: new(Route).Path("/111/{v1:[0-9]{3}}/333"),
|
||||
request: newRequest("GET", "http://localhost/111/222/333"),
|
||||
vars: map[string]string{"v1": "222"},
|
||||
host: "",
|
||||
path: "/111/222/333",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Path route with pattern, URL in request does not match",
|
||||
route: new(Route).Path("/111/{v1:[0-9]{3}}/333"),
|
||||
request: newRequest("GET", "http://localhost/111/aaa/333"),
|
||||
vars: map[string]string{"v1": "222"},
|
||||
host: "",
|
||||
path: "/111/222/333",
|
||||
shouldMatch: false,
|
||||
},
|
||||
{
|
||||
title: "Path route with multiple patterns, match",
|
||||
route: new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
|
||||
request: newRequest("GET", "http://localhost/111/222/333"),
|
||||
vars: map[string]string{"v1": "111", "v2": "222", "v3": "333"},
|
||||
host: "",
|
||||
path: "/111/222/333",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Path route with multiple patterns, URL in request does not match",
|
||||
route: new(Route).Path("/{v1:[0-9]{3}}/{v2:[0-9]{3}}/{v3:[0-9]{3}}"),
|
||||
request: newRequest("GET", "http://localhost/111/aaa/333"),
|
||||
vars: map[string]string{"v1": "111", "v2": "222", "v3": "333"},
|
||||
host: "",
|
||||
path: "/111/222/333",
|
||||
shouldMatch: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
testRoute(t, test)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathPrefix(t *testing.T) {
|
||||
tests := []routeTest{
|
||||
{
|
||||
title: "PathPrefix route, match",
|
||||
route: new(Route).PathPrefix("/111"),
|
||||
request: newRequest("GET", "http://localhost/111/222/333"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "/111",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "PathPrefix route, match substring",
|
||||
route: new(Route).PathPrefix("/1"),
|
||||
request: newRequest("GET", "http://localhost/111/222/333"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "/1",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "PathPrefix route, URL prefix in request does not match",
|
||||
route: new(Route).PathPrefix("/111"),
|
||||
request: newRequest("GET", "http://localhost/1/2/3"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "/111",
|
||||
shouldMatch: false,
|
||||
},
|
||||
{
|
||||
title: "PathPrefix route with pattern, match",
|
||||
route: new(Route).PathPrefix("/111/{v1:[0-9]{3}}"),
|
||||
request: newRequest("GET", "http://localhost/111/222/333"),
|
||||
vars: map[string]string{"v1": "222"},
|
||||
host: "",
|
||||
path: "/111/222",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "PathPrefix route with pattern, URL prefix in request does not match",
|
||||
route: new(Route).PathPrefix("/111/{v1:[0-9]{3}}"),
|
||||
request: newRequest("GET", "http://localhost/111/aaa/333"),
|
||||
vars: map[string]string{"v1": "222"},
|
||||
host: "",
|
||||
path: "/111/222",
|
||||
shouldMatch: false,
|
||||
},
|
||||
{
|
||||
title: "PathPrefix route with multiple patterns, match",
|
||||
route: new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"),
|
||||
request: newRequest("GET", "http://localhost/111/222/333"),
|
||||
vars: map[string]string{"v1": "111", "v2": "222"},
|
||||
host: "",
|
||||
path: "/111/222",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "PathPrefix route with multiple patterns, URL prefix in request does not match",
|
||||
route: new(Route).PathPrefix("/{v1:[0-9]{3}}/{v2:[0-9]{3}}"),
|
||||
request: newRequest("GET", "http://localhost/111/aaa/333"),
|
||||
vars: map[string]string{"v1": "111", "v2": "222"},
|
||||
host: "",
|
||||
path: "/111/222",
|
||||
shouldMatch: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
testRoute(t, test)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHostPath(t *testing.T) {
|
||||
tests := []routeTest{
|
||||
{
|
||||
title: "Host and Path route, match",
|
||||
route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
|
||||
request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Host and Path route, wrong host in request URL",
|
||||
route: new(Route).Host("aaa.bbb.ccc").Path("/111/222/333"),
|
||||
request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
shouldMatch: false,
|
||||
},
|
||||
{
|
||||
title: "Host and Path route with pattern, match",
|
||||
route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
|
||||
request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
|
||||
vars: map[string]string{"v1": "bbb", "v2": "222"},
|
||||
host: "aaa.bbb.ccc",
|
||||
path: "/111/222/333",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Host and Path route with pattern, URL in request does not match",
|
||||
route: new(Route).Host("aaa.{v1:[a-z]{3}}.ccc").Path("/111/{v2:[0-9]{3}}/333"),
|
||||
request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
|
||||
vars: map[string]string{"v1": "bbb", "v2": "222"},
|
||||
host: "aaa.bbb.ccc",
|
||||
path: "/111/222/333",
|
||||
shouldMatch: false,
|
||||
},
|
||||
{
|
||||
title: "Host and Path route with multiple patterns, match",
|
||||
route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"),
|
||||
request: newRequest("GET", "http://aaa.bbb.ccc/111/222/333"),
|
||||
vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
|
||||
host: "aaa.bbb.ccc",
|
||||
path: "/111/222/333",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Host and Path route with multiple patterns, URL in request does not match",
|
||||
route: new(Route).Host("{v1:[a-z]{3}}.{v2:[a-z]{3}}.{v3:[a-z]{3}}").Path("/{v4:[0-9]{3}}/{v5:[0-9]{3}}/{v6:[0-9]{3}}"),
|
||||
request: newRequest("GET", "http://aaa.222.ccc/111/222/333"),
|
||||
vars: map[string]string{"v1": "aaa", "v2": "bbb", "v3": "ccc", "v4": "111", "v5": "222", "v6": "333"},
|
||||
host: "aaa.bbb.ccc",
|
||||
path: "/111/222/333",
|
||||
shouldMatch: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
testRoute(t, test)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeaders(t *testing.T) {
|
||||
// newRequestHeaders creates a new request with a method, url, and headers
|
||||
newRequestHeaders := func(method, url string, headers map[string]string) *http.Request {
|
||||
req, err := http.NewRequest(method, url, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for k, v := range headers {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
return req
|
||||
}
|
||||
|
||||
tests := []routeTest{
|
||||
{
|
||||
title: "Headers route, match",
|
||||
route: new(Route).Headers("foo", "bar", "baz", "ding"),
|
||||
request: newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "ding"}),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Headers route, bad header values",
|
||||
route: new(Route).Headers("foo", "bar", "baz", "ding"),
|
||||
request: newRequestHeaders("GET", "http://localhost", map[string]string{"foo": "bar", "baz": "dong"}),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
shouldMatch: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
testRoute(t, test)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMethods(t *testing.T) {
|
||||
tests := []routeTest{
|
||||
{
|
||||
title: "Methods route, match GET",
|
||||
route: new(Route).Methods("GET", "POST"),
|
||||
request: newRequest("GET", "http://localhost"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Methods route, match POST",
|
||||
route: new(Route).Methods("GET", "POST"),
|
||||
request: newRequest("POST", "http://localhost"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Methods route, bad method",
|
||||
route: new(Route).Methods("GET", "POST"),
|
||||
request: newRequest("PUT", "http://localhost"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
shouldMatch: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
testRoute(t, test)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueries(t *testing.T) {
|
||||
tests := []routeTest{
|
||||
{
|
||||
title: "Queries route, match",
|
||||
route: new(Route).Queries("foo", "bar", "baz", "ding"),
|
||||
request: newRequest("GET", "http://localhost?foo=bar&baz=ding"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Queries route, match with a query string",
|
||||
route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"),
|
||||
request: newRequest("GET", "http://www.example.com/api?foo=bar&baz=ding"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Queries route, match with a query string out of order",
|
||||
route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"),
|
||||
request: newRequest("GET", "http://www.example.com/api?baz=ding&foo=bar"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Queries route, bad query",
|
||||
route: new(Route).Queries("foo", "bar", "baz", "ding"),
|
||||
request: newRequest("GET", "http://localhost?foo=bar&baz=dong"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
shouldMatch: false,
|
||||
},
|
||||
{
|
||||
title: "Queries route with pattern, match",
|
||||
route: new(Route).Queries("foo", "{v1}"),
|
||||
request: newRequest("GET", "http://localhost?foo=bar"),
|
||||
vars: map[string]string{"v1": "bar"},
|
||||
host: "",
|
||||
path: "",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Queries route with multiple patterns, match",
|
||||
route: new(Route).Queries("foo", "{v1}", "baz", "{v2}"),
|
||||
request: newRequest("GET", "http://localhost?foo=bar&baz=ding"),
|
||||
vars: map[string]string{"v1": "bar", "v2": "ding"},
|
||||
host: "",
|
||||
path: "",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Queries route with regexp pattern, match",
|
||||
route: new(Route).Queries("foo", "{v1:[0-9]+}"),
|
||||
request: newRequest("GET", "http://localhost?foo=10"),
|
||||
vars: map[string]string{"v1": "10"},
|
||||
host: "",
|
||||
path: "",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Queries route with regexp pattern, regexp does not match",
|
||||
route: new(Route).Queries("foo", "{v1:[0-9]+}"),
|
||||
request: newRequest("GET", "http://localhost?foo=a"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
shouldMatch: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
testRoute(t, test)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSchemes(t *testing.T) {
|
||||
tests := []routeTest{
|
||||
// Schemes
|
||||
{
|
||||
title: "Schemes route, match https",
|
||||
route: new(Route).Schemes("https", "ftp"),
|
||||
request: newRequest("GET", "https://localhost"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Schemes route, match ftp",
|
||||
route: new(Route).Schemes("https", "ftp"),
|
||||
request: newRequest("GET", "ftp://localhost"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "Schemes route, bad scheme",
|
||||
route: new(Route).Schemes("https", "ftp"),
|
||||
request: newRequest("GET", "http://localhost"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
shouldMatch: false,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
testRoute(t, test)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatcherFunc(t *testing.T) {
|
||||
m := func(r *http.Request, m *RouteMatch) bool {
|
||||
if r.URL.Host == "aaa.bbb.ccc" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
tests := []routeTest{
|
||||
{
|
||||
title: "MatchFunc route, match",
|
||||
route: new(Route).MatcherFunc(m),
|
||||
request: newRequest("GET", "http://aaa.bbb.ccc"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
title: "MatchFunc route, non-match",
|
||||
route: new(Route).MatcherFunc(m),
|
||||
request: newRequest("GET", "http://aaa.222.ccc"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "",
|
||||
shouldMatch: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
testRoute(t, test)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubRouter(t *testing.T) {
|
||||
subrouter1 := new(Route).Host("{v1:[a-z]+}.google.com").Subrouter()
|
||||
subrouter2 := new(Route).PathPrefix("/foo/{v1}").Subrouter()
|
||||
|
||||
tests := []routeTest{
|
||||
{
|
||||
route: subrouter1.Path("/{v2:[a-z]+}"),
|
||||
request: newRequest("GET", "http://aaa.google.com/bbb"),
|
||||
vars: map[string]string{"v1": "aaa", "v2": "bbb"},
|
||||
host: "aaa.google.com",
|
||||
path: "/bbb",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
route: subrouter1.Path("/{v2:[a-z]+}"),
|
||||
request: newRequest("GET", "http://111.google.com/111"),
|
||||
vars: map[string]string{"v1": "aaa", "v2": "bbb"},
|
||||
host: "aaa.google.com",
|
||||
path: "/bbb",
|
||||
shouldMatch: false,
|
||||
},
|
||||
{
|
||||
route: subrouter2.Path("/baz/{v2}"),
|
||||
request: newRequest("GET", "http://localhost/foo/bar/baz/ding"),
|
||||
vars: map[string]string{"v1": "bar", "v2": "ding"},
|
||||
host: "",
|
||||
path: "/foo/bar/baz/ding",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
route: subrouter2.Path("/baz/{v2}"),
|
||||
request: newRequest("GET", "http://localhost/foo/bar"),
|
||||
vars: map[string]string{"v1": "bar", "v2": "ding"},
|
||||
host: "",
|
||||
path: "/foo/bar/baz/ding",
|
||||
shouldMatch: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
testRoute(t, test)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamedRoutes(t *testing.T) {
|
||||
r1 := NewRouter()
|
||||
r1.NewRoute().Name("a")
|
||||
r1.NewRoute().Name("b")
|
||||
r1.NewRoute().Name("c")
|
||||
|
||||
r2 := r1.NewRoute().Subrouter()
|
||||
r2.NewRoute().Name("d")
|
||||
r2.NewRoute().Name("e")
|
||||
r2.NewRoute().Name("f")
|
||||
|
||||
r3 := r2.NewRoute().Subrouter()
|
||||
r3.NewRoute().Name("g")
|
||||
r3.NewRoute().Name("h")
|
||||
r3.NewRoute().Name("i")
|
||||
|
||||
if r1.namedRoutes == nil || len(r1.namedRoutes) != 9 {
|
||||
t.Errorf("Expected 9 named routes, got %v", r1.namedRoutes)
|
||||
} else if r1.Get("i") == nil {
|
||||
t.Errorf("Subroute name not registered")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStrictSlash(t *testing.T) {
|
||||
r := NewRouter()
|
||||
r.StrictSlash(true)
|
||||
|
||||
tests := []routeTest{
|
||||
{
|
||||
title: "Redirect path without slash",
|
||||
route: r.NewRoute().Path("/111/"),
|
||||
request: newRequest("GET", "http://localhost/111"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "/111/",
|
||||
shouldMatch: true,
|
||||
shouldRedirect: true,
|
||||
},
|
||||
{
|
||||
title: "Do not redirect path with slash",
|
||||
route: r.NewRoute().Path("/111/"),
|
||||
request: newRequest("GET", "http://localhost/111/"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "/111/",
|
||||
shouldMatch: true,
|
||||
shouldRedirect: false,
|
||||
},
|
||||
{
|
||||
title: "Redirect path with slash",
|
||||
route: r.NewRoute().Path("/111"),
|
||||
request: newRequest("GET", "http://localhost/111/"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "/111",
|
||||
shouldMatch: true,
|
||||
shouldRedirect: true,
|
||||
},
|
||||
{
|
||||
title: "Do not redirect path without slash",
|
||||
route: r.NewRoute().Path("/111"),
|
||||
request: newRequest("GET", "http://localhost/111"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "/111",
|
||||
shouldMatch: true,
|
||||
shouldRedirect: false,
|
||||
},
|
||||
{
|
||||
title: "Propagate StrictSlash to subrouters",
|
||||
route: r.NewRoute().PathPrefix("/static/").Subrouter().Path("/images/"),
|
||||
request: newRequest("GET", "http://localhost/static/images"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "/static/images/",
|
||||
shouldMatch: true,
|
||||
shouldRedirect: true,
|
||||
},
|
||||
{
|
||||
title: "Ignore StrictSlash for path prefix",
|
||||
route: r.NewRoute().PathPrefix("/static/"),
|
||||
request: newRequest("GET", "http://localhost/static/logo.png"),
|
||||
vars: map[string]string{},
|
||||
host: "",
|
||||
path: "/static/",
|
||||
shouldMatch: true,
|
||||
shouldRedirect: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
testRoute(t, test)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func getRouteTemplate(route *Route) string {
|
||||
host, path := "none", "none"
|
||||
if route.regexp != nil {
|
||||
if route.regexp.host != nil {
|
||||
host = route.regexp.host.template
|
||||
}
|
||||
if route.regexp.path != nil {
|
||||
path = route.regexp.path.template
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("Host: %v, Path: %v", host, path)
|
||||
}
|
||||
|
||||
func testRoute(t *testing.T, test routeTest) {
|
||||
request := test.request
|
||||
route := test.route
|
||||
vars := test.vars
|
||||
shouldMatch := test.shouldMatch
|
||||
host := test.host
|
||||
path := test.path
|
||||
url := test.host + test.path
|
||||
shouldRedirect := test.shouldRedirect
|
||||
|
||||
var match RouteMatch
|
||||
ok := route.Match(request, &match)
|
||||
if ok != shouldMatch {
|
||||
msg := "Should match"
|
||||
if !shouldMatch {
|
||||
msg = "Should not match"
|
||||
}
|
||||
t.Errorf("(%v) %v:\nRoute: %#v\nRequest: %#v\nVars: %v\n", test.title, msg, route, request, vars)
|
||||
return
|
||||
}
|
||||
if shouldMatch {
|
||||
if test.vars != nil && !stringMapEqual(test.vars, match.Vars) {
|
||||
t.Errorf("(%v) Vars not equal: expected %v, got %v", test.title, vars, match.Vars)
|
||||
return
|
||||
}
|
||||
if host != "" {
|
||||
u, _ := test.route.URLHost(mapToPairs(match.Vars)...)
|
||||
if host != u.Host {
|
||||
t.Errorf("(%v) URLHost not equal: expected %v, got %v -- %v", test.title, host, u.Host, getRouteTemplate(route))
|
||||
return
|
||||
}
|
||||
}
|
||||
if path != "" {
|
||||
u, _ := route.URLPath(mapToPairs(match.Vars)...)
|
||||
if path != u.Path {
|
||||
t.Errorf("(%v) URLPath not equal: expected %v, got %v -- %v", test.title, path, u.Path, getRouteTemplate(route))
|
||||
return
|
||||
}
|
||||
}
|
||||
if url != "" {
|
||||
u, _ := route.URL(mapToPairs(match.Vars)...)
|
||||
if url != u.Host+u.Path {
|
||||
t.Errorf("(%v) URL not equal: expected %v, got %v -- %v", test.title, url, u.Host+u.Path, getRouteTemplate(route))
|
||||
return
|
||||
}
|
||||
}
|
||||
if shouldRedirect && match.Handler == nil {
|
||||
t.Errorf("(%v) Did not redirect", test.title)
|
||||
return
|
||||
}
|
||||
if !shouldRedirect && match.Handler != nil {
|
||||
t.Errorf("(%v) Unexpected redirect", test.title)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that the context is cleared or not cleared properly depending on
|
||||
// the configuration of the router
|
||||
func TestKeepContext(t *testing.T) {
|
||||
func1 := func(w http.ResponseWriter, r *http.Request) {}
|
||||
|
||||
r := NewRouter()
|
||||
r.HandleFunc("/", func1).Name("func1")
|
||||
|
||||
req, _ := http.NewRequest("GET", "http://localhost/", nil)
|
||||
context.Set(req, "t", 1)
|
||||
|
||||
res := new(http.ResponseWriter)
|
||||
r.ServeHTTP(*res, req)
|
||||
|
||||
if _, ok := context.GetOk(req, "t"); ok {
|
||||
t.Error("Context should have been cleared at end of request")
|
||||
}
|
||||
|
||||
r.KeepContext = true
|
||||
|
||||
req, _ = http.NewRequest("GET", "http://localhost/", nil)
|
||||
context.Set(req, "t", 1)
|
||||
|
||||
r.ServeHTTP(*res, req)
|
||||
if _, ok := context.GetOk(req, "t"); !ok {
|
||||
t.Error("Context should NOT have been cleared at end of request")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type TestA301ResponseWriter struct {
|
||||
hh http.Header
|
||||
status int
|
||||
}
|
||||
|
||||
func (ho TestA301ResponseWriter) Header() http.Header {
|
||||
return http.Header(ho.hh)
|
||||
}
|
||||
|
||||
func (ho TestA301ResponseWriter) Write(b []byte) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (ho TestA301ResponseWriter) WriteHeader(code int) {
|
||||
ho.status = code
|
||||
}
|
||||
|
||||
func Test301Redirect(t *testing.T) {
|
||||
m := make(http.Header)
|
||||
|
||||
func1 := func(w http.ResponseWriter, r *http.Request) {}
|
||||
func2 := func(w http.ResponseWriter, r *http.Request) {}
|
||||
|
||||
r := NewRouter()
|
||||
r.HandleFunc("/api/", func2).Name("func2")
|
||||
r.HandleFunc("/", func1).Name("func1")
|
||||
|
||||
req, _ := http.NewRequest("GET", "http://localhost//api/?abc=def", nil)
|
||||
|
||||
res := TestA301ResponseWriter{
|
||||
hh: m,
|
||||
status: 0,
|
||||
}
|
||||
r.ServeHTTP(&res, req)
|
||||
|
||||
if "http://localhost/api/?abc=def" != res.hh["Location"][0] {
|
||||
t.Errorf("Should have complete URL with query string")
|
||||
}
|
||||
}
|
||||
|
||||
// https://plus.google.com/101022900381697718949/posts/eWy6DjFJ6uW
|
||||
func TestSubrouterHeader(t *testing.T) {
|
||||
expected := "func1 response"
|
||||
func1 := func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, expected)
|
||||
}
|
||||
func2 := func(http.ResponseWriter, *http.Request) {}
|
||||
|
||||
r := NewRouter()
|
||||
s := r.Headers("SomeSpecialHeader", "").Subrouter()
|
||||
s.HandleFunc("/", func1).Name("func1")
|
||||
r.HandleFunc("/", func2).Name("func2")
|
||||
|
||||
req, _ := http.NewRequest("GET", "http://localhost/", nil)
|
||||
req.Header.Add("SomeSpecialHeader", "foo")
|
||||
match := new(RouteMatch)
|
||||
matched := r.Match(req, match)
|
||||
if !matched {
|
||||
t.Errorf("Should match request")
|
||||
}
|
||||
if match.Route.GetName() != "func1" {
|
||||
t.Errorf("Expecting func1 handler, got %s", match.Route.GetName())
|
||||
}
|
||||
resp := NewRecorder()
|
||||
match.Handler.ServeHTTP(resp, req)
|
||||
if resp.Body.String() != expected {
|
||||
t.Errorf("Expecting %q", expected)
|
||||
}
|
||||
}
|
||||
|
||||
// mapToPairs converts a string map to a slice of string pairs
|
||||
func mapToPairs(m map[string]string) []string {
|
||||
var i int
|
||||
p := make([]string, len(m)*2)
|
||||
for k, v := range m {
|
||||
p[i] = k
|
||||
p[i+1] = v
|
||||
i += 2
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// stringMapEqual checks the equality of two string maps
|
||||
func stringMapEqual(m1, m2 map[string]string) bool {
|
||||
nil1 := m1 == nil
|
||||
nil2 := m2 == nil
|
||||
if nil1 != nil2 || len(m1) != len(m2) {
|
||||
return false
|
||||
}
|
||||
for k, v := range m1 {
|
||||
if v != m2[k] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// newRequest is a helper function to create a new request with a method and url
|
||||
func newRequest(method, url string) *http.Request {
|
||||
req, err := http.NewRequest(method, url, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return req
|
||||
}
|
|
@ -0,0 +1,714 @@
|
|||
// Old tests ported to Go1. This is a mess. Want to drop it one day.
|
||||
|
||||
// Copyright 2011 Gorilla 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 mux
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// ResponseRecorder
|
||||
// ----------------------------------------------------------------------------
|
||||
// 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.
|
||||
|
||||
// ResponseRecorder is an implementation of http.ResponseWriter that
|
||||
// records its mutations for later inspection in tests.
|
||||
type ResponseRecorder struct {
|
||||
Code int // the HTTP response code from WriteHeader
|
||||
HeaderMap http.Header // the HTTP response headers
|
||||
Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
|
||||
Flushed bool
|
||||
}
|
||||
|
||||
// NewRecorder returns an initialized ResponseRecorder.
|
||||
func NewRecorder() *ResponseRecorder {
|
||||
return &ResponseRecorder{
|
||||
HeaderMap: make(http.Header),
|
||||
Body: new(bytes.Buffer),
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultRemoteAddr is the default remote address to return in RemoteAddr if
|
||||
// an explicit DefaultRemoteAddr isn't set on ResponseRecorder.
|
||||
const DefaultRemoteAddr = "1.2.3.4"
|
||||
|
||||
// Header returns the response headers.
|
||||
func (rw *ResponseRecorder) Header() http.Header {
|
||||
return rw.HeaderMap
|
||||
}
|
||||
|
||||
// Write always succeeds and writes to rw.Body, if not nil.
|
||||
func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
|
||||
if rw.Body != nil {
|
||||
rw.Body.Write(buf)
|
||||
}
|
||||
if rw.Code == 0 {
|
||||
rw.Code = http.StatusOK
|
||||
}
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
// WriteHeader sets rw.Code.
|
||||
func (rw *ResponseRecorder) WriteHeader(code int) {
|
||||
rw.Code = code
|
||||
}
|
||||
|
||||
// Flush sets rw.Flushed to true.
|
||||
func (rw *ResponseRecorder) Flush() {
|
||||
rw.Flushed = true
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func TestRouteMatchers(t *testing.T) {
|
||||
var scheme, host, path, query, method string
|
||||
var headers map[string]string
|
||||
var resultVars map[bool]map[string]string
|
||||
|
||||
router := NewRouter()
|
||||
router.NewRoute().Host("{var1}.google.com").
|
||||
Path("/{var2:[a-z]+}/{var3:[0-9]+}").
|
||||
Queries("foo", "bar").
|
||||
Methods("GET").
|
||||
Schemes("https").
|
||||
Headers("x-requested-with", "XMLHttpRequest")
|
||||
router.NewRoute().Host("www.{var4}.com").
|
||||
PathPrefix("/foo/{var5:[a-z]+}/{var6:[0-9]+}").
|
||||
Queries("baz", "ding").
|
||||
Methods("POST").
|
||||
Schemes("http").
|
||||
Headers("Content-Type", "application/json")
|
||||
|
||||
reset := func() {
|
||||
// Everything match.
|
||||
scheme = "https"
|
||||
host = "www.google.com"
|
||||
path = "/product/42"
|
||||
query = "?foo=bar"
|
||||
method = "GET"
|
||||
headers = map[string]string{"X-Requested-With": "XMLHttpRequest"}
|
||||
resultVars = map[bool]map[string]string{
|
||||
true: {"var1": "www", "var2": "product", "var3": "42"},
|
||||
false: {},
|
||||
}
|
||||
}
|
||||
|
||||
reset2 := func() {
|
||||
// Everything match.
|
||||
scheme = "http"
|
||||
host = "www.google.com"
|
||||
path = "/foo/product/42/path/that/is/ignored"
|
||||
query = "?baz=ding"
|
||||
method = "POST"
|
||||
headers = map[string]string{"Content-Type": "application/json"}
|
||||
resultVars = map[bool]map[string]string{
|
||||
true: {"var4": "google", "var5": "product", "var6": "42"},
|
||||
false: {},
|
||||
}
|
||||
}
|
||||
|
||||
match := func(shouldMatch bool) {
|
||||
url := scheme + "://" + host + path + query
|
||||
request, _ := http.NewRequest(method, url, nil)
|
||||
for key, value := range headers {
|
||||
request.Header.Add(key, value)
|
||||
}
|
||||
|
||||
var routeMatch RouteMatch
|
||||
matched := router.Match(request, &routeMatch)
|
||||
if matched != shouldMatch {
|
||||
// Need better messages. :)
|
||||
if matched {
|
||||
t.Errorf("Should match.")
|
||||
} else {
|
||||
t.Errorf("Should not match.")
|
||||
}
|
||||
}
|
||||
|
||||
if matched {
|
||||
currentRoute := routeMatch.Route
|
||||
if currentRoute == nil {
|
||||
t.Errorf("Expected a current route.")
|
||||
}
|
||||
vars := routeMatch.Vars
|
||||
expectedVars := resultVars[shouldMatch]
|
||||
if len(vars) != len(expectedVars) {
|
||||
t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars)
|
||||
}
|
||||
for name, value := range vars {
|
||||
if expectedVars[name] != value {
|
||||
t.Errorf("Expected vars: %v Got: %v.", expectedVars, vars)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1st route --------------------------------------------------------------
|
||||
|
||||
// Everything match.
|
||||
reset()
|
||||
match(true)
|
||||
|
||||
// Scheme doesn't match.
|
||||
reset()
|
||||
scheme = "http"
|
||||
match(false)
|
||||
|
||||
// Host doesn't match.
|
||||
reset()
|
||||
host = "www.mygoogle.com"
|
||||
match(false)
|
||||
|
||||
// Path doesn't match.
|
||||
reset()
|
||||
path = "/product/notdigits"
|
||||
match(false)
|
||||
|
||||
// Query doesn't match.
|
||||
reset()
|
||||
query = "?foo=baz"
|
||||
match(false)
|
||||
|
||||
// Method doesn't match.
|
||||
reset()
|
||||
method = "POST"
|
||||
match(false)
|
||||
|
||||
// Header doesn't match.
|
||||
reset()
|
||||
headers = map[string]string{}
|
||||
match(false)
|
||||
|
||||
// Everything match, again.
|
||||
reset()
|
||||
match(true)
|
||||
|
||||
// 2nd route --------------------------------------------------------------
|
||||
|
||||
// Everything match.
|
||||
reset2()
|
||||
match(true)
|
||||
|
||||
// Scheme doesn't match.
|
||||
reset2()
|
||||
scheme = "https"
|
||||
match(false)
|
||||
|
||||
// Host doesn't match.
|
||||
reset2()
|
||||
host = "sub.google.com"
|
||||
match(false)
|
||||
|
||||
// Path doesn't match.
|
||||
reset2()
|
||||
path = "/bar/product/42"
|
||||
match(false)
|
||||
|
||||
// Query doesn't match.
|
||||
reset2()
|
||||
query = "?foo=baz"
|
||||
match(false)
|
||||
|
||||
// Method doesn't match.
|
||||
reset2()
|
||||
method = "GET"
|
||||
match(false)
|
||||
|
||||
// Header doesn't match.
|
||||
reset2()
|
||||
headers = map[string]string{}
|
||||
match(false)
|
||||
|
||||
// Everything match, again.
|
||||
reset2()
|
||||
match(true)
|
||||
}
|
||||
|
||||
type headerMatcherTest struct {
|
||||
matcher headerMatcher
|
||||
headers map[string]string
|
||||
result bool
|
||||
}
|
||||
|
||||
var headerMatcherTests = []headerMatcherTest{
|
||||
{
|
||||
matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}),
|
||||
headers: map[string]string{"X-Requested-With": "XMLHttpRequest"},
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
matcher: headerMatcher(map[string]string{"x-requested-with": ""}),
|
||||
headers: map[string]string{"X-Requested-With": "anything"},
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
matcher: headerMatcher(map[string]string{"x-requested-with": "XMLHttpRequest"}),
|
||||
headers: map[string]string{},
|
||||
result: false,
|
||||
},
|
||||
}
|
||||
|
||||
type hostMatcherTest struct {
|
||||
matcher *Route
|
||||
url string
|
||||
vars map[string]string
|
||||
result bool
|
||||
}
|
||||
|
||||
var hostMatcherTests = []hostMatcherTest{
|
||||
{
|
||||
matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
|
||||
url: "http://abc.def.ghi/",
|
||||
vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
matcher: NewRouter().NewRoute().Host("{foo:[a-z][a-z][a-z]}.{bar:[a-z][a-z][a-z]}.{baz:[a-z][a-z][a-z]}"),
|
||||
url: "http://a.b.c/",
|
||||
vars: map[string]string{"foo": "abc", "bar": "def", "baz": "ghi"},
|
||||
result: false,
|
||||
},
|
||||
}
|
||||
|
||||
type methodMatcherTest struct {
|
||||
matcher methodMatcher
|
||||
method string
|
||||
result bool
|
||||
}
|
||||
|
||||
var methodMatcherTests = []methodMatcherTest{
|
||||
{
|
||||
matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
|
||||
method: "GET",
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
|
||||
method: "POST",
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
|
||||
method: "PUT",
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
matcher: methodMatcher([]string{"GET", "POST", "PUT"}),
|
||||
method: "DELETE",
|
||||
result: false,
|
||||
},
|
||||
}
|
||||
|
||||
type pathMatcherTest struct {
|
||||
matcher *Route
|
||||
url string
|
||||
vars map[string]string
|
||||
result bool
|
||||
}
|
||||
|
||||
var pathMatcherTests = []pathMatcherTest{
|
||||
{
|
||||
matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"),
|
||||
url: "http://localhost:8080/123/456/789",
|
||||
vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"},
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
matcher: NewRouter().NewRoute().Path("/{foo:[0-9][0-9][0-9]}/{bar:[0-9][0-9][0-9]}/{baz:[0-9][0-9][0-9]}"),
|
||||
url: "http://localhost:8080/1/2/3",
|
||||
vars: map[string]string{"foo": "123", "bar": "456", "baz": "789"},
|
||||
result: false,
|
||||
},
|
||||
}
|
||||
|
||||
type schemeMatcherTest struct {
|
||||
matcher schemeMatcher
|
||||
url string
|
||||
result bool
|
||||
}
|
||||
|
||||
var schemeMatcherTests = []schemeMatcherTest{
|
||||
{
|
||||
matcher: schemeMatcher([]string{"http", "https"}),
|
||||
url: "http://localhost:8080/",
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
matcher: schemeMatcher([]string{"http", "https"}),
|
||||
url: "https://localhost:8080/",
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
matcher: schemeMatcher([]string{"https"}),
|
||||
url: "http://localhost:8080/",
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
matcher: schemeMatcher([]string{"http"}),
|
||||
url: "https://localhost:8080/",
|
||||
result: false,
|
||||
},
|
||||
}
|
||||
|
||||
type urlBuildingTest struct {
|
||||
route *Route
|
||||
vars []string
|
||||
url string
|
||||
}
|
||||
|
||||
var urlBuildingTests = []urlBuildingTest{
|
||||
{
|
||||
route: new(Route).Host("foo.domain.com"),
|
||||
vars: []string{},
|
||||
url: "http://foo.domain.com",
|
||||
},
|
||||
{
|
||||
route: new(Route).Host("{subdomain}.domain.com"),
|
||||
vars: []string{"subdomain", "bar"},
|
||||
url: "http://bar.domain.com",
|
||||
},
|
||||
{
|
||||
route: new(Route).Host("foo.domain.com").Path("/articles"),
|
||||
vars: []string{},
|
||||
url: "http://foo.domain.com/articles",
|
||||
},
|
||||
{
|
||||
route: new(Route).Path("/articles"),
|
||||
vars: []string{},
|
||||
url: "/articles",
|
||||
},
|
||||
{
|
||||
route: new(Route).Path("/articles/{category}/{id:[0-9]+}"),
|
||||
vars: []string{"category", "technology", "id", "42"},
|
||||
url: "/articles/technology/42",
|
||||
},
|
||||
{
|
||||
route: new(Route).Host("{subdomain}.domain.com").Path("/articles/{category}/{id:[0-9]+}"),
|
||||
vars: []string{"subdomain", "foo", "category", "technology", "id", "42"},
|
||||
url: "http://foo.domain.com/articles/technology/42",
|
||||
},
|
||||
}
|
||||
|
||||
func TestHeaderMatcher(t *testing.T) {
|
||||
for _, v := range headerMatcherTests {
|
||||
request, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||
for key, value := range v.headers {
|
||||
request.Header.Add(key, value)
|
||||
}
|
||||
var routeMatch RouteMatch
|
||||
result := v.matcher.Match(request, &routeMatch)
|
||||
if result != v.result {
|
||||
if v.result {
|
||||
t.Errorf("%#v: should match %v.", v.matcher, request.Header)
|
||||
} else {
|
||||
t.Errorf("%#v: should not match %v.", v.matcher, request.Header)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHostMatcher(t *testing.T) {
|
||||
for _, v := range hostMatcherTests {
|
||||
request, _ := http.NewRequest("GET", v.url, nil)
|
||||
var routeMatch RouteMatch
|
||||
result := v.matcher.Match(request, &routeMatch)
|
||||
vars := routeMatch.Vars
|
||||
if result != v.result {
|
||||
if v.result {
|
||||
t.Errorf("%#v: should match %v.", v.matcher, v.url)
|
||||
} else {
|
||||
t.Errorf("%#v: should not match %v.", v.matcher, v.url)
|
||||
}
|
||||
}
|
||||
if result {
|
||||
if len(vars) != len(v.vars) {
|
||||
t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars))
|
||||
}
|
||||
for name, value := range vars {
|
||||
if v.vars[name] != value {
|
||||
t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if len(vars) != 0 {
|
||||
t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMethodMatcher(t *testing.T) {
|
||||
for _, v := range methodMatcherTests {
|
||||
request, _ := http.NewRequest(v.method, "http://localhost:8080/", nil)
|
||||
var routeMatch RouteMatch
|
||||
result := v.matcher.Match(request, &routeMatch)
|
||||
if result != v.result {
|
||||
if v.result {
|
||||
t.Errorf("%#v: should match %v.", v.matcher, v.method)
|
||||
} else {
|
||||
t.Errorf("%#v: should not match %v.", v.matcher, v.method)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathMatcher(t *testing.T) {
|
||||
for _, v := range pathMatcherTests {
|
||||
request, _ := http.NewRequest("GET", v.url, nil)
|
||||
var routeMatch RouteMatch
|
||||
result := v.matcher.Match(request, &routeMatch)
|
||||
vars := routeMatch.Vars
|
||||
if result != v.result {
|
||||
if v.result {
|
||||
t.Errorf("%#v: should match %v.", v.matcher, v.url)
|
||||
} else {
|
||||
t.Errorf("%#v: should not match %v.", v.matcher, v.url)
|
||||
}
|
||||
}
|
||||
if result {
|
||||
if len(vars) != len(v.vars) {
|
||||
t.Errorf("%#v: vars length should be %v, got %v.", v.matcher, len(v.vars), len(vars))
|
||||
}
|
||||
for name, value := range vars {
|
||||
if v.vars[name] != value {
|
||||
t.Errorf("%#v: expected value %v for key %v, got %v.", v.matcher, v.vars[name], name, value)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if len(vars) != 0 {
|
||||
t.Errorf("%#v: vars length should be 0, got %v.", v.matcher, len(vars))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSchemeMatcher(t *testing.T) {
|
||||
for _, v := range schemeMatcherTests {
|
||||
request, _ := http.NewRequest("GET", v.url, nil)
|
||||
var routeMatch RouteMatch
|
||||
result := v.matcher.Match(request, &routeMatch)
|
||||
if result != v.result {
|
||||
if v.result {
|
||||
t.Errorf("%#v: should match %v.", v.matcher, v.url)
|
||||
} else {
|
||||
t.Errorf("%#v: should not match %v.", v.matcher, v.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUrlBuilding(t *testing.T) {
|
||||
|
||||
for _, v := range urlBuildingTests {
|
||||
u, _ := v.route.URL(v.vars...)
|
||||
url := u.String()
|
||||
if url != v.url {
|
||||
t.Errorf("expected %v, got %v", v.url, url)
|
||||
/*
|
||||
reversePath := ""
|
||||
reverseHost := ""
|
||||
if v.route.pathTemplate != nil {
|
||||
reversePath = v.route.pathTemplate.Reverse
|
||||
}
|
||||
if v.route.hostTemplate != nil {
|
||||
reverseHost = v.route.hostTemplate.Reverse
|
||||
}
|
||||
|
||||
t.Errorf("%#v:\nexpected: %q\ngot: %q\nreverse path: %q\nreverse host: %q", v.route, v.url, url, reversePath, reverseHost)
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
ArticleHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
router := NewRouter()
|
||||
router.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).Name("article")
|
||||
|
||||
url, _ := router.Get("article").URL("category", "technology", "id", "42")
|
||||
expected := "/articles/technology/42"
|
||||
if url.String() != expected {
|
||||
t.Errorf("Expected %v, got %v", expected, url.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchedRouteName(t *testing.T) {
|
||||
routeName := "stock"
|
||||
router := NewRouter()
|
||||
route := router.NewRoute().Path("/products/").Name(routeName)
|
||||
|
||||
url := "http://www.domain.com/products/"
|
||||
request, _ := http.NewRequest("GET", url, nil)
|
||||
var rv RouteMatch
|
||||
ok := router.Match(request, &rv)
|
||||
|
||||
if !ok || rv.Route != route {
|
||||
t.Errorf("Expected same route, got %+v.", rv.Route)
|
||||
}
|
||||
|
||||
retName := rv.Route.GetName()
|
||||
if retName != routeName {
|
||||
t.Errorf("Expected %q, got %q.", routeName, retName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubRouting(t *testing.T) {
|
||||
// Example from docs.
|
||||
router := NewRouter()
|
||||
subrouter := router.NewRoute().Host("www.domain.com").Subrouter()
|
||||
route := subrouter.NewRoute().Path("/products/").Name("products")
|
||||
|
||||
url := "http://www.domain.com/products/"
|
||||
request, _ := http.NewRequest("GET", url, nil)
|
||||
var rv RouteMatch
|
||||
ok := router.Match(request, &rv)
|
||||
|
||||
if !ok || rv.Route != route {
|
||||
t.Errorf("Expected same route, got %+v.", rv.Route)
|
||||
}
|
||||
|
||||
u, _ := router.Get("products").URL()
|
||||
builtUrl := u.String()
|
||||
// Yay, subroute aware of the domain when building!
|
||||
if builtUrl != url {
|
||||
t.Errorf("Expected %q, got %q.", url, builtUrl)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVariableNames(t *testing.T) {
|
||||
route := new(Route).Host("{arg1}.domain.com").Path("/{arg1}/{arg2:[0-9]+}")
|
||||
if route.err == nil {
|
||||
t.Errorf("Expected error for duplicated variable names")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedirectSlash(t *testing.T) {
|
||||
var route *Route
|
||||
var routeMatch RouteMatch
|
||||
r := NewRouter()
|
||||
|
||||
r.StrictSlash(false)
|
||||
route = r.NewRoute()
|
||||
if route.strictSlash != false {
|
||||
t.Errorf("Expected false redirectSlash.")
|
||||
}
|
||||
|
||||
r.StrictSlash(true)
|
||||
route = r.NewRoute()
|
||||
if route.strictSlash != true {
|
||||
t.Errorf("Expected true redirectSlash.")
|
||||
}
|
||||
|
||||
route = new(Route)
|
||||
route.strictSlash = true
|
||||
route.Path("/{arg1}/{arg2:[0-9]+}/")
|
||||
request, _ := http.NewRequest("GET", "http://localhost/foo/123", nil)
|
||||
routeMatch = RouteMatch{}
|
||||
_ = route.Match(request, &routeMatch)
|
||||
vars := routeMatch.Vars
|
||||
if vars["arg1"] != "foo" {
|
||||
t.Errorf("Expected foo.")
|
||||
}
|
||||
if vars["arg2"] != "123" {
|
||||
t.Errorf("Expected 123.")
|
||||
}
|
||||
rsp := NewRecorder()
|
||||
routeMatch.Handler.ServeHTTP(rsp, request)
|
||||
if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123/" {
|
||||
t.Errorf("Expected redirect header.")
|
||||
}
|
||||
|
||||
route = new(Route)
|
||||
route.strictSlash = true
|
||||
route.Path("/{arg1}/{arg2:[0-9]+}")
|
||||
request, _ = http.NewRequest("GET", "http://localhost/foo/123/", nil)
|
||||
routeMatch = RouteMatch{}
|
||||
_ = route.Match(request, &routeMatch)
|
||||
vars = routeMatch.Vars
|
||||
if vars["arg1"] != "foo" {
|
||||
t.Errorf("Expected foo.")
|
||||
}
|
||||
if vars["arg2"] != "123" {
|
||||
t.Errorf("Expected 123.")
|
||||
}
|
||||
rsp = NewRecorder()
|
||||
routeMatch.Handler.ServeHTTP(rsp, request)
|
||||
if rsp.HeaderMap.Get("Location") != "http://localhost/foo/123" {
|
||||
t.Errorf("Expected redirect header.")
|
||||
}
|
||||
}
|
||||
|
||||
// Test for the new regexp library, still not available in stable Go.
|
||||
func TestNewRegexp(t *testing.T) {
|
||||
var p *routeRegexp
|
||||
var matches []string
|
||||
|
||||
tests := map[string]map[string][]string{
|
||||
"/{foo:a{2}}": {
|
||||
"/a": nil,
|
||||
"/aa": {"aa"},
|
||||
"/aaa": nil,
|
||||
"/aaaa": nil,
|
||||
},
|
||||
"/{foo:a{2,}}": {
|
||||
"/a": nil,
|
||||
"/aa": {"aa"},
|
||||
"/aaa": {"aaa"},
|
||||
"/aaaa": {"aaaa"},
|
||||
},
|
||||
"/{foo:a{2,3}}": {
|
||||
"/a": nil,
|
||||
"/aa": {"aa"},
|
||||
"/aaa": {"aaa"},
|
||||
"/aaaa": nil,
|
||||
},
|
||||
"/{foo:[a-z]{3}}/{bar:[a-z]{2}}": {
|
||||
"/a": nil,
|
||||
"/ab": nil,
|
||||
"/abc": nil,
|
||||
"/abcd": nil,
|
||||
"/abc/ab": {"abc", "ab"},
|
||||
"/abc/abc": nil,
|
||||
"/abcd/ab": nil,
|
||||
},
|
||||
`/{foo:\w{3,}}/{bar:\d{2,}}`: {
|
||||
"/a": nil,
|
||||
"/ab": nil,
|
||||
"/abc": nil,
|
||||
"/abc/1": nil,
|
||||
"/abc/12": {"abc", "12"},
|
||||
"/abcd/12": {"abcd", "12"},
|
||||
"/abcd/123": {"abcd", "123"},
|
||||
},
|
||||
}
|
||||
|
||||
for pattern, paths := range tests {
|
||||
p, _ = newRouteRegexp(pattern, false, false, false, false)
|
||||
for path, result := range paths {
|
||||
matches = p.regexp.FindStringSubmatch(path)
|
||||
if result == nil {
|
||||
if matches != nil {
|
||||
t.Errorf("%v should not match %v.", pattern, path)
|
||||
}
|
||||
} else {
|
||||
if len(matches) != len(result)+1 {
|
||||
t.Errorf("Expected %v matches, got %v.", len(result)+1, len(matches))
|
||||
} else {
|
||||
for k, v := range result {
|
||||
if matches[k+1] != v {
|
||||
t.Errorf("Expected %v, got %v.", v, matches[k+1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,276 @@
|
|||
// Copyright 2012 The Gorilla 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 mux
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// newRouteRegexp parses a route template and returns a routeRegexp,
|
||||
// used to match a host, a path or a query string.
|
||||
//
|
||||
// It will extract named variables, assemble a regexp to be matched, create
|
||||
// a "reverse" template to build URLs and compile regexps to validate variable
|
||||
// values used in URL building.
|
||||
//
|
||||
// Previously we accepted only Python-like identifiers for variable
|
||||
// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
|
||||
// name and pattern can't be empty, and names can't contain a colon.
|
||||
func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash bool) (*routeRegexp, error) {
|
||||
// Check if it is well-formed.
|
||||
idxs, errBraces := braceIndices(tpl)
|
||||
if errBraces != nil {
|
||||
return nil, errBraces
|
||||
}
|
||||
// Backup the original.
|
||||
template := tpl
|
||||
// Now let's parse it.
|
||||
defaultPattern := "[^/]+"
|
||||
if matchQuery {
|
||||
defaultPattern = "[^?&]+"
|
||||
matchPrefix = true
|
||||
} else if matchHost {
|
||||
defaultPattern = "[^.]+"
|
||||
matchPrefix = false
|
||||
}
|
||||
// Only match strict slash if not matching
|
||||
if matchPrefix || matchHost || matchQuery {
|
||||
strictSlash = false
|
||||
}
|
||||
// Set a flag for strictSlash.
|
||||
endSlash := false
|
||||
if strictSlash && strings.HasSuffix(tpl, "/") {
|
||||
tpl = tpl[:len(tpl)-1]
|
||||
endSlash = true
|
||||
}
|
||||
varsN := make([]string, len(idxs)/2)
|
||||
varsR := make([]*regexp.Regexp, len(idxs)/2)
|
||||
pattern := bytes.NewBufferString("")
|
||||
if !matchQuery {
|
||||
pattern.WriteByte('^')
|
||||
}
|
||||
reverse := bytes.NewBufferString("")
|
||||
var end int
|
||||
var err error
|
||||
for i := 0; i < len(idxs); i += 2 {
|
||||
// Set all values we are interested in.
|
||||
raw := tpl[end:idxs[i]]
|
||||
end = idxs[i+1]
|
||||
parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2)
|
||||
name := parts[0]
|
||||
patt := defaultPattern
|
||||
if len(parts) == 2 {
|
||||
patt = parts[1]
|
||||
}
|
||||
// Name or pattern can't be empty.
|
||||
if name == "" || patt == "" {
|
||||
return nil, fmt.Errorf("mux: missing name or pattern in %q",
|
||||
tpl[idxs[i]:end])
|
||||
}
|
||||
// Build the regexp pattern.
|
||||
fmt.Fprintf(pattern, "%s(%s)", regexp.QuoteMeta(raw), patt)
|
||||
// Build the reverse template.
|
||||
fmt.Fprintf(reverse, "%s%%s", raw)
|
||||
// Append variable name and compiled pattern.
|
||||
varsN[i/2] = name
|
||||
varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Add the remaining.
|
||||
raw := tpl[end:]
|
||||
pattern.WriteString(regexp.QuoteMeta(raw))
|
||||
if strictSlash {
|
||||
pattern.WriteString("[/]?")
|
||||
}
|
||||
if !matchPrefix {
|
||||
pattern.WriteByte('$')
|
||||
}
|
||||
reverse.WriteString(raw)
|
||||
if endSlash {
|
||||
reverse.WriteByte('/')
|
||||
}
|
||||
// Compile full regexp.
|
||||
reg, errCompile := regexp.Compile(pattern.String())
|
||||
if errCompile != nil {
|
||||
return nil, errCompile
|
||||
}
|
||||
// Done!
|
||||
return &routeRegexp{
|
||||
template: template,
|
||||
matchHost: matchHost,
|
||||
matchQuery: matchQuery,
|
||||
strictSlash: strictSlash,
|
||||
regexp: reg,
|
||||
reverse: reverse.String(),
|
||||
varsN: varsN,
|
||||
varsR: varsR,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// routeRegexp stores a regexp to match a host or path and information to
|
||||
// collect and validate route variables.
|
||||
type routeRegexp struct {
|
||||
// The unmodified template.
|
||||
template string
|
||||
// True for host match, false for path or query string match.
|
||||
matchHost bool
|
||||
// True for query string match, false for path and host match.
|
||||
matchQuery bool
|
||||
// The strictSlash value defined on the route, but disabled if PathPrefix was used.
|
||||
strictSlash bool
|
||||
// Expanded regexp.
|
||||
regexp *regexp.Regexp
|
||||
// Reverse template.
|
||||
reverse string
|
||||
// Variable names.
|
||||
varsN []string
|
||||
// Variable regexps (validators).
|
||||
varsR []*regexp.Regexp
|
||||
}
|
||||
|
||||
// Match matches the regexp against the URL host or path.
|
||||
func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
|
||||
if !r.matchHost {
|
||||
if r.matchQuery {
|
||||
return r.regexp.MatchString(req.URL.RawQuery)
|
||||
} else {
|
||||
return r.regexp.MatchString(req.URL.Path)
|
||||
}
|
||||
}
|
||||
return r.regexp.MatchString(getHost(req))
|
||||
}
|
||||
|
||||
// url builds a URL part using the given values.
|
||||
func (r *routeRegexp) url(pairs ...string) (string, error) {
|
||||
values, err := mapFromPairs(pairs...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
urlValues := make([]interface{}, len(r.varsN))
|
||||
for k, v := range r.varsN {
|
||||
value, ok := values[v]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("mux: missing route variable %q", v)
|
||||
}
|
||||
urlValues[k] = value
|
||||
}
|
||||
rv := fmt.Sprintf(r.reverse, urlValues...)
|
||||
if !r.regexp.MatchString(rv) {
|
||||
// The URL is checked against the full regexp, instead of checking
|
||||
// individual variables. This is faster but to provide a good error
|
||||
// message, we check individual regexps if the URL doesn't match.
|
||||
for k, v := range r.varsN {
|
||||
if !r.varsR[k].MatchString(values[v]) {
|
||||
return "", fmt.Errorf(
|
||||
"mux: variable %q doesn't match, expected %q", values[v],
|
||||
r.varsR[k].String())
|
||||
}
|
||||
}
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
// braceIndices returns the first level curly brace indices from a string.
|
||||
// It returns an error in case of unbalanced braces.
|
||||
func braceIndices(s string) ([]int, error) {
|
||||
var level, idx int
|
||||
idxs := make([]int, 0)
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '{':
|
||||
if level++; level == 1 {
|
||||
idx = i
|
||||
}
|
||||
case '}':
|
||||
if level--; level == 0 {
|
||||
idxs = append(idxs, idx, i+1)
|
||||
} else if level < 0 {
|
||||
return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
if level != 0 {
|
||||
return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
|
||||
}
|
||||
return idxs, nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// routeRegexpGroup
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// routeRegexpGroup groups the route matchers that carry variables.
|
||||
type routeRegexpGroup struct {
|
||||
host *routeRegexp
|
||||
path *routeRegexp
|
||||
queries []*routeRegexp
|
||||
}
|
||||
|
||||
// setMatch extracts the variables from the URL once a route matches.
|
||||
func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
|
||||
// Store host variables.
|
||||
if v.host != nil {
|
||||
hostVars := v.host.regexp.FindStringSubmatch(getHost(req))
|
||||
if hostVars != nil {
|
||||
for k, v := range v.host.varsN {
|
||||
m.Vars[v] = hostVars[k+1]
|
||||
}
|
||||
}
|
||||
}
|
||||
// Store path variables.
|
||||
if v.path != nil {
|
||||
pathVars := v.path.regexp.FindStringSubmatch(req.URL.Path)
|
||||
if pathVars != nil {
|
||||
for k, v := range v.path.varsN {
|
||||
m.Vars[v] = pathVars[k+1]
|
||||
}
|
||||
// Check if we should redirect.
|
||||
if v.path.strictSlash {
|
||||
p1 := strings.HasSuffix(req.URL.Path, "/")
|
||||
p2 := strings.HasSuffix(v.path.template, "/")
|
||||
if p1 != p2 {
|
||||
u, _ := url.Parse(req.URL.String())
|
||||
if p1 {
|
||||
u.Path = u.Path[:len(u.Path)-1]
|
||||
} else {
|
||||
u.Path += "/"
|
||||
}
|
||||
m.Handler = http.RedirectHandler(u.String(), 301)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Store query string variables.
|
||||
rawQuery := req.URL.RawQuery
|
||||
for _, q := range v.queries {
|
||||
queryVars := q.regexp.FindStringSubmatch(rawQuery)
|
||||
if queryVars != nil {
|
||||
for k, v := range q.varsN {
|
||||
m.Vars[v] = queryVars[k+1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getHost tries its best to return the request host.
|
||||
func getHost(r *http.Request) string {
|
||||
if r.URL.IsAbs() {
|
||||
return r.URL.Host
|
||||
}
|
||||
host := r.Host
|
||||
// Slice off any port information.
|
||||
if i := strings.Index(host, ":"); i != -1 {
|
||||
host = host[:i]
|
||||
}
|
||||
return host
|
||||
|
||||
}
|
|
@ -0,0 +1,524 @@
|
|||
// Copyright 2012 The Gorilla 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 mux
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Route stores information to match a request and build URLs.
|
||||
type Route struct {
|
||||
// Parent where the route was registered (a Router).
|
||||
parent parentRoute
|
||||
// Request handler for the route.
|
||||
handler http.Handler
|
||||
// List of matchers.
|
||||
matchers []matcher
|
||||
// Manager for the variables from host and path.
|
||||
regexp *routeRegexpGroup
|
||||
// If true, when the path pattern is "/path/", accessing "/path" will
|
||||
// redirect to the former and vice versa.
|
||||
strictSlash bool
|
||||
// If true, this route never matches: it is only used to build URLs.
|
||||
buildOnly bool
|
||||
// The name used to build URLs.
|
||||
name string
|
||||
// Error resulted from building a route.
|
||||
err error
|
||||
}
|
||||
|
||||
// Match matches the route against the request.
|
||||
func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
|
||||
if r.buildOnly || r.err != nil {
|
||||
return false
|
||||
}
|
||||
// Match everything.
|
||||
for _, m := range r.matchers {
|
||||
if matched := m.Match(req, match); !matched {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Yay, we have a match. Let's collect some info about it.
|
||||
if match.Route == nil {
|
||||
match.Route = r
|
||||
}
|
||||
if match.Handler == nil {
|
||||
match.Handler = r.handler
|
||||
}
|
||||
if match.Vars == nil {
|
||||
match.Vars = make(map[string]string)
|
||||
}
|
||||
// Set variables.
|
||||
if r.regexp != nil {
|
||||
r.regexp.setMatch(req, match, r)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Route attributes
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// GetError returns an error resulted from building the route, if any.
|
||||
func (r *Route) GetError() error {
|
||||
return r.err
|
||||
}
|
||||
|
||||
// BuildOnly sets the route to never match: it is only used to build URLs.
|
||||
func (r *Route) BuildOnly() *Route {
|
||||
r.buildOnly = true
|
||||
return r
|
||||
}
|
||||
|
||||
// Handler --------------------------------------------------------------------
|
||||
|
||||
// Handler sets a handler for the route.
|
||||
func (r *Route) Handler(handler http.Handler) *Route {
|
||||
if r.err == nil {
|
||||
r.handler = handler
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// HandlerFunc sets a handler function for the route.
|
||||
func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route {
|
||||
return r.Handler(http.HandlerFunc(f))
|
||||
}
|
||||
|
||||
// GetHandler returns the handler for the route, if any.
|
||||
func (r *Route) GetHandler() http.Handler {
|
||||
return r.handler
|
||||
}
|
||||
|
||||
// Name -----------------------------------------------------------------------
|
||||
|
||||
// Name sets the name for the route, used to build URLs.
|
||||
// If the name was registered already it will be overwritten.
|
||||
func (r *Route) Name(name string) *Route {
|
||||
if r.name != "" {
|
||||
r.err = fmt.Errorf("mux: route already has name %q, can't set %q",
|
||||
r.name, name)
|
||||
}
|
||||
if r.err == nil {
|
||||
r.name = name
|
||||
r.getNamedRoutes()[name] = r
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// GetName returns the name for the route, if any.
|
||||
func (r *Route) GetName() string {
|
||||
return r.name
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Matchers
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// matcher types try to match a request.
|
||||
type matcher interface {
|
||||
Match(*http.Request, *RouteMatch) bool
|
||||
}
|
||||
|
||||
// addMatcher adds a matcher to the route.
|
||||
func (r *Route) addMatcher(m matcher) *Route {
|
||||
if r.err == nil {
|
||||
r.matchers = append(r.matchers, m)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// addRegexpMatcher adds a host or path matcher and builder to a route.
|
||||
func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery bool) error {
|
||||
if r.err != nil {
|
||||
return r.err
|
||||
}
|
||||
r.regexp = r.getRegexpGroup()
|
||||
if !matchHost && !matchQuery {
|
||||
if len(tpl) == 0 || tpl[0] != '/' {
|
||||
return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
|
||||
}
|
||||
if r.regexp.path != nil {
|
||||
tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
|
||||
}
|
||||
}
|
||||
rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, q := range r.regexp.queries {
|
||||
if err = uniqueVars(rr.varsN, q.varsN); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if matchHost {
|
||||
if r.regexp.path != nil {
|
||||
if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
r.regexp.host = rr
|
||||
} else {
|
||||
if r.regexp.host != nil {
|
||||
if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if matchQuery {
|
||||
r.regexp.queries = append(r.regexp.queries, rr)
|
||||
} else {
|
||||
r.regexp.path = rr
|
||||
}
|
||||
}
|
||||
r.addMatcher(rr)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Headers --------------------------------------------------------------------
|
||||
|
||||
// headerMatcher matches the request against header values.
|
||||
type headerMatcher map[string]string
|
||||
|
||||
func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
||||
return matchMap(m, r.Header, true)
|
||||
}
|
||||
|
||||
// Headers adds a matcher for request header values.
|
||||
// It accepts a sequence of key/value pairs to be matched. For example:
|
||||
//
|
||||
// r := mux.NewRouter()
|
||||
// r.Headers("Content-Type", "application/json",
|
||||
// "X-Requested-With", "XMLHttpRequest")
|
||||
//
|
||||
// The above route will only match if both request header values match.
|
||||
//
|
||||
// It the value is an empty string, it will match any value if the key is set.
|
||||
func (r *Route) Headers(pairs ...string) *Route {
|
||||
if r.err == nil {
|
||||
var headers map[string]string
|
||||
headers, r.err = mapFromPairs(pairs...)
|
||||
return r.addMatcher(headerMatcher(headers))
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Host -----------------------------------------------------------------------
|
||||
|
||||
// Host adds a matcher for the URL host.
|
||||
// It accepts a template with zero or more URL variables enclosed by {}.
|
||||
// Variables can define an optional regexp pattern to me matched:
|
||||
//
|
||||
// - {name} matches anything until the next dot.
|
||||
//
|
||||
// - {name:pattern} matches the given regexp pattern.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// r := mux.NewRouter()
|
||||
// r.Host("www.domain.com")
|
||||
// r.Host("{subdomain}.domain.com")
|
||||
// r.Host("{subdomain:[a-z]+}.domain.com")
|
||||
//
|
||||
// Variable names must be unique in a given route. They can be retrieved
|
||||
// calling mux.Vars(request).
|
||||
func (r *Route) Host(tpl string) *Route {
|
||||
r.err = r.addRegexpMatcher(tpl, true, false, false)
|
||||
return r
|
||||
}
|
||||
|
||||
// MatcherFunc ----------------------------------------------------------------
|
||||
|
||||
// MatcherFunc is the function signature used by custom matchers.
|
||||
type MatcherFunc func(*http.Request, *RouteMatch) bool
|
||||
|
||||
func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool {
|
||||
return m(r, match)
|
||||
}
|
||||
|
||||
// MatcherFunc adds a custom function to be used as request matcher.
|
||||
func (r *Route) MatcherFunc(f MatcherFunc) *Route {
|
||||
return r.addMatcher(f)
|
||||
}
|
||||
|
||||
// Methods --------------------------------------------------------------------
|
||||
|
||||
// methodMatcher matches the request against HTTP methods.
|
||||
type methodMatcher []string
|
||||
|
||||
func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
||||
return matchInArray(m, r.Method)
|
||||
}
|
||||
|
||||
// Methods adds a matcher for HTTP methods.
|
||||
// It accepts a sequence of one or more methods to be matched, e.g.:
|
||||
// "GET", "POST", "PUT".
|
||||
func (r *Route) Methods(methods ...string) *Route {
|
||||
for k, v := range methods {
|
||||
methods[k] = strings.ToUpper(v)
|
||||
}
|
||||
return r.addMatcher(methodMatcher(methods))
|
||||
}
|
||||
|
||||
// Path -----------------------------------------------------------------------
|
||||
|
||||
// Path adds a matcher for the URL path.
|
||||
// It accepts a template with zero or more URL variables enclosed by {}. The
|
||||
// template must start with a "/".
|
||||
// Variables can define an optional regexp pattern to me matched:
|
||||
//
|
||||
// - {name} matches anything until the next slash.
|
||||
//
|
||||
// - {name:pattern} matches the given regexp pattern.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// r := mux.NewRouter()
|
||||
// r.Path("/products/").Handler(ProductsHandler)
|
||||
// r.Path("/products/{key}").Handler(ProductsHandler)
|
||||
// r.Path("/articles/{category}/{id:[0-9]+}").
|
||||
// Handler(ArticleHandler)
|
||||
//
|
||||
// Variable names must be unique in a given route. They can be retrieved
|
||||
// calling mux.Vars(request).
|
||||
func (r *Route) Path(tpl string) *Route {
|
||||
r.err = r.addRegexpMatcher(tpl, false, false, false)
|
||||
return r
|
||||
}
|
||||
|
||||
// PathPrefix -----------------------------------------------------------------
|
||||
|
||||
// PathPrefix adds a matcher for the URL path prefix. This matches if the given
|
||||
// template is a prefix of the full URL path. See Route.Path() for details on
|
||||
// the tpl argument.
|
||||
//
|
||||
// Note that it does not treat slashes specially ("/foobar/" will be matched by
|
||||
// the prefix "/foo") so you may want to use a trailing slash here.
|
||||
//
|
||||
// Also note that the setting of Router.StrictSlash() has no effect on routes
|
||||
// with a PathPrefix matcher.
|
||||
func (r *Route) PathPrefix(tpl string) *Route {
|
||||
r.err = r.addRegexpMatcher(tpl, false, true, false)
|
||||
return r
|
||||
}
|
||||
|
||||
// Query ----------------------------------------------------------------------
|
||||
|
||||
// Queries adds a matcher for URL query values.
|
||||
// It accepts a sequence of key/value pairs. Values may define variables.
|
||||
// For example:
|
||||
//
|
||||
// r := mux.NewRouter()
|
||||
// r.Queries("foo", "bar", "id", "{id:[0-9]+}")
|
||||
//
|
||||
// The above route will only match if the URL contains the defined queries
|
||||
// values, e.g.: ?foo=bar&id=42.
|
||||
//
|
||||
// It the value is an empty string, it will match any value if the key is set.
|
||||
//
|
||||
// Variables can define an optional regexp pattern to me matched:
|
||||
//
|
||||
// - {name} matches anything until the next slash.
|
||||
//
|
||||
// - {name:pattern} matches the given regexp pattern.
|
||||
func (r *Route) Queries(pairs ...string) *Route {
|
||||
length := len(pairs)
|
||||
if length%2 != 0 {
|
||||
r.err = fmt.Errorf(
|
||||
"mux: number of parameters must be multiple of 2, got %v", pairs)
|
||||
return nil
|
||||
}
|
||||
for i := 0; i < length; i += 2 {
|
||||
if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], false, true, true); r.err != nil {
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Schemes --------------------------------------------------------------------
|
||||
|
||||
// schemeMatcher matches the request against URL schemes.
|
||||
type schemeMatcher []string
|
||||
|
||||
func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
||||
return matchInArray(m, r.URL.Scheme)
|
||||
}
|
||||
|
||||
// Schemes adds a matcher for URL schemes.
|
||||
// It accepts a sequence of schemes to be matched, e.g.: "http", "https".
|
||||
func (r *Route) Schemes(schemes ...string) *Route {
|
||||
for k, v := range schemes {
|
||||
schemes[k] = strings.ToLower(v)
|
||||
}
|
||||
return r.addMatcher(schemeMatcher(schemes))
|
||||
}
|
||||
|
||||
// Subrouter ------------------------------------------------------------------
|
||||
|
||||
// Subrouter creates a subrouter for the route.
|
||||
//
|
||||
// It will test the inner routes only if the parent route matched. For example:
|
||||
//
|
||||
// r := mux.NewRouter()
|
||||
// s := r.Host("www.domain.com").Subrouter()
|
||||
// s.HandleFunc("/products/", ProductsHandler)
|
||||
// s.HandleFunc("/products/{key}", ProductHandler)
|
||||
// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
|
||||
//
|
||||
// Here, the routes registered in the subrouter won't be tested if the host
|
||||
// doesn't match.
|
||||
func (r *Route) Subrouter() *Router {
|
||||
router := &Router{parent: r, strictSlash: r.strictSlash}
|
||||
r.addMatcher(router)
|
||||
return router
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// URL building
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// URL builds a URL for the route.
|
||||
//
|
||||
// It accepts a sequence of key/value pairs for the route variables. For
|
||||
// example, given this route:
|
||||
//
|
||||
// r := mux.NewRouter()
|
||||
// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
||||
// Name("article")
|
||||
//
|
||||
// ...a URL for it can be built using:
|
||||
//
|
||||
// url, err := r.Get("article").URL("category", "technology", "id", "42")
|
||||
//
|
||||
// ...which will return an url.URL with the following path:
|
||||
//
|
||||
// "/articles/technology/42"
|
||||
//
|
||||
// This also works for host variables:
|
||||
//
|
||||
// r := mux.NewRouter()
|
||||
// r.Host("{subdomain}.domain.com").
|
||||
// HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
||||
// Name("article")
|
||||
//
|
||||
// // url.String() will be "http://news.domain.com/articles/technology/42"
|
||||
// url, err := r.Get("article").URL("subdomain", "news",
|
||||
// "category", "technology",
|
||||
// "id", "42")
|
||||
//
|
||||
// All variables defined in the route are required, and their values must
|
||||
// conform to the corresponding patterns.
|
||||
func (r *Route) URL(pairs ...string) (*url.URL, error) {
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
if r.regexp == nil {
|
||||
return nil, errors.New("mux: route doesn't have a host or path")
|
||||
}
|
||||
var scheme, host, path string
|
||||
var err error
|
||||
if r.regexp.host != nil {
|
||||
// Set a default scheme.
|
||||
scheme = "http"
|
||||
if host, err = r.regexp.host.url(pairs...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if r.regexp.path != nil {
|
||||
if path, err = r.regexp.path.url(pairs...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &url.URL{
|
||||
Scheme: scheme,
|
||||
Host: host,
|
||||
Path: path,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// URLHost builds the host part of the URL for a route. See Route.URL().
|
||||
//
|
||||
// The route must have a host defined.
|
||||
func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
if r.regexp == nil || r.regexp.host == nil {
|
||||
return nil, errors.New("mux: route doesn't have a host")
|
||||
}
|
||||
host, err := r.regexp.host.url(pairs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &url.URL{
|
||||
Scheme: "http",
|
||||
Host: host,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// URLPath builds the path part of the URL for a route. See Route.URL().
|
||||
//
|
||||
// The route must have a path defined.
|
||||
func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
if r.regexp == nil || r.regexp.path == nil {
|
||||
return nil, errors.New("mux: route doesn't have a path")
|
||||
}
|
||||
path, err := r.regexp.path.url(pairs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &url.URL{
|
||||
Path: path,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// parentRoute
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// parentRoute allows routes to know about parent host and path definitions.
|
||||
type parentRoute interface {
|
||||
getNamedRoutes() map[string]*Route
|
||||
getRegexpGroup() *routeRegexpGroup
|
||||
}
|
||||
|
||||
// getNamedRoutes returns the map where named routes are registered.
|
||||
func (r *Route) getNamedRoutes() map[string]*Route {
|
||||
if r.parent == nil {
|
||||
// During tests router is not always set.
|
||||
r.parent = NewRouter()
|
||||
}
|
||||
return r.parent.getNamedRoutes()
|
||||
}
|
||||
|
||||
// getRegexpGroup returns regexp definitions from this route.
|
||||
func (r *Route) getRegexpGroup() *routeRegexpGroup {
|
||||
if r.regexp == nil {
|
||||
if r.parent == nil {
|
||||
// During tests router is not always set.
|
||||
r.parent = NewRouter()
|
||||
}
|
||||
regexp := r.parent.getRegexpGroup()
|
||||
if regexp == nil {
|
||||
r.regexp = new(routeRegexpGroup)
|
||||
} else {
|
||||
// Copy.
|
||||
r.regexp = &routeRegexpGroup{
|
||||
host: regexp.host,
|
||||
path: regexp.path,
|
||||
queries: regexp.queries,
|
||||
}
|
||||
}
|
||||
}
|
||||
return r.regexp
|
||||
}
|
6
pkg/api/Godeps/_workspace/src/github.com/minio-io/erasure/.gitignore
generated
vendored
Normal file
6
pkg/api/Godeps/_workspace/src/github.com/minio-io/erasure/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
*.o
|
||||
*.a
|
||||
*.so
|
||||
*~
|
||||
*.dSYM
|
||||
*.syso
|
80
pkg/api/Godeps/_workspace/src/github.com/minio-io/erasure/BUILDDEPS.md
generated
vendored
Normal file
80
pkg/api/Godeps/_workspace/src/github.com/minio-io/erasure/BUILDDEPS.md
generated
vendored
Normal file
|
@ -0,0 +1,80 @@
|
|||
## Ubuntu (Kylin) 14.04
|
||||
### Build Dependencies
|
||||
This installation document assumes Ubuntu 14.04 or later on x86-64 platform.
|
||||
|
||||
##### Install YASM
|
||||
|
||||
Erasure depends on Intel ISAL library, ISAL uses Intel AVX2 processor instructions, to compile these files one needs to install ``yasm`` which supports AVX2 instructions. AVX2 support only ended in ``yasm`` from version ``1.2.0``, any version below ``1.2.0`` will throw a build error.
|
||||
|
||||
```sh
|
||||
$ sudo apt-get install yasm
|
||||
```
|
||||
|
||||
##### Install Go 1.4+
|
||||
Download Go 1.4+ from [https://golang.org/dl/](https://golang.org/dl/) and extract it into ``${HOME}/local`` and setup ``${HOME}/mygo`` as your project workspace folder.
|
||||
For example:
|
||||
```sh
|
||||
.... Extract and install golang ....
|
||||
|
||||
$ wget https://storage.googleapis.com/golang/go1.4.linux-amd64.tar.gz
|
||||
$ mkdir -p ${HOME}/local
|
||||
$ mkdir -p $HOME/mygo
|
||||
$ tar -C ${HOME}/local -xzf go1.4.linux-amd64.tar.gz
|
||||
|
||||
.... Export necessary environment variables ....
|
||||
|
||||
$ export PATH=$PATH:${HOME}/local/go/bin
|
||||
$ export GOROOT=${HOME}/local/go
|
||||
$ export GOPATH=$HOME/mygo
|
||||
$ export PATH=$PATH:$GOPATH/bin
|
||||
|
||||
.... Add paths to your bashrc ....
|
||||
|
||||
$ echo "export PATH=$PATH:${HOME}/local/go/bin" >> ${HOME}/.bashrc
|
||||
$ echo "export GOROOT=${HOME}/local/go" >> ${HOME}/.bashrc
|
||||
$ echo "export GOPATH=$HOME/mygo" >> ${HOME}/.bashrc
|
||||
$ echo "export PATH=$PATH:$GOPATH/bin" >> ${HOME}/.bashrc
|
||||
```
|
||||
|
||||
## Mac OSX (Yosemite) 10.10
|
||||
### Build Dependencies
|
||||
This installation document assumes Mac OSX Yosemite 10.10 or later on x86-64 platform.
|
||||
|
||||
##### Install brew
|
||||
```sh
|
||||
$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
|
||||
```
|
||||
|
||||
##### Install Git
|
||||
```sh
|
||||
$ brew install git
|
||||
```
|
||||
|
||||
##### Install YASM
|
||||
|
||||
Erasure depends on Intel ISAL library, ISAL uses Intel AVX2 processor instructions, to compile these files one needs to install ``yasm`` which supports AVX2 instructions. AVX2 support only ended in ``yasm`` from version ``1.2.0``, any version below ``1.2.0`` will throw a build error.
|
||||
|
||||
```sh
|
||||
$ brew install yasm
|
||||
```
|
||||
|
||||
##### Install Go 1.4+
|
||||
On MacOSX ``brew.sh`` is the best way to install golang
|
||||
|
||||
For example:
|
||||
```sh
|
||||
.... Install golang using `brew` ....
|
||||
|
||||
$ brew install go
|
||||
$ mkdir -p $HOME/mygo
|
||||
|
||||
.... Export necessary environment variables ....
|
||||
|
||||
$ export GOPATH=$HOME/mygo
|
||||
$ export PATH=$PATH:$GOPATH/bin
|
||||
|
||||
.... Add paths to your bashrc ....
|
||||
|
||||
$ echo "export GOPATH=$HOME/mygo" >> ${HOME}/.bashrc
|
||||
$ echo "export PATH=$PATH:$GOPATH/bin" >> ${HOME}/.bashrc
|
||||
```
|
30
pkg/api/Godeps/_workspace/src/github.com/minio-io/erasure/CONTRIBUTING.md
generated
vendored
Normal file
30
pkg/api/Godeps/_workspace/src/github.com/minio-io/erasure/CONTRIBUTING.md
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
### Setup your Erasure Github Repository
|
||||
Fork [Erasure upstream](https://github.com/minio-io/erasure/fork) source repository to your own personal repository. Copy the URL and pass it to ``go get`` command. Go uses git to clone a copy into your project workspace folder.
|
||||
```sh
|
||||
$ git clone https://github.com/$USER_ID/erasure
|
||||
$ cd erasure
|
||||
$ mkdir -p ${GOPATH}/src/github.com/minio-io
|
||||
$ ln -s ${PWD} $GOPATH/src/github.com/minio-io/
|
||||
```
|
||||
|
||||
### Compiling Erasure from source
|
||||
```sh
|
||||
$ go generate
|
||||
$ go build
|
||||
```
|
||||
### Developer Guidelines
|
||||
To make the process as seamless as possible, we ask for the following:
|
||||
* Go ahead and fork the project and make your changes. We encourage pull requests to discuss code changes.
|
||||
- Fork it
|
||||
- Create your feature branch (git checkout -b my-new-feature)
|
||||
- Commit your changes (git commit -am 'Add some feature')
|
||||
- Push to the branch (git push origin my-new-feature)
|
||||
- Create new Pull Request
|
||||
* When you're ready to create a pull request, be sure to:
|
||||
- Have test cases for the new code. If you have questions about how to do it, please ask in your pull request.
|
||||
- Run `go fmt`
|
||||
- Squash your commits into a single commit. `git rebase -i`. It's okay to force update your pull request.
|
||||
- Make sure `go test -race ./...` and `go build` completes.
|
||||
* Read [Effective Go](https://github.com/golang/go/wiki/CodeReviewComments) article from Golang project
|
||||
- `Erasure` project is strictly conformant with Golang style
|
||||
- if you happen to observe offending code, please feel free to send a pull request
|
26
pkg/api/Godeps/_workspace/src/github.com/minio-io/erasure/LICENSE.INTEL
generated
vendored
Normal file
26
pkg/api/Godeps/_workspace/src/github.com/minio-io/erasure/LICENSE.INTEL
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
Copyright(c) 2011-2014 Intel Corporation All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Intel Corporation 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.
|
202
pkg/api/Godeps/_workspace/src/github.com/minio-io/erasure/LICENSE.MINIO
generated
vendored
Normal file
202
pkg/api/Godeps/_workspace/src/github.com/minio-io/erasure/LICENSE.MINIO
generated
vendored
Normal file
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
25
pkg/api/Godeps/_workspace/src/github.com/minio-io/erasure/README.md
generated
vendored
Normal file
25
pkg/api/Godeps/_workspace/src/github.com/minio-io/erasure/README.md
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
## Introduction
|
||||
|
||||
Erasure is an open source Golang library written on top of ISAL (Intel Intelligent Storage Library) released under [Apache license v2](./LICENSE)
|
||||
|
||||
### Developers
|
||||
* [Get Source](./CONTRIBUTING.md)
|
||||
* [Build Dependencies](./BUILDDEPS.md)
|
||||
* [Development Workflow](./CONTRIBUTING.md#developer-guidelines)
|
||||
* [Developer discussions and bugs](https://github.com/Minio-io/erasure/issues)
|
||||
|
||||
### Supported platforms
|
||||
|
||||
| Name | Supported |
|
||||
| ------------- | ------------- |
|
||||
| Linux | Yes |
|
||||
| Windows | Not yet |
|
||||
| Mac OSX | Yes |
|
||||
|
||||
### Supported architectures
|
||||
|
||||
| Arch | Supported |
|
||||
| ------------- | ------------- |
|
||||
| x86-64 | Yes |
|
||||
| arm64 | Not yet|
|
||||
| i386 | Never |
|
49
pkg/api/Godeps/_workspace/src/github.com/minio-io/erasure/RELEASE-NOTES.INTEL
generated
vendored
Normal file
49
pkg/api/Godeps/_workspace/src/github.com/minio-io/erasure/RELEASE-NOTES.INTEL
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
================================================================================
|
||||
v2.10 Intel Intelligent Storage Acceleration Library Release Notes
|
||||
Open Source Version
|
||||
================================================================================
|
||||
|
||||
================================================================================
|
||||
RELEASE NOTE CONTENTS
|
||||
================================================================================
|
||||
1. KNOWN ISSUES
|
||||
2. FIXED ISSUES
|
||||
3. CHANGE LOG & FEATURES ADDED
|
||||
|
||||
================================================================================
|
||||
1. KNOWN ISSUES
|
||||
================================================================================
|
||||
|
||||
* Only erasure code unit included in open source version at this time.
|
||||
|
||||
* Perf tests do not run in Windows environment.
|
||||
|
||||
* Leaving <unit>/bin directories from builds in unit directories will cause the
|
||||
top-level make build to fail. Build only in top-level or ensure unit
|
||||
directories are clean of objects and /bin.
|
||||
|
||||
* 32-bit lib is not supported in Windows.
|
||||
|
||||
================================================================================
|
||||
2. FIXED ISSUES
|
||||
================================================================================
|
||||
v2.10
|
||||
|
||||
* Fix for windows register save overlap in gf_{3-6}vect_dot_prod_sse.asm. Only
|
||||
affects windows versions of erasure code. GP register saves/restore were
|
||||
pushed to same stack area as XMM.
|
||||
|
||||
================================================================================
|
||||
3. CHANGE LOG & FEATURES ADDED
|
||||
================================================================================
|
||||
v2.10
|
||||
|
||||
* Erasure code updates
|
||||
- New AVX and AVX2 support functions.
|
||||
- Changes min len requirement on gf_vect_dot_prod() to 32 from 16.
|
||||
- Tests include both source and parity recovery with ec_encode_data().
|
||||
- New encoding examples with Vandermonde or Cauchy matrix.
|
||||
|
||||
v2.8
|
||||
|
||||
* First open release of erasure code unit that is part of ISA-L.
|
3
pkg/api/Godeps/_workspace/src/github.com/minio-io/erasure/RELEASE-NOTES.MINIO
generated
vendored
Normal file
3
pkg/api/Godeps/_workspace/src/github.com/minio-io/erasure/RELEASE-NOTES.MINIO
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
v1.0 - Erasure Golang Package
|
||||
============================
|
||||
- First release, supports only amd64 or x86-64 architecture
|
71
pkg/api/Godeps/_workspace/src/github.com/minio-io/erasure/cauchy_test.go
generated
vendored
Normal file
71
pkg/api/Godeps/_workspace/src/github.com/minio-io/erasure/cauchy_test.go
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Minimalist Object Storage, (C) 2014 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package erasure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type MySuite struct{}
|
||||
|
||||
var _ = Suite(&MySuite{})
|
||||
|
||||
func Test(t *testing.T) { TestingT(t) }
|
||||
|
||||
const (
|
||||
k = 10
|
||||
m = 5
|
||||
)
|
||||
|
||||
func (s *MySuite) TestCauchyEncodeDecodeFailure(c *C) {
|
||||
ep, _ := ValidateParams(k, m, Cauchy)
|
||||
|
||||
data := []byte("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.")
|
||||
|
||||
e := NewErasure(ep)
|
||||
chunks, err := e.Encode(data)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
errorIndex := []int{0, 3, 5, 9, 11, 13}
|
||||
chunks = corruptChunks(chunks, errorIndex)
|
||||
|
||||
_, err = e.Decode(chunks, len(data))
|
||||
c.Assert(err, Not(IsNil))
|
||||
}
|
||||
|
||||
func (s *MySuite) TestCauchyEncodeDecodeSuccess(c *C) {
|
||||
ep, _ := ValidateParams(k, m, Cauchy)
|
||||
|
||||
data := []byte("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.")
|
||||
|
||||
e := NewErasure(ep)
|
||||
chunks, err := e.Encode(data)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
errorIndex := []int{0, 3, 5, 9, 13}
|
||||
chunks = corruptChunks(chunks, errorIndex)
|
||||
|
||||
recoveredData, err := e.Decode(chunks, len(data))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
if !bytes.Equal(data, recoveredData) {
|
||||
c.Fatalf("Recovered data mismatches with original data")
|
||||
}
|
||||
}
|
59
pkg/api/Godeps/_workspace/src/github.com/minio-io/erasure/ctypes.go
generated
vendored
Normal file
59
pkg/api/Godeps/_workspace/src/github.com/minio-io/erasure/ctypes.go
generated
vendored
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Minimalist Object Storage, (C) 2014 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package erasure
|
||||
|
||||
// #include <stdint.h>
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// intSlice2CIntArray converts Go int slice to C int array
|
||||
func intSlice2CIntArray(srcErrList []int) *C.int32_t {
|
||||
var sizeErrInt = int(unsafe.Sizeof(srcErrList[0]))
|
||||
switch sizeInt {
|
||||
case sizeErrInt:
|
||||
return (*C.int32_t)(unsafe.Pointer(&srcErrList[0]))
|
||||
case sizeInt8:
|
||||
int8Array := make([]int8, len(srcErrList))
|
||||
for i, v := range srcErrList {
|
||||
int8Array[i] = int8(v)
|
||||
}
|
||||
return (*C.int32_t)(unsafe.Pointer(&int8Array[0]))
|
||||
case sizeInt16:
|
||||
int16Array := make([]int16, len(srcErrList))
|
||||
for i, v := range srcErrList {
|
||||
int16Array[i] = int16(v)
|
||||
}
|
||||
return (*C.int32_t)(unsafe.Pointer(&int16Array[0]))
|
||||
case sizeInt32:
|
||||
int32Array := make([]int32, len(srcErrList))
|
||||
for i, v := range srcErrList {
|
||||
int32Array[i] = int32(v)
|
||||
}
|
||||
return (*C.int32_t)(unsafe.Pointer(&int32Array[0]))
|
||||
case sizeInt64:
|
||||
int64Array := make([]int64, len(srcErrList))
|
||||
for i, v := range srcErrList {
|
||||
int64Array[i] = int64(v)
|
||||
}
|
||||
return (*C.int32_t)(unsafe.Pointer(&int64Array[0]))
|
||||
default:
|
||||
panic(fmt.Sprintf("Unsupported: %d", sizeInt))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
// Package erasure is a Go wrapper for the Intel Intelligent Storage
|
||||
// Acceleration Library (Intel ISA-L). Intel ISA-L is a CPU optimized
|
||||
// implementation of erasure coding algorithms.
|
||||
//
|
||||
// For more information on Intel ISA-L, please visit:
|
||||
// https://01.org/intel%C2%AE-storage-acceleration-library-open-source-version
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// Encode encodes a block of data. The input is the original data. The output
|
||||
// is a 2 tuple containing (k + m) chunks of erasure encoded data and the
|
||||
// length of the original object.
|
||||
//
|
||||
// Decode decodes 2 tuple data containing (k + m) chunks back into its original form.
|
||||
// Additionally original block length should also be provided as input.
|
||||
//
|
||||
// Decoded data is exactly similar in length and content as the original data.
|
||||
//
|
||||
// Encoding data may be performed in 3 steps.
|
||||
//
|
||||
// 1. Create a parse set of encoder parameters
|
||||
// 2. Create a new encoder
|
||||
// 3. Encode data
|
||||
//
|
||||
// Decoding data is also performed in 3 steps.
|
||||
//
|
||||
// 1. Create a parse set of encoder parameters for validation
|
||||
// 2. Create a new encoder
|
||||
// 3. Decode data
|
||||
//
|
||||
// Erasure parameters contain three configurable elements:
|
||||
// ValidateParams(k, m, technique int) (ErasureParams, error)
|
||||
// k - Number of rows in matrix
|
||||
// m - Number of colums in matrix
|
||||
// technique - Matrix type, can be either Cauchy (recommended) or Vandermonde
|
||||
// constraints: k + m < Galois Field (2^8)
|
||||
//
|
||||
// Choosing right parity and matrix technique is left for application to decide.
|
||||
//
|
||||
// But here are the few points to keep in mind
|
||||
//
|
||||
// Techniques:
|
||||
// - Vandermonde is most commonly used method for choosing coefficients in erasure
|
||||
// encoding but does not guarantee invertable for every sub matrix.
|
||||
// Users may want to adjust for k > 5. (k is data blocks)
|
||||
// - Whereas Cauchy is our recommended method for choosing coefficients in erasure coding.
|
||||
// Since any sub-matrix of a Cauchy matrix is invertable.
|
||||
//
|
||||
// Total blocks:
|
||||
// - Data blocks and Parity blocks should not be greater than 'Galois Field' (2^8)
|
||||
//
|
||||
// Example
|
||||
//
|
||||
// Creating and using an encoder
|
||||
// var bytes []byte
|
||||
// params := erasure.ValidateParams(10, 5, erasure.Cauchy)
|
||||
// encoder := erasure.NewErasure(params)
|
||||
// encodedData, length := encoder.Encode(bytes)
|
||||
//
|
||||
// Creating and using a decoder
|
||||
// var encodedData [][]byte
|
||||
// var length int
|
||||
// params := erasure.ValidateParams(10, 5, erasure.Cauchy)
|
||||
// encoder := erasure.NewErasure(params)
|
||||
// originalData, err := encoder.Decode(encodedData, length)
|
||||
//
|
||||
package erasure
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue