Skip to content

Commit

Permalink
Support xpath query
Browse files Browse the repository at this point in the history
  • Loading branch information
subchen committed Sep 28, 2016
1 parent 06a6969 commit 3a6d72c
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 0 deletions.
29 changes: 29 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,35 @@ func ExampleNode_GetChildren() {
// testcase: name = ExampleParse
}

func ExampleNode_Query() {
node := xmldom.Must(xmldom.ParseXML(ExampleXml)).Root
// xpath expr: https://github.com/antchfx/xpath

// find all children
fmt.Printf("children = %v\n", len(node.Query("//*")))

// find node matched tag name
nodeList := node.Query("//testcase")
for _, c := range nodeList {
fmt.Printf("%v: name = %v\n", c.Name, c.GetAttributeValue("name"))
}
// Output:
// children = 5
// testcase: name = ExampleParseXML
// testcase: name = ExampleParse
}

func ExampleNode_QueryOne() {
node := xmldom.Must(xmldom.ParseXML(ExampleXml)).Root
// xpath expr: https://github.com/antchfx/xpath

// find node matched attr name
c := node.QueryOne("//testcase[@name='ExampleParseXML']")
fmt.Printf("%v: name = %v\n", c.Name, c.GetAttributeValue("name"))
// Output:
// testcase: name = ExampleParseXML
}

func ExampleDocument_XML() {
doc := xmldom.Must(xmldom.ParseXML(ExampleXml))
fmt.Println(doc.XML())
Expand Down
12 changes: 12 additions & 0 deletions node.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,18 @@ func (n *Node) FindByID(id string) *Node {
return nil
}

func (n *Node) Query(xpath string) []*Node {
return xpathQuery(n, xpath)
}

func (n *Node) QueryOne(xpath string) *Node {
return xpathQueryOne(n, xpath)
}

func (n *Node) QueryEach(xpath string, cb func(int, *Node)) {
xpathQueryEach(n, xpath, cb)
}

func (n *Node) XML() string {
buf := new(bytes.Buffer)
printXML(buf, n, 0, "")
Expand Down
150 changes: 150 additions & 0 deletions query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package xmldom

import (
"github.com/antchfx/xpath"
)

// createXPathNavigator creates a new xpath.NodeNavigator for the specified xmldom.Node.
func createXPathNavigator(top *Node) xpath.NodeNavigator {
return &xmlNodeNavigator{curr: top, attrIndex: -1}
}

// xpathQuery searches the Node that matches by the specified XPath expr.
func xpathQuery(top *Node, expr string) []*Node {
t := xpath.Select(createXPathNavigator(top), expr)
var nodes []*Node
for t.MoveNext() {
nodes = append(nodes, (t.Current().(*xmlNodeNavigator)).curr)
}
return nodes
}

// xpathQueryOne searches the Node that matches by the specified XPath expr,
// and returns first element of matched.
func xpathQueryOne(top *Node, expr string) *Node {
t := xpath.Select(createXPathNavigator(top), expr)
if t.MoveNext() {
return (t.Current().(*xmlNodeNavigator)).curr
}
return nil
}

// xpathQueryEach searches the xmldom.Node and calls functions cb.
func xpathQueryEach(top *Node, expr string, cb func(int, *Node)) {
t := xpath.Select(createXPathNavigator(top), expr)
var i int
for t.MoveNext() {
cb(i, (t.Current().(*xmlNodeNavigator)).curr)
i++
}
}

type xmlNodeNavigator struct {
curr *Node
attrIndex int
}

func (x *xmlNodeNavigator) NodeType() xpath.NodeType {
if x.curr == x.curr.Root() {
return xpath.RootNode
}
if x.attrIndex != -1 {
return xpath.AttributeNode
}
return xpath.ElementNode
}

func (x *xmlNodeNavigator) LocalName() string {
if x.attrIndex != -1 {
return x.curr.Attributes[x.attrIndex].Name
}
return x.curr.Name
}

func (x *xmlNodeNavigator) Prefix() string {
return ""
}

func (x *xmlNodeNavigator) Value() string {
if x.attrIndex != -1 {
return x.curr.Attributes[x.attrIndex].Value
}
return x.curr.Text
}

func (x *xmlNodeNavigator) Copy() xpath.NodeNavigator {
n := *x
return &n
}

func (x *xmlNodeNavigator) MoveToRoot() {
x.curr = x.curr.Root()
}

func (x *xmlNodeNavigator) MoveToParent() bool {
if node := x.curr.Parent; node != nil {
x.curr = node
return true
}
return false
}

func (x *xmlNodeNavigator) MoveToNextAttribute() bool {
if x.attrIndex >= len(x.curr.Attributes)-1 {
return false
}
x.attrIndex++
return true
}

func (x *xmlNodeNavigator) MoveToChild() bool {
if node := x.curr.FirstChild(); node != nil {
x.curr = node
return true
}
return false
}

func (x *xmlNodeNavigator) MoveToFirst() bool {
if x.curr.Parent != nil {
node := x.curr.Parent.FirstChild()
if node != nil {
x.curr = node
return true
}
}
return false
}

func (x *xmlNodeNavigator) MoveToPrevious() bool {
node := x.curr.PrevSibling()
if node != nil {
x.curr = node
return true
}
return false
}

func (x *xmlNodeNavigator) MoveToNext() bool {
node := x.curr.NextSibling()
if node != nil {
x.curr = node
return true
}
return false
}

func (x *xmlNodeNavigator) MoveTo(other xpath.NodeNavigator) bool {
node, ok := other.(*xmlNodeNavigator)
if !ok || node.curr.Root() != x.curr.Root() {
return false
}

x.curr = node.curr
x.attrIndex = node.attrIndex
return true
}

func (x *xmlNodeNavigator) String() string {
return x.Value()
}

0 comments on commit 3a6d72c

Please sign in to comment.