pax_global_header 0000666 0000000 0000000 00000000064 13655307200 0014513 g ustar 00root root 0000000 0000000 52 comment=b8565c15b0757d4d4fbbeddba11d627a1ba08e62
jsonquery-1.1.4/ 0000775 0000000 0000000 00000000000 13655307200 0013555 5 ustar 00root root 0000000 0000000 jsonquery-1.1.4/.gitignore 0000664 0000000 0000000 00000000462 13655307200 0015547 0 ustar 00root root 0000000 0000000 # 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
*.prof jsonquery-1.1.4/.travis.yml 0000664 0000000 0000000 00000000352 13655307200 0015666 0 ustar 00root root 0000000 0000000 language: 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-ci jsonquery-1.1.4/LICENSE 0000664 0000000 0000000 00000001776 13655307200 0014575 0 ustar 00root root 0000000 0000000 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. jsonquery-1.1.4/README.md 0000664 0000000 0000000 00000007713 13655307200 0015044 0 ustar 00root root 0000000 0000000 jsonquery
====
[](https://travis-ci.org/antchfx/jsonquery)
[](https://coveralls.io/github/antchfx/jsonquery?branch=master)
[](https://godoc.org/github.com/antchfx/jsonquery)
[](https://goreportcard.com/report/github.com/antchfx/jsonquery)
Overview
===
jsonquery is an XPath query package for JSON document, lets you extract data from JSON documents through an XPath expression. Built-in XPath expression cache avoid re-compile XPath expression each query.
Getting Started
===
### Install Package
```
go get github.com/antchfx/jsonquery
```
#### Load JSON document from URL.
```go
doc, err := jsonquery.LoadURL("http://www.example.com/feed?json")
```
#### Load JSON document from string.
```go
s :=`{
"name":"John",
"age":31,
"city":"New York"
}`
doc, err := jsonquery.Parse(strings.NewReader(s))
```
#### Load JSON document from io.Reader.
```go
f, err := os.Open("./books.json")
doc, err := jsonquery.Parse(f)
```
#### Find authors of all books in the store.
```go
list := jsonquery.Find(doc, "store/book/*/author")
// or equal to
list := jsonquery.Find(doc, "//author")
// or by QueryAll()
nodes, err := jsonquery.QueryAll(doc, "//a")
```
#### Find the third book.
```go
book := jsonquery.Find(doc, "//book/*[3]")
```
#### Find the last book.
```go
book := jsonquery.Find(doc, "//book/*[last()]")
```
#### Find all books that have an isbn number.
```go
list := jsonquery.Find(doc, "//book/*[isbn]")
```
#### Find all books priced less than 10.
```go
list := jsonquery.Find(doc, "//book/*[price<10]")
```
Examples
===
```go
func main() {
s := `{
"name": "John",
"age" : 26,
"address" : {
"streetAddress": "naist street",
"city" : "Nara",
"postalCode" : "630-0192"
},
"phoneNumbers": [
{
"type" : "iPhone",
"number": "0123-4567-8888"
},
{
"type" : "home",
"number": "0123-4567-8910"
}
]
}`
doc, err := jsonquery.Parse(strings.NewReader(s))
if err != nil {
panic(err)
}
name := jsonquery.FindOne(doc, "name")
fmt.Printf("name: %s\n", name.InnerText())
var a []string
for _, n := range jsonquery.Find(doc, "phoneNumbers/*/number") {
a = append(a, n.InnerText())
}
fmt.Printf("phone number: %s\n", strings.Join(a, ","))
if n := jsonquery.FindOne(doc, "address/streetAddress"); n != nil {
fmt.Printf("address: %s\n", n.InnerText())
}
}
```
Implement Principle
===
If you are familiar with XPath and XML, you can easily figure out how to
write your XPath expression.
```json
{
"name":"John",
"age":30,
"cars": [
{ "name":"Ford", "models":[ "Fiesta", "Focus", "Mustang" ] },
{ "name":"BMW", "models":[ "320", "X3", "X5" ] },
{ "name":"Fiat", "models":[ "500", "Panda" ] }
]
}
```
The above JSON document will be convert to similar to XML document by the *JSONQuery*, like below:
```XML
John
30
Ford
Fiesta
Focus
Mustang
BMW
320
X3
X5
Fiat
500
Panda
```
Notes: `element` is empty element that have no any name.
List of XPath query packages
===
|Name |Description |
|--------------------------|----------------|
|[htmlquery](https://github.com/antchfx/htmlquery) | XPath query package for the HTML document|
|[xmlquery](https://github.com/antchfx/xmlquery) | XPath query package for the XML document|
|[jsonquery](https://github.com/antchfx/jsonquery) | XPath query package for the JSON document|
jsonquery-1.1.4/books.json 0000664 0000000 0000000 00000001714 13655307200 0015570 0 ustar 00root root 0000000 0000000 {
"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
}
}
} jsonquery-1.1.4/cache.go 0000664 0000000 0000000 00000001623 13655307200 0015151 0 ustar 00root root 0000000 0000000 package 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.1.4/go.mod 0000664 0000000 0000000 00000000234 13655307200 0014662 0 ustar 00root root 0000000 0000000 module github.com/antchfx/jsonquery
go 1.14
require (
github.com/antchfx/xpath v1.1.7
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
)
jsonquery-1.1.4/go.sum 0000664 0000000 0000000 00000001065 13655307200 0014712 0 ustar 00root root 0000000 0000000 github.com/antchfx/xpath v1.1.6 h1:6sVh6hB5T6phw1pFpHRQ+C4bd8sNI+O58flqtg7h0R0=
github.com/antchfx/xpath v1.1.6/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
github.com/antchfx/xpath v1.1.7 h1:RgnAdTaRzF4bBiTqdDA7ZQ7IU8ivc72KSTf3/XCA/ic=
github.com/antchfx/xpath v1.1.7/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
jsonquery-1.1.4/node.go 0000664 0000000 0000000 00000010046 13655307200 0015032 0 ustar 00root root 0000000 0000000 package jsonquery
import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"sort"
"strconv"
)
// 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
}
// 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 gets the value of the node and all its child nodes.
func (n *Node) InnerText() string {
var output func(*bytes.Buffer, *Node)
output = func(buf *bytes.Buffer, n *Node) {
if n.Type == TextNode {
buf.WriteString(n.Data)
return
}
for child := n.FirstChild; child != nil; child = child.NextSibling {
output(buf, child)
}
}
var buf bytes.Buffer
output(&buf, n)
return buf.String()
}
func outputXML(buf *bytes.Buffer, n *Node) {
switch n.Type {
case ElementNode:
if n.Data == "" {
buf.WriteString("")
} else {
buf.WriteString("<" + n.Data + ">")
}
case TextNode:
buf.WriteString(n.Data)
return
}
for child := n.FirstChild; child != nil; child = child.NextSibling {
outputXML(buf, child)
}
if n.Data == "" {
buf.WriteString("")
} else {
buf.WriteString("" + n.Data + ">")
}
}
// OutputXML prints the XML string.
func (n *Node) OutputXML() string {
var buf bytes.Buffer
buf.WriteString(``)
for n := n.FirstChild; n != nil; n = n.NextSibling {
outputXML(&buf, n)
}
return buf.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{}:
for _, vv := range v {
n := &Node{Type: ElementNode, level: level}
addNode(n)
parseValue(vv, n, level+1)
}
case map[string]interface{}:
// 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}
addNode(n)
parseValue(v[key], n, level+1)
}
case string:
n := &Node{Data: v, Type: TextNode, level: level}
addNode(n)
case float64:
s := strconv.FormatFloat(v, 'f', -1, 64)
n := &Node{Data: s, Type: TextNode, level: level}
addNode(n)
case bool:
s := strconv.FormatBool(v)
n := &Node{Data: s, Type: TextNode, level: level}
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.1.4/node_test.go 0000664 0000000 0000000 00000007221 13655307200 0016072 0 ustar 00root root 0000000 0000000 package jsonquery
import (
"sort"
"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)
}
// output like below:
// 1
// 2
// ...
// 6
if e, g := 6, len(doc.ChildNodes()); e != g {
t.Fatalf("excepted %v but got %v", e, g)
}
var v []string
for _, n := range doc.ChildNodes() {
v = append(v, n.InnerText())
}
if got, expected := strings.Join(v, ","), "1,2,3,4,5,6"; got != expected {
t.Fatalf("got %v but expected %v", got, expected)
}
}
func TestParseJsonObject(t *testing.T) {
s := `{
"name":"John",
"age":31,
"city":"New York"
}`
doc, err := parseString(s)
if err != nil {
t.Fatal(err)
}
// output like below:
// John
// 31
// New York
m := make(map[string]string)
for _, n := range doc.ChildNodes() {
m[n.Data] = n.InnerText()
}
expected := []struct {
name, value string
}{
{"name", "John"},
{"age", "31"},
{"city", "New York"},
}
for _, v := range expected {
if e, g := v.value, m[v.name]; e != g {
t.Fatalf("expected %v=%v,but %v=%v", v.name, e, v.name, g)
}
}
}
func TestParseJsonObjectArray(t *testing.T) {
s := `[
{ "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)
}
/**
Ford
Fiesta
Focus
Mustang
BMW
320
X3
X5
....
*/
if e, g := 3, len(doc.ChildNodes()); e != g {
t.Fatalf("expected %v, but %v", e, g)
}
m := make(map[string][]string)
for _, n := range doc.ChildNodes() {
// Go to the next of the element list.
var name string
var models []string
for _, e := range n.ChildNodes() {
if e.Data == "name" {
// a name node.
name = e.InnerText()
} else {
// a models node.
for _, k := range e.ChildNodes() {
models = append(models, k.InnerText())
}
}
}
// Sort models list.
sort.Strings(models)
m[name] = models
}
expected := []struct {
name, value string
}{
{"Ford", "Fiesta,Focus,Mustang"},
{"BMW", "320,X3,X5"},
{"Fiat", "500,Panda"},
}
for _, v := range expected {
if e, g := v.value, strings.Join(m[v.name], ","); e != g {
t.Fatalf("expected %v=%v,but %v=%v", v.name, e, v.name, g)
}
}
}
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.InnerText() != "365823929453" {
t.Fatalf("expected %v but %v", "365823929453", n.InnerText())
}
}
jsonquery-1.1.4/query.go 0000664 0000000 0000000 00000007140 13655307200 0015253 0 ustar 00root root 0000000 0000000 package 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 {
switch a.cur.Type {
case ElementNode:
return a.cur.InnerText()
case TextNode:
return a.cur.Data
}
return ""
}
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.1.4/query_test.go 0000664 0000000 0000000 00000006016 13655307200 0016313 0 ustar 00root root 0000000 0000000 package 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)
/**
30
...
Ford
...
BMW
...
Fiat
John
*/
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 := "30", nav.Value(); 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
}
expected := []struct {
name, value string
}{
{"Ford", "Fiesta,Focus,Mustang"},
{"BMW", "320,X3,X5"},
{"Fiat", "500,Panda"},
}
for _, v := range expected {
if e, g := v.value, strings.Join(m[v.name], ","); e != g {
t.Fatalf("expected %v=%v,but %v=%v", v.name, e, v.name, g)
}
}
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")
}
}