You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
619 lines
13 KiB
619 lines
13 KiB
// This work is subject to the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication |
|
// license. Its contents can be found at: |
|
// http://creativecommons.org/publicdomain/zero/1.0/ |
|
|
|
package xmlx |
|
|
|
import ( |
|
"bytes" |
|
"encoding/xml" |
|
"fmt" |
|
"strconv" |
|
"strings" |
|
) |
|
|
|
const ( |
|
NT_ROOT = iota |
|
NT_DIRECTIVE |
|
NT_PROCINST |
|
NT_COMMENT |
|
NT_TEXT |
|
NT_ELEMENT |
|
) |
|
|
|
// IndentPrefix holds the value for a single identation level, if one |
|
// chooses to want indentation in the node.String() and node.Bytes() output. |
|
// This would normally be set to a single tab, or a number of spaces. |
|
var IndentPrefix = "" |
|
|
|
type Attr struct { |
|
Name xml.Name // Attribute namespace and name. |
|
Value string // Attribute value. |
|
} |
|
|
|
type Node struct { |
|
Type byte // Node type. |
|
Name xml.Name // Node namespace and name. |
|
Children []*Node // Child nodes. |
|
Attributes []*Attr // Node attributes. |
|
Parent *Node // Parent node. |
|
Value string // Node value. |
|
Target string // procinst field. |
|
} |
|
|
|
func NewNode(tid byte) *Node { |
|
n := new(Node) |
|
n.Type = tid |
|
n.Children = make([]*Node, 0, 10) |
|
n.Attributes = make([]*Attr, 0, 10) |
|
return n |
|
} |
|
|
|
// This wraps the standard xml.Unmarshal function and supplies this particular |
|
// node as the content to be unmarshalled. |
|
func (this *Node) Unmarshal(obj interface{}) error { |
|
return xml.NewDecoder(bytes.NewBuffer(this.bytes())).Decode(obj) |
|
} |
|
|
|
func (this *Node) GetValue() string { |
|
res := "" |
|
for _, node := range this.Children { |
|
if node.Type == NT_TEXT { |
|
res += strings.TrimSpace(node.Value) |
|
} |
|
} |
|
|
|
return res |
|
} |
|
|
|
// SetValue sets the value of the node to the given parameter. |
|
// It deletes all children of the node so the old data does not get back at node.GetValue |
|
func (this *Node) SetValue(val string) { |
|
t := NewNode(NT_TEXT) |
|
t.Value = val |
|
t.Parent = this |
|
this.Children = []*Node{t} // brutally replace all other children |
|
} |
|
|
|
// Get node value as string |
|
func (this *Node) S(namespace, name string) string { |
|
foundNode := rec_SelectNode(this, namespace, name) |
|
if foundNode == nil { |
|
return "" |
|
} else { |
|
return foundNode.GetValue() |
|
} |
|
} |
|
|
|
// Get node value as int |
|
func (this *Node) I(namespace, name string) int { |
|
value := this.S(namespace, name) |
|
if value != "" { |
|
n, _ := strconv.ParseInt(value, 10, 0) |
|
return int(n) |
|
} |
|
return 0 |
|
} |
|
|
|
// Get node value as int8 |
|
func (this *Node) I8(namespace, name string) int8 { |
|
value := this.S(namespace, name) |
|
if value != "" { |
|
n, _ := strconv.ParseInt(value, 10, 8) |
|
return int8(n) |
|
} |
|
return 0 |
|
} |
|
|
|
// Get node value as int16 |
|
func (this *Node) I16(namespace, name string) int16 { |
|
value := this.S(namespace, name) |
|
if value != "" { |
|
n, _ := strconv.ParseInt(value, 10, 16) |
|
return int16(n) |
|
} |
|
return 0 |
|
} |
|
|
|
// Get node value as int32 |
|
func (this *Node) I32(namespace, name string) int32 { |
|
value := this.S(namespace, name) |
|
if value != "" { |
|
n, _ := strconv.ParseInt(value, 10, 32) |
|
return int32(n) |
|
} |
|
return 0 |
|
} |
|
|
|
// Get node value as int64 |
|
func (this *Node) I64(namespace, name string) int64 { |
|
value := this.S(namespace, name) |
|
if value != "" { |
|
n, _ := strconv.ParseInt(value, 10, 64) |
|
return n |
|
} |
|
return 0 |
|
} |
|
|
|
// Get node value as uint |
|
func (this *Node) U(namespace, name string) uint { |
|
value := this.S(namespace, name) |
|
if value != "" { |
|
n, _ := strconv.ParseUint(value, 10, 0) |
|
return uint(n) |
|
} |
|
return 0 |
|
} |
|
|
|
// Get node value as uint8 |
|
func (this *Node) U8(namespace, name string) uint8 { |
|
value := this.S(namespace, name) |
|
if value != "" { |
|
n, _ := strconv.ParseUint(value, 10, 8) |
|
return uint8(n) |
|
} |
|
return 0 |
|
} |
|
|
|
// Get node value as uint16 |
|
func (this *Node) U16(namespace, name string) uint16 { |
|
value := this.S(namespace, name) |
|
if value != "" { |
|
n, _ := strconv.ParseUint(value, 10, 16) |
|
return uint16(n) |
|
} |
|
return 0 |
|
} |
|
|
|
// Get node value as uint32 |
|
func (this *Node) U32(namespace, name string) uint32 { |
|
value := this.S(namespace, name) |
|
if value != "" { |
|
n, _ := strconv.ParseUint(value, 10, 32) |
|
return uint32(n) |
|
} |
|
return 0 |
|
} |
|
|
|
// Get node value as uint64 |
|
func (this *Node) U64(namespace, name string) uint64 { |
|
value := this.S(namespace, name) |
|
if value != "" { |
|
n, _ := strconv.ParseUint(value, 10, 64) |
|
return n |
|
} |
|
return 0 |
|
} |
|
|
|
// Get node value as float32 |
|
func (this *Node) F32(namespace, name string) float32 { |
|
value := this.S(namespace, name) |
|
if value != "" { |
|
n, _ := strconv.ParseFloat(value, 32) |
|
return float32(n) |
|
} |
|
return 0 |
|
} |
|
|
|
// Get node value as float64 |
|
func (this *Node) F64(namespace, name string) float64 { |
|
value := this.S(namespace, name) |
|
if value != "" { |
|
n, _ := strconv.ParseFloat(value, 64) |
|
return n |
|
} |
|
return 0 |
|
} |
|
|
|
// Get node value as bool |
|
func (this *Node) B(namespace, name string) bool { |
|
value := this.S(namespace, name) |
|
if value != "" { |
|
n, _ := strconv.ParseBool(value) |
|
return n |
|
} |
|
return false |
|
} |
|
|
|
// Get attribute value as string |
|
func (this *Node) As(namespace, name string) string { |
|
for _, v := range this.Attributes { |
|
if (namespace == "*" || namespace == v.Name.Space) && name == v.Name.Local { |
|
return v.Value |
|
} |
|
} |
|
return "" |
|
} |
|
|
|
// Get attribute value as int |
|
func (this *Node) Ai(namespace, name string) int { |
|
s := this.As(namespace, name) |
|
if s != "" { |
|
n, _ := strconv.ParseInt(s, 10, 0) |
|
return int(n) |
|
} |
|
return 0 |
|
} |
|
|
|
// Get attribute value as int8 |
|
func (this *Node) Ai8(namespace, name string) int8 { |
|
s := this.As(namespace, name) |
|
if s != "" { |
|
n, _ := strconv.ParseInt(s, 10, 8) |
|
return int8(n) |
|
} |
|
return 0 |
|
} |
|
|
|
// Get attribute value as int16 |
|
func (this *Node) Ai16(namespace, name string) int16 { |
|
s := this.As(namespace, name) |
|
if s != "" { |
|
n, _ := strconv.ParseInt(s, 10, 16) |
|
return int16(n) |
|
} |
|
return 0 |
|
} |
|
|
|
// Get attribute value as int32 |
|
func (this *Node) Ai32(namespace, name string) int32 { |
|
s := this.As(namespace, name) |
|
if s != "" { |
|
n, _ := strconv.ParseInt(s, 10, 32) |
|
return int32(n) |
|
} |
|
return 0 |
|
} |
|
|
|
// Get attribute value as int64 |
|
func (this *Node) Ai64(namespace, name string) int64 { |
|
s := this.As(namespace, name) |
|
if s != "" { |
|
n, _ := strconv.ParseInt(s, 10, 64) |
|
return n |
|
} |
|
return 0 |
|
} |
|
|
|
// Get attribute value as uint |
|
func (this *Node) Au(namespace, name string) uint { |
|
s := this.As(namespace, name) |
|
if s != "" { |
|
n, _ := strconv.ParseUint(s, 10, 0) |
|
return uint(n) |
|
} |
|
return 0 |
|
} |
|
|
|
// Get attribute value as uint8 |
|
func (this *Node) Au8(namespace, name string) uint8 { |
|
s := this.As(namespace, name) |
|
if s != "" { |
|
n, _ := strconv.ParseUint(s, 10, 8) |
|
return uint8(n) |
|
} |
|
return 0 |
|
} |
|
|
|
// Get attribute value as uint16 |
|
func (this *Node) Au16(namespace, name string) uint16 { |
|
s := this.As(namespace, name) |
|
if s != "" { |
|
n, _ := strconv.ParseUint(s, 10, 16) |
|
return uint16(n) |
|
} |
|
return 0 |
|
} |
|
|
|
// Get attribute value as uint32 |
|
func (this *Node) Au32(namespace, name string) uint32 { |
|
s := this.As(namespace, name) |
|
if s != "" { |
|
n, _ := strconv.ParseUint(s, 10, 32) |
|
return uint32(n) |
|
} |
|
return 0 |
|
} |
|
|
|
// Get attribute value as uint64 |
|
func (this *Node) Au64(namespace, name string) uint64 { |
|
s := this.As(namespace, name) |
|
if s != "" { |
|
n, _ := strconv.ParseUint(s, 10, 64) |
|
return n |
|
} |
|
return 0 |
|
} |
|
|
|
// Get attribute value as float32 |
|
func (this *Node) Af32(namespace, name string) float32 { |
|
s := this.As(namespace, name) |
|
if s != "" { |
|
n, _ := strconv.ParseFloat(s, 32) |
|
return float32(n) |
|
} |
|
return 0 |
|
} |
|
|
|
// Get attribute value as float64 |
|
func (this *Node) Af64(namespace, name string) float64 { |
|
s := this.As(namespace, name) |
|
if s != "" { |
|
n, _ := strconv.ParseFloat(s, 64) |
|
return n |
|
} |
|
return 0 |
|
} |
|
|
|
// Get attribute value as bool |
|
func (this *Node) Ab(namespace, name string) bool { |
|
s := this.As(namespace, name) |
|
if s != "" { |
|
n, _ := strconv.ParseBool(s) |
|
return n |
|
} |
|
return false |
|
} |
|
|
|
// Returns true if this node has the specified attribute. False otherwise. |
|
func (this *Node) HasAttr(namespace, name string) bool { |
|
for _, v := range this.Attributes { |
|
if namespace != "*" && namespace != v.Name.Space { |
|
continue |
|
} |
|
|
|
if name == "*" || name == v.Name.Local { |
|
return true |
|
} |
|
} |
|
|
|
return false |
|
} |
|
|
|
// Select single node by name |
|
func (this *Node) SelectNode(namespace, name string) *Node { |
|
return rec_SelectNode(this, namespace, name) |
|
} |
|
|
|
func rec_SelectNode(cn *Node, namespace, name string) *Node { |
|
if (namespace == "*" || cn.Name.Space == namespace) && (name == "*" || cn.Name.Local == name) { |
|
return cn |
|
} |
|
|
|
var tn *Node |
|
for _, v := range cn.Children { |
|
if tn = rec_SelectNode(v, namespace, name); tn != nil { |
|
return tn |
|
} |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// Select multiple nodes by name |
|
func (this *Node) SelectNodes(namespace, name string) []*Node { |
|
list := make([]*Node, 0, 16) |
|
rec_SelectNodes(this, namespace, name, &list, false) |
|
return list |
|
} |
|
|
|
// Select multiple nodes directly under this node, by name. |
|
func (this *Node) SelectNodesDirect(namespace, name string) []*Node { |
|
list := make([]*Node, 0, 16) |
|
|
|
for _, v := range this.Children { |
|
if (namespace == "*" || v.Name.Space == namespace) && (name == "*" || v.Name.Local == name) { |
|
list = append(list, v) |
|
} |
|
} |
|
|
|
return list |
|
} |
|
|
|
// Select multiple nodes by name |
|
func (this *Node) SelectNodesRecursive(namespace, name string) []*Node { |
|
list := make([]*Node, 0, 16) |
|
rec_SelectNodes(this, namespace, name, &list, true) |
|
return list |
|
} |
|
|
|
func rec_SelectNodes(cn *Node, namespace, name string, list *[]*Node, recurse bool) { |
|
if (namespace == "*" || cn.Name.Space == namespace) && (name == "*" || cn.Name.Local == name) { |
|
*list = append(*list, cn) |
|
if !recurse { |
|
return |
|
} |
|
} |
|
|
|
for _, v := range cn.Children { |
|
rec_SelectNodes(v, namespace, name, list, recurse) |
|
} |
|
} |
|
|
|
func (this *Node) RemoveNameSpace() { |
|
this.Name.Space = "" |
|
// this.RemoveAttr("xmlns") //This is questionable |
|
|
|
for _, v := range this.Children { |
|
v.RemoveNameSpace() |
|
} |
|
} |
|
|
|
func (this *Node) RemoveAttr(name string) { |
|
for i, v := range this.Attributes { |
|
if name == v.Name.Local { |
|
//Delete it |
|
this.Attributes = append(this.Attributes[:i], this.Attributes[i+1:]...) |
|
} |
|
} |
|
} |
|
|
|
func (this *Node) SetAttr(name, value string) { |
|
for _, v := range this.Attributes { |
|
if name == v.Name.Local { |
|
v.Value = value |
|
return |
|
} |
|
} |
|
//Add |
|
attr := new(Attr) |
|
attr.Name.Local = name |
|
attr.Name.Space = "" |
|
attr.Value = value |
|
this.Attributes = append(this.Attributes, attr) |
|
return |
|
} |
|
|
|
// Convert node to appropriate []byte representation based on it's @Type. |
|
// Note that NT_ROOT is a special-case empty node used as the root for a |
|
// Document. This one has no representation by itself. It merely forwards the |
|
// String() call to it's child nodes. |
|
func (this *Node) Bytes() []byte { return this.bytes() } |
|
|
|
func (this *Node) bytes() (b []byte) { |
|
switch this.Type { |
|
case NT_PROCINST: |
|
b = this.printProcInst() |
|
case NT_COMMENT: |
|
b = this.printComment() |
|
case NT_DIRECTIVE: |
|
b = this.printDirective() |
|
case NT_ELEMENT: |
|
b = this.printElement() |
|
case NT_TEXT: |
|
b = this.printText() |
|
case NT_ROOT: |
|
b = this.printRoot() |
|
} |
|
return |
|
} |
|
|
|
// Convert node to appropriate string representation based on it's @Type. |
|
// Note that NT_ROOT is a special-case empty node used as the root for a |
|
// Document. This one has no representation by itself. It merely forwards the |
|
// String() call to it's child nodes. |
|
func (this *Node) String() (s string) { |
|
return string(this.bytes()) |
|
} |
|
|
|
func (this *Node) printRoot() []byte { |
|
var b bytes.Buffer |
|
for _, v := range this.Children { |
|
b.Write(v.bytes()) |
|
} |
|
return b.Bytes() |
|
} |
|
|
|
func (this *Node) printProcInst() []byte { |
|
return []byte("<?" + this.Target + " " + this.Value + "?>") |
|
} |
|
|
|
func (this *Node) printComment() []byte { |
|
return []byte("<!-- " + this.Value + " -->") |
|
} |
|
|
|
func (this *Node) printDirective() []byte { |
|
return []byte("<!" + this.Value + "!>") |
|
} |
|
|
|
func (this *Node) printText() []byte { |
|
val := []byte(this.Value) |
|
if len(this.Parent.Children) > 1 { |
|
return val |
|
} |
|
var b bytes.Buffer |
|
xml.EscapeText(&b, val) |
|
return b.Bytes() |
|
} |
|
|
|
func (this *Node) printElement() []byte { |
|
var b bytes.Buffer |
|
|
|
if len(this.Name.Space) > 0 { |
|
b.WriteRune('<') |
|
b.WriteString(this.Name.Space) |
|
b.WriteRune(':') |
|
b.WriteString(this.Name.Local) |
|
} else { |
|
b.WriteRune('<') |
|
b.WriteString(this.Name.Local) |
|
} |
|
|
|
for _, v := range this.Attributes { |
|
if len(v.Name.Space) > 0 { |
|
prefix := this.spacePrefix(v.Name.Space) |
|
b.WriteString(fmt.Sprintf(` %s:%s="%s"`, prefix, v.Name.Local, v.Value)) |
|
} else { |
|
b.WriteString(fmt.Sprintf(` %s="%s"`, v.Name.Local, v.Value)) |
|
} |
|
} |
|
|
|
if len(this.Children) == 0 && len(this.Value) == 0 { |
|
b.WriteString(" />") |
|
return b.Bytes() |
|
} |
|
|
|
b.WriteRune('>') |
|
|
|
for _, v := range this.Children { |
|
b.Write(v.bytes()) |
|
} |
|
|
|
xml.EscapeText(&b, []byte(this.Value)) |
|
if len(this.Name.Space) > 0 { |
|
b.WriteString("</") |
|
b.WriteString(this.Name.Space) |
|
b.WriteRune(':') |
|
b.WriteString(this.Name.Local) |
|
b.WriteRune('>') |
|
} else { |
|
b.WriteString("</") |
|
b.WriteString(this.Name.Local) |
|
b.WriteRune('>') |
|
} |
|
|
|
return b.Bytes() |
|
} |
|
|
|
// spacePrefix resolves the given space (e.g. a url) to the prefix it was |
|
// assigned by an attribute by the current node, or one of its parents. |
|
func (this *Node) spacePrefix(space string) string { |
|
for _, attr := range this.Attributes { |
|
if attr.Name.Space == "xmlns" && attr.Value == space { |
|
return attr.Name.Local |
|
} |
|
} |
|
if this.Parent == nil { |
|
return space |
|
} |
|
return this.Parent.spacePrefix(space) |
|
} |
|
|
|
// Add a child node |
|
func (this *Node) AddChild(t *Node) { |
|
if t.Parent != nil { |
|
t.Parent.RemoveChild(t) |
|
} |
|
t.Parent = this |
|
this.Children = append(this.Children, t) |
|
} |
|
|
|
// Remove a child node |
|
func (this *Node) RemoveChild(t *Node) { |
|
p := -1 |
|
for i, v := range this.Children { |
|
if v == t { |
|
p = i |
|
break |
|
} |
|
} |
|
|
|
if p == -1 { |
|
return |
|
} |
|
|
|
copy(this.Children[p:], this.Children[p+1:]) |
|
this.Children = this.Children[0 : len(this.Children)-1] |
|
|
|
t.Parent = nil |
|
}
|
|
|