Skip to content
This repository has been archived by the owner on Dec 12, 2023. It is now read-only.

Commit

Permalink
init xmltree package
Browse files Browse the repository at this point in the history
  • Loading branch information
jony4 committed Apr 21, 2021
0 parents commit 832a8bf
Show file tree
Hide file tree
Showing 85 changed files with 175,433 additions and 0 deletions.
68 changes: 68 additions & 0 deletions compare.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package xmltree

import (
"bytes"
"encoding/xml"
"sort"
)

// Equal returns true if two xmltree.Elements are equal, ignoring
// differences in white space, sub-element order, and namespace prefixes.
func Equal(a, b *Element) bool {
return equal(a, b, 0)
}

type byName []Element

func (l byName) Len() int { return len(l) }
func (l byName) Less(i, j int) bool {
return l[i].Name.Space+l[i].Name.Local < l[j].Name.Space+l[j].Name.Local
}
func (l byName) Swap(i, j int) { l[i], l[j] = l[j], l[i] }

func equal(a, b *Element, depth int) bool {
const maxDepth = 1000
if depth > maxDepth {
return false
}
if !equalElement(a, b) {
return false
}
if len(a.Children) != len(b.Children) {
return false
}
if len(a.Children) == 0 {
return bytes.Equal(bytes.TrimSpace(a.Content), bytes.TrimSpace(b.Content))
}
sort.Sort(byName(a.Children))
sort.Sort(byName(b.Children))
for i := range a.Children {
if !equal(&a.Children[i], &b.Children[i], depth+1) {
return false
}
}
return true
}

func equalElement(a, b *Element) bool {
if a.Name != b.Name {
return false
}
attrs := make(map[xml.Name]string)
for _, a := range a.StartElement.Attr {
if a.Name.Space == "xmlns" || a.Name.Local == "xmlns" {
continue
}
attrs[a.Name] = a.Value
}

for _, a := range b.StartElement.Attr {
if a.Name.Space == "xmlns" || a.Name.Local == "xmlns" {
continue
}
if v, ok := attrs[a.Name]; !ok || v != a.Value {
return false
}
}
return true
}
149 changes: 149 additions & 0 deletions element.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package xmltree

import (
"encoding/xml"
"fmt"
"log"
)

// An Element represents a single element in an XML document. Elements
// may have zero or more children. The byte array used by the Content
// field is shared among all elements in the document, and should not
// be modified. An Element also captures xml namespace prefixes, so
// that arbitrary QNames in attribute values can be resolved.
type Element struct {
xml.StartElement
// The XML namespace scope at this element's location in the
// document.
Scope
// The raw content contained within this element's start and
// end tags. Uses the underlying byte array passed to Parse.
Content []byte
// Sub-elements contained within this element.
Children []Element
}

// Attr gets the value of the first attribute whose name matches the
// space and local arguments. If space is the empty string, only
// attributes' local names are considered when looking for a match.
// If an attribute could not be found, the empty string is returned.
func (ele *Element) Attr(space, local string) string {
for _, v := range ele.StartElement.Attr {
if v.Name.Local != local {
continue
}
if space == "" || space == v.Name.Space {
return v.Value
}
}
return ""
}

func (ele *Element) parse(scanner *scanner, data []byte, depth int) error {
if depth > recursionLimit {
return errDeepXML
}
ele.StartElement.Attr = ele.pushNS(ele.StartElement)

begin := scanner.InputOffset()
end := begin
walk:
for scanner.scan() {
switch tok := scanner.tok.(type) {
case xml.StartElement:
child := Element{StartElement: tok.Copy(), Scope: ele.Scope}
if err := child.parse(scanner, data, depth+1); err != nil {
return err
}
ele.Children = append(ele.Children, child)
case xml.EndElement:
if tok.Name != ele.Name {
return fmt.Errorf("Expecting </%s>, got </%s>", ele.Prefix(ele.Name), ele.Prefix(tok.Name))
}
ele.Content = data[int(begin):int(end)]
break walk
}
end = scanner.InputOffset()
}
return scanner.err
}

// Flatten produces a slice of Element pointers referring to
// the children of el, and their children, in depth-first order.
func (ele *Element) Flatten() []*Element {
return ele.SearchFunc(func(*Element) bool { return true })
}

// SetAttr adds an XML attribute to an Element's existing Attributes.
// If the attribute already exists, it is replaced.
func (ele *Element) SetAttr(space, local, value string) {
for i, a := range ele.StartElement.Attr {
if a.Name.Local != local {
continue
}
if space == "" || a.Name.Space == space {
ele.StartElement.Attr[i].Value = value
return
}
}
ele.StartElement.Attr = append(ele.StartElement.Attr, xml.Attr{
Name: xml.Name{Space: space, Local: local},
Value: value,
})
}

// Search searches the Element tree for Elements with an xml tag
// matching the name and xml namespace. If space is the empty string,
// any namespace is matched.
func (ele *Element) Search(space, local string) []*Element {
return ele.SearchFunc(func(el *Element) bool {
if local != el.Name.Local {
return false
}
return space == "" || space == el.Name.Space
})
}

// SearchFunc traverses the Element tree in depth-first order and returns
// a slice of Elements for which the function fn returns true.
func (ele *Element) SearchFunc(fn func(*Element) bool) []*Element {
var results []*Element
var search func(el *Element)

search = func(el *Element) {
if fn(el) {
results = append(results, el)
}
log.Print(el.walk(search))
}
log.Print(ele.walk(search))
return results
}

// walkFunc is the type of the function called for each of an Element's
// children.
type walkFunc func(*Element)

// The walk method calls the walkFunc for each of the Element's children.
// If the WalkFunc returns a non-nil error, Walk will return it
// immediately.
func (ele *Element) walk(fn walkFunc) error {
for i := 0; i < len(ele.Children); i++ {
fn(&ele.Children[i])
}
return nil
}

type byXMLName []xml.Name

func (x byXMLName) Len() int { return len(x) }
func (x byXMLName) Less(i, j int) bool {
return x[i].Space+x[i].Local < x[j].Space+x[j].Local
}
func (x byXMLName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }

// String returns the XML encoding of an Element
// and its children as a string.
func (ele *Element) String() string {
return string(Marshal(ele))
}
Loading

0 comments on commit 832a8bf

Please sign in to comment.