pax_global_header00006660000000000000000000000064143644661730014530gustar00rootroot0000000000000052 comment=99ab1df2b59cf75584a78ca11093b9d1e8886b7c jsonquery-1.3.2/000077500000000000000000000000001436446617300135725ustar00rootroot00000000000000jsonquery-1.3.2/.gitignore000066400000000000000000000004621436446617300155640ustar00rootroot00000000000000# vscode .vscode debug *.test ./build # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.profjsonquery-1.3.2/.travis.yml000066400000000000000000000003521436446617300157030ustar00rootroot00000000000000language: go go: - 1.9.x - 1.12.x - 1.13.x install: - go get github.com/antchfx/xpath - go get github.com/mattn/goveralls - go get github.com/golang/groupcache script: - $HOME/gopath/bin/goveralls -service=travis-cijsonquery-1.3.2/LICENSE000066400000000000000000000017761436446617300146120ustar00rootroot00000000000000Permission 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.jsonquery-1.3.2/README.md000066400000000000000000000155521436446617300150610ustar00rootroot00000000000000# jsonquery [![Build Status](https://travis-ci.org/antchfx/jsonquery.svg?branch=master)](https://travis-ci.org/antchfx/jsonquery) [![Coverage Status](https://coveralls.io/repos/github/antchfx/jsonquery/badge.svg?branch=master)](https://coveralls.io/github/antchfx/jsonquery?branch=master) [![GoDoc](https://godoc.org/github.com/antchfx/jsonquery?status.svg)](https://godoc.org/github.com/antchfx/jsonquery) [![Go Report Card](https://goreportcard.com/badge/github.com/antchfx/jsonquery)](https://goreportcard.com/report/github.com/antchfx/jsonquery) # Overview [jsonquery](https://github.com/antchfx/jsonquery) is XPath query package for JSON document depended on [xpath](https://github.com/antchfx/xpath) package, writing in go. jsonquery helps you easy to extract any data from JSON using XPath query without using pre-defined object structure to unmarshal in go, saving your time. - [htmlquery](https://github.com/antchfx/htmlquery) - XPath query package for HTML document - [xmlquery](https://github.com/antchfx/xmlquery) - XPath query package for XML document. ### Install Package ``` go get github.com/antchfx/jsonquery ``` ## Get Started The below code may be help your understand what it does. We don't need pre-defined structure or using regexp to extract some data in JSON file, gets any data is easy and fast in jsonquery now. Using an xpath like syntax to access specific fields of a json structure. ```go // https://go.dev/play/p/vqoD_jWryKY package main import ( "fmt" "strings" "github.com/antchfx/jsonquery" ) func main() { s := `{ "person":{ "name":"John", "age":31, "female":false, "city":null, "hobbies":[ "coding", "eating", "football" ] } }` doc, err := jsonquery.Parse(strings.NewReader(s)) if err != nil { panic(err) } // xpath query age := jsonquery.FindOne(doc, "age") // or age = jsonquery.FindOne(doc, "person/age") fmt.Printf("%#v[%T]\n", age.Value(), age.Value()) // prints 31[float64] hobbies := jsonquery.FindOne(doc, "//hobbies") fmt.Printf("%#v\n", hobbies.Value()) // prints []interface {}{"coding", "eating", "football"} firstHobby := jsonquery.FindOne(doc, "//hobbies/*[1]") fmt.Printf("%#v\n", firstHobby.Value()) // "coding" } ``` Iterating over a json structure. ```go // https://go.dev/play/p/vwXQKTCLdVK package main import ( "fmt" "strings" "github.com/antchfx/jsonquery" ) func main() { s := `{ "name":"John", "age":31, "female":false, "city":null }` doc, err := jsonquery.Parse(strings.NewReader(s)) if err != nil { panic(err) } // iterate all json objects from child ndoes. for _, n := range doc.ChildNodes() { fmt.Printf("%s: %v[%T]\n", n.Data, n.Value(), n.Value()) } } ``` Output: ``` name: John[string] age: 31[float64] female: false[bool] city: [] ``` The default Json types and Go types are: | JSON | jsonquery(go) | | ------- | ------------- | | object | interface{} | | string | string | | number | float64 | | boolean | bool | | array | []interface{} | | null | nil | For more information about JSON & Go see the https://go.dev/blog/json ## Getting Started #### Load JSON from URL. ```go doc, err := jsonquery.LoadURL("http://www.example.com/feed?json") ``` #### Load JSON from string. ```go s :=`{ "name":"John", "age":31, "city":"New York" }` doc, err := jsonquery.Parse(strings.NewReader(s)) ``` #### Load JSON from io.Reader. ```go f, err := os.Open("./books.json") doc, err := jsonquery.Parse(f) ``` #### Parse JSON array ```go s := `[1,2,3,4,5,6]` doc, _ := jsonquery.Parse(strings.NewReader(s)) list := jsonquery.Find(doc, "*") for _, n := range list { fmt.Print(n.Value().(float64)) } ``` // Output: `1,2,3,4,5,6` #### Convert JSON object to XML ```go doc, _ := jsonquery.Parse(strings.NewReader(s)) fmt.Println(doc.OutputXML()) ``` ### Methods #### FindOne() ```go n := jsonquery.FindOne(doc,"//a") ``` #### Find() ```go list := jsonquery.Find(doc,"//a") ``` #### QuerySelector() ```go n := jsonquery.QuerySelector(doc, xpath.MustCompile("//a")) ``` #### QuerySelectorAll() ```go list :=jsonquery.QuerySelectorAll(doc, xpath.MustCompile("//a")) ``` #### Query() ```go n, err := jsonquery.Query(doc, "*") ``` #### QueryAll() ```go list, err := jsonquery.QueryAll(doc, "*") ``` #### Query() vs FindOne() - `Query()` will return an error if give xpath query expr is not valid. - `FindOne` will panic error and interrupt your program if give xpath query expr is not valid. #### OutputXML() Convert current JSON object to XML format. # Examples ```json { "store": { "book": [ { "id": 1, "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 }, { "id": 2, "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 }, { "id": 3, "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99 }, { "id": 4, "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99 } ], "bicycle": { "color": "red", "price": 19.95 } }, "expensive": 10 } ``` #### Tests | Query | Matched | Native Value Types | Native Values | | ------- | -------------| -------- | -------------| | `//book` | 1 | []interface{} | `{"book": [{"id":1,... }, {"id":2,... }, {"id":3,... }, {"id":4,... }]}` | | `//book/*` | 4 | [map[string]interface{}] |`{"id":1,... }`, `{"id":2,... }`, `{"id":3,... }`, `{"id":4,... }` | | `//*[price<12.99]` | 2 | [map[string]interface{}] | `{"id":1,...}`, `{"id":3,...}` | | `//book/*/author` | 4 | []string | `{"author": "Nigel Rees"}`, `{"author": "Evelyn Waugh"}`, `{"author": "Herman Melville"}`, `{"author": "J. R. R. Tolkien"}` | | `//book/*[last()]` | 1 | map[string]interface {} | `{"id":4,...}` | | `//book/*[2]` | 1 | map[string]interface{} | `{"id":2,...}` | | `//*[isbn]` | 2 | [map[string]interface{}] | `{"id":3,"isbn":"0-553-21311-3",...}`,`{"id":4,"isbn":"0-395-19395-8",...}` | | `//*[isbn='0-553-21311-3']` | 1 | map[string]interface{} | `{"id":3,"isbn":"0-553-21311-3",...}` | | `//bicycle` | 1 | map[string]interface {} | `{"bicycle":{"color":...,}}` | | `//bicycle/color[text()='red']` | 1 | map[string]interface {} | `{"color":"red"}` | | `//*/category[contains(.,'refer')]` | 1 | string | `{"category": "reference"}` | | `//price[.=22.99]` | 1 | float64 | `{"price": 22.99}` | | `//expensive/text()` | 1 | string | `10` | For more supports XPath feature and function see https://github.com/antchfx/xpathjsonquery-1.3.2/cache.go000066400000000000000000000016231436446617300151660ustar00rootroot00000000000000package jsonquery import ( "sync" "github.com/golang/groupcache/lru" "github.com/antchfx/xpath" ) // DisableSelectorCache will disable caching for the query selector if value is true. var DisableSelectorCache = false // SelectorCacheMaxEntries allows how many selector object can be caching. Default is 50. // Will disable caching if SelectorCacheMaxEntries <= 0. var SelectorCacheMaxEntries = 50 var ( cacheOnce sync.Once cache *lru.Cache cacheMutex sync.Mutex ) func getQuery(expr string) (*xpath.Expr, error) { if DisableSelectorCache || SelectorCacheMaxEntries <= 0 { return xpath.Compile(expr) } cacheOnce.Do(func() { cache = lru.New(SelectorCacheMaxEntries) }) cacheMutex.Lock() defer cacheMutex.Unlock() if v, ok := cache.Get(expr); ok { return v.(*xpath.Expr), nil } v, err := xpath.Compile(expr) if err != nil { return nil, err } cache.Add(expr, v) return v, nil } jsonquery-1.3.2/go.mod000066400000000000000000000002341436446617300146770ustar00rootroot00000000000000module github.com/antchfx/jsonquery go 1.14 require ( github.com/antchfx/xpath v1.2.3 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da ) jsonquery-1.3.2/go.sum000066400000000000000000000006161436446617300147300ustar00rootroot00000000000000github.com/antchfx/xpath v1.2.3 h1:CCZWOzv5bAqjVv0offZ2LVgVYFbeldKQVuLNbViZdes= github.com/antchfx/xpath v1.2.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= jsonquery-1.3.2/node.go000066400000000000000000000107601436446617300150520ustar00rootroot00000000000000package jsonquery import ( "encoding/json" "fmt" "io" "io/ioutil" "net/http" "reflect" "sort" "strings" ) // A NodeType is the type of a Node. type NodeType uint const ( // DocumentNode is a document object that, as the root of the document tree, // provides access to the entire XML document. DocumentNode NodeType = iota // ElementNode is an element. ElementNode // TextNode is the text content of a node. TextNode ) // A Node consists of a NodeType and some Data (tag name for // element nodes, content for text) and are part of a tree of Nodes. type Node struct { Parent, PrevSibling, NextSibling, FirstChild, LastChild *Node Type NodeType Data string level int value interface{} } // Gets the JSON object value. func (n *Node) Value() interface{} { return n.value } // ChildNodes gets all child nodes of the node. func (n *Node) ChildNodes() []*Node { var a []*Node for nn := n.FirstChild; nn != nil; nn = nn.NextSibling { a = append(a, nn) } return a } // InnerText will gets the value of the node and all its child nodes. // // Deprecated: Use Value() to get JSON object value. func (n *Node) InnerText() string { var output func(*strings.Builder, *Node) output = func(b *strings.Builder, n *Node) { if n.Type == TextNode { b.WriteString(fmt.Sprintf("%v", n.value)) return } for child := n.FirstChild; child != nil; child = child.NextSibling { output(b, child) } } var b strings.Builder output(&b, n) return b.String() } func outputXML(b *strings.Builder, n *Node, level int, skip bool) { level++ if n.Type == TextNode { b.WriteString(fmt.Sprintf("%v", n.value)) return } if v := reflect.ValueOf(n.value); v.Kind() == reflect.Slice { for child := n.FirstChild; child != nil; child = child.NextSibling { b.WriteString("<" + n.Data + ">") outputXML(b, child, level, true) b.WriteString("") } } else { d := n.Data if !skip { if d == "" { d = fmt.Sprintf("%v", n.value) } b.WriteString("<" + d + ">") } for child := n.FirstChild; child != nil; child = child.NextSibling { outputXML(b, child, level, false) } if !skip { b.WriteString("") } } } // OutputXML prints the XML string. func (n *Node) OutputXML() string { var b strings.Builder b.WriteString(``) b.WriteString("") level := 0 for n := n.FirstChild; n != nil; n = n.NextSibling { outputXML(&b, n, level, false) } b.WriteString("") return b.String() } // SelectElement finds the first of child elements with the // specified name. func (n *Node) SelectElement(name string) *Node { for nn := n.FirstChild; nn != nil; nn = nn.NextSibling { if nn.Data == name { return nn } } return nil } // LoadURL loads the JSON document from the specified URL. func LoadURL(url string) (*Node, error) { resp, err := http.Get(url) if err != nil { return nil, err } defer resp.Body.Close() return Parse(resp.Body) } func parseValue(x interface{}, top *Node, level int) { addNode := func(n *Node) { if n.level == top.level { top.NextSibling = n n.PrevSibling = top n.Parent = top.Parent if top.Parent != nil { top.Parent.LastChild = n } } else if n.level > top.level { n.Parent = top if top.FirstChild == nil { top.FirstChild = n top.LastChild = n } else { t := top.LastChild t.NextSibling = n n.PrevSibling = t top.LastChild = n } } } switch v := x.(type) { case []interface{}: // JSON array for _, vv := range v { n := &Node{Type: ElementNode, level: level, value: vv} addNode(n) parseValue(vv, n, level+1) } case map[string]interface{}: // JSON object // The Go’s map iteration order is random. // (https://blog.golang.org/go-maps-in-action#Iteration-order) var keys []string for key := range v { keys = append(keys, key) } sort.Strings(keys) for _, key := range keys { n := &Node{Data: key, Type: ElementNode, level: level, value: v[key]} addNode(n) parseValue(v[key], n, level+1) } default: // JSON types: string, number, boolean n := &Node{Data: fmt.Sprintf("%v", v), Type: TextNode, level: level, value: fmt.Sprintf("%v", v)} addNode(n) } } func parse(b []byte) (*Node, error) { var v interface{} if err := json.Unmarshal(b, &v); err != nil { return nil, err } doc := &Node{Type: DocumentNode} parseValue(v, doc, 1) return doc, nil } // Parse JSON document. func Parse(r io.Reader) (*Node, error) { b, err := ioutil.ReadAll(r) if err != nil { return nil, err } return parse(b) } jsonquery-1.3.2/node_test.go000066400000000000000000000054311436446617300161100ustar00rootroot00000000000000package jsonquery import ( "reflect" "strings" "testing" ) func parseString(s string) (*Node, error) { return Parse(strings.NewReader(s)) } func TestParseJsonNumberArray(t *testing.T) { s := `[1,2,3,4,5,6]` doc, err := parseString(s) if err != nil { t.Fatal(err) } var values []float64 for _, n := range doc.ChildNodes() { values = append(values, n.Value().(float64)) } expected := []float64{1, 2, 3, 4, 5, 6} if p1, p2 := len(values), len(expected); p1 != p2 { t.Fatalf("got %d elements but expected %d", p1, p2) } if !reflect.DeepEqual(values, expected) { t.Fatalf("got %v but expected %v", values, expected) } } func TestParseJsonObject(t *testing.T) { s := `{ "name":"John", "age":31, "female":false }` doc, err := parseString(s) if err != nil { t.Fatal(err) } m := make(map[string]interface{}) for _, n := range doc.ChildNodes() { m[n.Data] = n.Value() } expected := []struct { name string value interface{} }{ {"name", "John"}, {"age", float64(31)}, {"female", false}, } for _, v := range expected { if e, g := v.value, m[v.name]; e != g { t.Fatalf("expected %s = %v(%T),but %s = %v(%t)", v.name, e, e, v.name, g, g) } } } func TestParseJsonObjectArray(t *testing.T) { s := `[ {"models":[ "Fiesta", "Focus", "Mustang" ] } ]` doc, err := parseString(s) if err != nil { t.Fatal(err) } first := doc.FirstChild models := first.SelectElement("models") if expected := reflect.ValueOf(models.Value()).Kind(); expected != reflect.Slice { t.Fatalf("expected models is slice(Array) but got %v", expected) } expected := []string{"Fiesta", "Focus", "Mustang"} var values []string for _, v := range models.Value().([]interface{}) { values = append(values, v.(string)) } if !reflect.DeepEqual(expected, values) { t.Fatalf("expected %v but got %v", expected, values) } } func TestParseJson(t *testing.T) { s := `{ "name":"John", "age":30, "cars": [ { "name":"Ford", "models":[ "Fiesta", "Focus", "Mustang" ] }, { "name":"BMW", "models":[ "320", "X3", "X5" ] }, { "name":"Fiat", "models":[ "500", "Panda" ] } ] }` doc, err := parseString(s) if err != nil { t.Fatal(err) } n := doc.SelectElement("name") if n == nil { t.Fatal("n is nil") } if n.NextSibling != nil { t.Fatal("next sibling shoud be nil") } if e, g := "John", n.InnerText(); e != g { t.Fatalf("expected %v but %v", e, g) } cars := doc.SelectElement("cars") if e, g := 3, len(cars.ChildNodes()); e != g { t.Fatalf("expected %v but %v", e, g) } } func TestLargeFloat(t *testing.T) { s := `{ "large_number": 365823929453 }` doc, err := parseString(s) if err != nil { t.Fatal(err) } n := doc.SelectElement("large_number") if n.Value() != float64(365823929453) { t.Fatalf("expected %v but %v", "365823929453", n.InnerText()) } } jsonquery-1.3.2/query.go000066400000000000000000000071321436446617300152710ustar00rootroot00000000000000package jsonquery import ( "fmt" "github.com/antchfx/xpath" ) var _ xpath.NodeNavigator = &NodeNavigator{} // CreateXPathNavigator creates a new xpath.NodeNavigator for the specified html.Node. func CreateXPathNavigator(top *Node) *NodeNavigator { return &NodeNavigator{cur: top, root: top} } // Find is like QueryAll but will panics if `expr` cannot be parsed. func Find(top *Node, expr string) []*Node { nodes, err := QueryAll(top, expr) if err != nil { panic(err) } return nodes } // FindOne is like Query but will panics if `expr` cannot be parsed. func FindOne(top *Node, expr string) *Node { node, err := Query(top, expr) if err != nil { panic(err) } return node } // QueryAll searches the Node that matches by the specified XPath expr. // Return an error if the expression `expr` cannot be parsed. func QueryAll(top *Node, expr string) ([]*Node, error) { exp, err := getQuery(expr) if err != nil { return nil, err } return QuerySelectorAll(top, exp), nil } // Query searches the Node that matches by the specified XPath expr, // and returns first element of matched. func Query(top *Node, expr string) (*Node, error) { exp, err := getQuery(expr) if err != nil { return nil, err } return QuerySelector(top, exp), nil } // QuerySelectorAll searches all of the Node that matches the specified XPath selectors. func QuerySelectorAll(top *Node, selector *xpath.Expr) []*Node { t := selector.Select(CreateXPathNavigator(top)) var elems []*Node for t.MoveNext() { elems = append(elems, (t.Current().(*NodeNavigator)).cur) } return elems } // QuerySelector returns the first matched XML Node by the specified XPath selector. func QuerySelector(top *Node, selector *xpath.Expr) *Node { t := selector.Select(CreateXPathNavigator(top)) if t.MoveNext() { return (t.Current().(*NodeNavigator)).cur } return nil } // NodeNavigator is for navigating JSON document. type NodeNavigator struct { root, cur *Node } func (a *NodeNavigator) Current() *Node { return a.cur } func (a *NodeNavigator) NodeType() xpath.NodeType { switch a.cur.Type { case TextNode: return xpath.TextNode case DocumentNode: return xpath.RootNode case ElementNode: return xpath.ElementNode default: panic(fmt.Sprintf("unknown node type %v", a.cur.Type)) } } func (a *NodeNavigator) LocalName() string { return a.cur.Data } func (a *NodeNavigator) Prefix() string { return "" } func (a *NodeNavigator) Value() string { return fmt.Sprintf("%v", a.cur.value) } func (a *NodeNavigator) GetValue() interface{} { return a.cur.value } func (a *NodeNavigator) Copy() xpath.NodeNavigator { n := *a return &n } func (a *NodeNavigator) MoveToRoot() { a.cur = a.root } func (a *NodeNavigator) MoveToParent() bool { if n := a.cur.Parent; n != nil { a.cur = n return true } return false } func (x *NodeNavigator) MoveToNextAttribute() bool { return false } func (a *NodeNavigator) MoveToChild() bool { if n := a.cur.FirstChild; n != nil { a.cur = n return true } return false } func (a *NodeNavigator) MoveToFirst() bool { for n := a.cur.PrevSibling; n != nil; n = n.PrevSibling { a.cur = n } return true } func (a *NodeNavigator) String() string { return a.Value() } func (a *NodeNavigator) MoveToNext() bool { if n := a.cur.NextSibling; n != nil { a.cur = n return true } return false } func (a *NodeNavigator) MoveToPrevious() bool { if n := a.cur.PrevSibling; n != nil { a.cur = n return true } return false } func (a *NodeNavigator) MoveTo(other xpath.NodeNavigator) bool { node, ok := other.(*NodeNavigator) if !ok || node.root != a.root { return false } a.cur = node.cur return true } jsonquery-1.3.2/query_test.go000066400000000000000000000130601436446617300163250ustar00rootroot00000000000000package jsonquery import ( "strings" "testing" "github.com/antchfx/xpath" ) func BenchmarkSelectorCache(b *testing.B) { DisableSelectorCache = false for i := 0; i < b.N; i++ { getQuery("/AAA/BBB/DDD/CCC/EEE/ancestor::*") } } func BenchmarkDisableSelectorCache(b *testing.B) { DisableSelectorCache = true for i := 0; i < b.N; i++ { getQuery("/AAA/BBB/DDD/CCC/EEE/ancestor::*") } } func TestNavigator(t *testing.T) { s := `{ "name":"John", "age":30, "cars": [ { "name":"Ford", "models":[ "Fiesta", "Focus", "Mustang" ] }, { "name":"BMW", "models":[ "320", "X3", "X5" ] }, { "name":"Fiat", "models":[ "500", "Panda" ] } ] }` doc, _ := parseString(s) nav := CreateXPathNavigator(doc) nav.MoveToRoot() if nav.NodeType() != xpath.RootNode { t.Fatal("node type is not RootNode") } // Move to first child(age). if e, g := true, nav.MoveToChild(); e != g { t.Fatalf("expected %v but %v", e, g) } if e, g := "age", nav.Current().Data; e != g { t.Fatalf("expected %v but %v", e, g) } if e, g := float64(30), nav.GetValue().(float64); e != g { t.Fatalf("expected %v but %v", e, g) } // Move to next sibling node(cars). if e, g := true, nav.MoveToNext(); e != g { t.Fatalf("expected %v but %v", e, g) } if e, g := "cars", nav.Current().Data; e != g { t.Fatalf("expected %v but %v", e, g) } m := make(map[string][]string) // Move to cars child node. cur := nav.Copy() for ok := nav.MoveToChild(); ok; ok = nav.MoveToNext() { // Move to node. // ...Ford cur1 := nav.Copy() var name string var models []string // name || models for ok := nav.MoveToChild(); ok; ok = nav.MoveToNext() { cur2 := nav.Copy() n := nav.Current() if n.Data == "name" { name = n.InnerText() } else { for ok := nav.MoveToChild(); ok; ok = nav.MoveToNext() { cur3 := nav.Copy() models = append(models, nav.Value()) nav.MoveTo(cur3) } } nav.MoveTo(cur2) } nav.MoveTo(cur1) m[name] = models } nav.MoveTo(cur) // move to name. if e, g := true, nav.MoveToNext(); e != g { t.Fatalf("expected %v but %v", e, g) } // move to cars nav.MoveToPrevious() if e, g := "cars", nav.Current().Data; e != g { t.Fatalf("expected %v but %v", e, g) } // move to age. nav.MoveToFirst() if e, g := "age", nav.Current().Data; e != g { t.Fatalf("expected %v but %v", e, g) } nav.MoveToParent() if g := nav.Current().Type; g != DocumentNode { t.Fatalf("node type is not DocumentNode") } } func TestToXML(t *testing.T) { s := `{ "name":"John", "age":31, "female":false }` doc, _ := Parse(strings.NewReader(s)) expected := `31falseJohn` if got := doc.OutputXML(); got != expected { t.Fatalf("expected %s, but got %s", expected, got) } } func TestArrayToXML(t *testing.T) { s := `[1,2,3,4]` doc, _ := Parse(strings.NewReader(s)) expected := `<1>1<2>2<3>3<4>4` if got := doc.OutputXML(); got != expected { t.Fatalf("expected %s, but got %s", expected, got) } } func TestNestToArray(t *testing.T) { s := `{ "address": { "city": "Nara", "postalCode": "630-0192", "streetAddress": "naist street" }, "age": 26, "name": "John", "phoneNumbers": [ { "number": "0123-4567-8888", "type": "iPhone" }, { "number": "0123-4567-8910", "type": "home" } ] }` doc, _ := Parse(strings.NewReader(s)) expected := `
Nara630-0192naist street
26John0123-4567-8888iPhone0123-4567-8910home
` if got := doc.OutputXML(); got != expected { t.Fatalf("expected \n%s, but got \n%s", expected, got) } } func TestQuery(t *testing.T) { doc, err := Parse(strings.NewReader(BooksExample)) if err != nil { t.Fatal(err) } q := "/store/bicycle" n := FindOne(doc, q) if n == nil { t.Fatal("should matched 1 but got nil") } q = "/store/bicycle/color" n = FindOne(doc, q) if n == nil { t.Fatal("should matched 1 but got nil") } if n.Data != "color" { t.Fatalf("expected data is color, but got %s", n.Data) } } func TestQueryWhere(t *testing.T) { doc, err := Parse(strings.NewReader(BooksExample)) if err != nil { t.Fatal(err) } // for number q := "//*[price<=12.99]" list := Find(doc, q) if got, expected := len(list), 3; got != expected { t.Fatalf("%s expected %d objects, but got %d", q, expected, got) } // for string q = "//*/isbn[text()='0-553-21311-3']" if n := FindOne(doc, q); n == nil { t.Fatal("should matched 1 but got nil") } else if n.Data != "isbn" { t.Fatalf("should matched `isbm` but got %s", n.Data) } } var BooksExample string = `{ "store": { "book": [ { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 }, { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 }, { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99 }, { "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99 } ], "bicycle": { "color": "red", "price": 19.95 } }, "expensive": 10 } `