This repository has been archived on 2023-08-14. You can view files and clone it, but cannot push or open issues or pull requests.
dex/vendor/github.com/beevik/etree/etree.go

1454 lines
37 KiB
Go
Raw Normal View History

// Copyright 2015-2019 Brett Vickers.
2017-01-09 22:51:47 +00:00
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package etree provides XML services through an Element Tree
// abstraction.
package etree
import (
"bufio"
"bytes"
"encoding/xml"
"errors"
"io"
"os"
"sort"
2017-01-09 22:51:47 +00:00
"strings"
)
const (
// NoIndent is used with Indent to disable all indenting.
NoIndent = -1
)
// ErrXML is returned when XML parsing fails due to incorrect formatting.
var ErrXML = errors.New("etree: invalid XML format")
// ReadSettings allow for changing the default behavior of the ReadFrom*
// methods.
type ReadSettings struct {
// CharsetReader to be passed to standard xml.Decoder. Default: nil.
CharsetReader func(charset string, input io.Reader) (io.Reader, error)
// Permissive allows input containing common mistakes such as missing tags
// or attribute values. Default: false.
Permissive bool
// Entity to be passed to standard xml.Decoder. Default: nil.
Entity map[string]string
2017-01-09 22:51:47 +00:00
}
// newReadSettings creates a default ReadSettings record.
func newReadSettings() ReadSettings {
return ReadSettings{
CharsetReader: func(label string, input io.Reader) (io.Reader, error) {
return input, nil
},
Permissive: false,
}
2017-01-09 22:51:47 +00:00
}
// WriteSettings allow for changing the serialization behavior of the WriteTo*
// methods.
type WriteSettings struct {
// CanonicalEndTags forces the production of XML end tags, even for
// elements that have no child elements. Default: false.
CanonicalEndTags bool
// CanonicalText forces the production of XML character references for
// text data characters &, <, and >. If false, XML character references
// are also produced for " and '. Default: false.
CanonicalText bool
// CanonicalAttrVal forces the production of XML character references for
// attribute value characters &, < and ". If false, XML character
// references are also produced for > and '. Default: false.
CanonicalAttrVal bool
// When outputting indented XML, use a carriage return and linefeed
// ("\r\n") as a new-line delimiter instead of just a linefeed ("\n").
// This is useful on Windows-based systems.
UseCRLF bool
2017-01-09 22:51:47 +00:00
}
// newWriteSettings creates a default WriteSettings record.
func newWriteSettings() WriteSettings {
return WriteSettings{
CanonicalEndTags: false,
CanonicalText: false,
CanonicalAttrVal: false,
UseCRLF: false,
2017-01-09 22:51:47 +00:00
}
}
// A Token is an empty interface that represents an Element, CharData,
// Comment, Directive, or ProcInst.
type Token interface {
Parent() *Element
Index() int
2017-01-09 22:51:47 +00:00
dup(parent *Element) Token
setParent(parent *Element)
setIndex(index int)
2017-01-09 22:51:47 +00:00
writeTo(w *bufio.Writer, s *WriteSettings)
}
// A Document is a container holding a complete XML hierarchy. Its embedded
// element contains zero or more children, one of which is usually the root
// element. The embedded element may include other children such as
// processing instructions or BOM CharData tokens.
type Document struct {
Element
ReadSettings ReadSettings
WriteSettings WriteSettings
}
// An Element represents an XML element, its attributes, and its child tokens.
type Element struct {
Space, Tag string // namespace prefix and tag
2017-01-09 22:51:47 +00:00
Attr []Attr // key-value attribute pairs
Child []Token // child tokens (elements, comments, etc.)
parent *Element // parent element
index int // token index in parent's children
2017-01-09 22:51:47 +00:00
}
// An Attr represents a key-value attribute of an XML element.
type Attr struct {
Space, Key string // The attribute's namespace prefix and key
Value string // The attribute value string
element *Element // element containing the attribute
2017-01-09 22:51:47 +00:00
}
// charDataFlags are used with CharData tokens to store additional settings.
type charDataFlags uint8
const (
// The CharData was created by an indent function as whitespace.
whitespaceFlag charDataFlags = 1 << iota
// The CharData contains a CDATA section.
cdataFlag
)
// CharData can be used to represent character data or a CDATA section within
// an XML document.
2017-01-09 22:51:47 +00:00
type CharData struct {
Data string
parent *Element
index int
flags charDataFlags
2017-01-09 22:51:47 +00:00
}
// A Comment represents an XML comment.
type Comment struct {
Data string
parent *Element
index int
2017-01-09 22:51:47 +00:00
}
// A Directive represents an XML directive.
type Directive struct {
Data string
parent *Element
index int
2017-01-09 22:51:47 +00:00
}
// A ProcInst represents an XML processing instruction.
type ProcInst struct {
Target string
Inst string
parent *Element
index int
2017-01-09 22:51:47 +00:00
}
// NewDocument creates an XML document without a root element.
func NewDocument() *Document {
return &Document{
Element{Child: make([]Token, 0)},
newReadSettings(),
newWriteSettings(),
}
}
// Copy returns a recursive, deep copy of the document.
func (d *Document) Copy() *Document {
return &Document{*(d.dup(nil).(*Element)), d.ReadSettings, d.WriteSettings}
}
// Root returns the root element of the document, or nil if there is no root
// element.
func (d *Document) Root() *Element {
for _, t := range d.Child {
if c, ok := t.(*Element); ok {
return c
}
}
return nil
}
// SetRoot replaces the document's root element with e. If the document
// already has a root when this function is called, then the document's
// original root is unbound first. If the element e is bound to another
// document (or to another element within a document), then it is unbound
// first.
func (d *Document) SetRoot(e *Element) {
if e.parent != nil {
e.parent.RemoveChild(e)
}
p := &d.Element
e.setParent(p)
// If there is already a root element, replace it.
for i, t := range p.Child {
2017-01-09 22:51:47 +00:00
if _, ok := t.(*Element); ok {
t.setParent(nil)
t.setIndex(-1)
p.Child[i] = e
e.setIndex(i)
2017-01-09 22:51:47 +00:00
return
}
}
// No existing root element, so add it.
p.addChild(e)
2017-01-09 22:51:47 +00:00
}
// ReadFrom reads XML from the reader r into the document d. It returns the
// number of bytes read and any error encountered.
func (d *Document) ReadFrom(r io.Reader) (n int64, err error) {
return d.Element.readFrom(r, d.ReadSettings)
2017-01-09 22:51:47 +00:00
}
// ReadFromFile reads XML from the string s into the document d.
func (d *Document) ReadFromFile(filename string) error {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
_, err = d.ReadFrom(f)
return err
}
// ReadFromBytes reads XML from the byte slice b into the document d.
func (d *Document) ReadFromBytes(b []byte) error {
_, err := d.ReadFrom(bytes.NewReader(b))
return err
}
// ReadFromString reads XML from the string s into the document d.
func (d *Document) ReadFromString(s string) error {
_, err := d.ReadFrom(strings.NewReader(s))
return err
}
// WriteTo serializes an XML document into the writer w. It
// returns the number of bytes written and any error encountered.
func (d *Document) WriteTo(w io.Writer) (n int64, err error) {
cw := newCountWriter(w)
b := bufio.NewWriter(cw)
for _, c := range d.Child {
c.writeTo(b, &d.WriteSettings)
}
err, n = b.Flush(), cw.bytes
return
}
// WriteToFile serializes an XML document into the file named
// filename.
func (d *Document) WriteToFile(filename string) error {
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
_, err = d.WriteTo(f)
return err
}
// WriteToBytes serializes the XML document into a slice of
// bytes.
func (d *Document) WriteToBytes() (b []byte, err error) {
var buf bytes.Buffer
if _, err = d.WriteTo(&buf); err != nil {
return
}
return buf.Bytes(), nil
}
// WriteToString serializes the XML document into a string.
func (d *Document) WriteToString() (s string, err error) {
var b []byte
if b, err = d.WriteToBytes(); err != nil {
return
}
return string(b), nil
}
type indentFunc func(depth int) string
// Indent modifies the document's element tree by inserting character data
// tokens containing newlines and indentation. The amount of indentation per
2017-01-09 22:51:47 +00:00
// depth level is given as spaces. Pass etree.NoIndent for spaces if you want
// no indentation at all.
func (d *Document) Indent(spaces int) {
var indent indentFunc
switch {
case spaces < 0:
indent = func(depth int) string { return "" }
case d.WriteSettings.UseCRLF == true:
indent = func(depth int) string { return indentCRLF(depth*spaces, indentSpaces) }
2017-01-09 22:51:47 +00:00
default:
indent = func(depth int) string { return indentLF(depth*spaces, indentSpaces) }
2017-01-09 22:51:47 +00:00
}
d.Element.indent(0, indent)
}
// IndentTabs modifies the document's element tree by inserting CharData
// tokens containing newlines and tabs for indentation. One tab is used per
// indentation level.
2017-01-09 22:51:47 +00:00
func (d *Document) IndentTabs() {
var indent indentFunc
switch d.WriteSettings.UseCRLF {
case true:
indent = func(depth int) string { return indentCRLF(depth, indentTabs) }
default:
indent = func(depth int) string { return indentLF(depth, indentTabs) }
}
2017-01-09 22:51:47 +00:00
d.Element.indent(0, indent)
}
// NewElement creates an unparented element with the specified tag. The tag
// may be prefixed by a namespace prefix and a colon.
2017-01-09 22:51:47 +00:00
func NewElement(tag string) *Element {
space, stag := spaceDecompose(tag)
return newElement(space, stag, nil)
}
// newElement is a helper function that creates an element and binds it to
// a parent element if possible.
func newElement(space, tag string, parent *Element) *Element {
e := &Element{
Space: space,
Tag: tag,
Attr: make([]Attr, 0),
Child: make([]Token, 0),
parent: parent,
index: -1,
2017-01-09 22:51:47 +00:00
}
if parent != nil {
parent.addChild(e)
}
return e
}
// Copy creates a recursive, deep copy of the element and all its attributes
// and children. The returned element has no parent but can be parented to a
// another element using AddElement, or to a document using SetRoot.
func (e *Element) Copy() *Element {
return e.dup(nil).(*Element)
}
// FullTag returns the element e's complete tag, including namespace prefix if
// present.
func (e *Element) FullTag() string {
if e.Space == "" {
return e.Tag
}
return e.Space + ":" + e.Tag
}
// NamespaceURI returns the XML namespace URI associated with the element. If
// the element is part of the XML default namespace, NamespaceURI returns the
// empty string.
func (e *Element) NamespaceURI() string {
if e.Space == "" {
return e.findDefaultNamespaceURI()
}
return e.findLocalNamespaceURI(e.Space)
2017-01-09 22:51:47 +00:00
}
// findLocalNamespaceURI finds the namespace URI corresponding to the
// requested prefix.
func (e *Element) findLocalNamespaceURI(prefix string) string {
for _, a := range e.Attr {
if a.Space == "xmlns" && a.Key == prefix {
return a.Value
}
}
if e.parent == nil {
return ""
}
return e.parent.findLocalNamespaceURI(prefix)
}
// findDefaultNamespaceURI finds the default namespace URI of the element.
func (e *Element) findDefaultNamespaceURI() string {
for _, a := range e.Attr {
if a.Space == "" && a.Key == "xmlns" {
return a.Value
}
}
if e.parent == nil {
return ""
}
return e.parent.findDefaultNamespaceURI()
}
// hasText returns true if the element has character data immediately
// folllowing the element's opening tag.
func (e *Element) hasText() bool {
if len(e.Child) == 0 {
return false
}
_, ok := e.Child[0].(*CharData)
return ok
}
// namespacePrefix returns the namespace prefix associated with the element.
func (e *Element) namespacePrefix() string {
return e.Space
}
// name returns the tag associated with the element.
func (e *Element) name() string {
return e.Tag
}
// Text returns all character data immediately following the element's opening
// tag.
2017-01-09 22:51:47 +00:00
func (e *Element) Text() string {
if len(e.Child) == 0 {
return ""
}
text := ""
for _, ch := range e.Child {
if cd, ok := ch.(*CharData); ok {
if text == "" {
text = cd.Data
} else {
text = text + cd.Data
}
} else {
break
}
2017-01-09 22:51:47 +00:00
}
return text
2017-01-09 22:51:47 +00:00
}
// SetText replaces all character data immediately following an element's
// opening tag with the requested string.
2017-01-09 22:51:47 +00:00
func (e *Element) SetText(text string) {
e.replaceText(0, text, 0)
}
// SetCData replaces all character data immediately following an element's
// opening tag with a CDATA section.
func (e *Element) SetCData(text string) {
e.replaceText(0, text, cdataFlag)
}
// Tail returns all character data immediately following the element's end
// tag.
func (e *Element) Tail() string {
if e.Parent() == nil {
return ""
}
p := e.Parent()
i := e.Index()
text := ""
for _, ch := range p.Child[i+1:] {
if cd, ok := ch.(*CharData); ok {
if text == "" {
text = cd.Data
} else {
text = text + cd.Data
}
} else {
break
}
}
return text
}
// SetTail replaces all character data immediately following the element's end
// tag with the requested string.
func (e *Element) SetTail(text string) {
if e.Parent() == nil {
return
}
p := e.Parent()
p.replaceText(e.Index()+1, text, 0)
}
// replaceText is a helper function that replaces a series of chardata tokens
// starting at index i with the requested text.
func (e *Element) replaceText(i int, text string, flags charDataFlags) {
end := e.findTermCharDataIndex(i)
switch {
case end == i:
if text != "" {
// insert a new chardata token at index i
cd := newCharData(text, flags, nil)
e.InsertChildAt(i, cd)
}
case end == i+1:
if text == "" {
// remove the chardata token at index i
e.RemoveChildAt(i)
} else {
// replace the first and only character token at index i
cd := e.Child[i].(*CharData)
cd.Data, cd.flags = text, flags
}
default:
if text == "" {
// remove all chardata tokens starting from index i
copy(e.Child[i:], e.Child[end:])
removed := end - i
e.Child = e.Child[:len(e.Child)-removed]
for j := i; j < len(e.Child); j++ {
e.Child[j].setIndex(j)
}
} else {
// replace the first chardata token at index i and remove all
// subsequent chardata tokens
cd := e.Child[i].(*CharData)
cd.Data, cd.flags = text, flags
copy(e.Child[i+1:], e.Child[end:])
removed := end - (i + 1)
e.Child = e.Child[:len(e.Child)-removed]
for j := i + 1; j < len(e.Child); j++ {
e.Child[j].setIndex(j)
}
}
}
}
// findTermCharDataIndex finds the index of the first child token that isn't
// a CharData token. It starts from the requested start index.
func (e *Element) findTermCharDataIndex(start int) int {
for i := start; i < len(e.Child); i++ {
if _, ok := e.Child[i].(*CharData); !ok {
return i
2017-01-09 22:51:47 +00:00
}
}
return len(e.Child)
2017-01-09 22:51:47 +00:00
}
// CreateElement creates an element with the specified tag and adds it as the
// last child element of the element e. The tag may be prefixed by a namespace
// prefix and a colon.
2017-01-09 22:51:47 +00:00
func (e *Element) CreateElement(tag string) *Element {
space, stag := spaceDecompose(tag)
return newElement(space, stag, e)
}
// AddChild adds the token t as the last child of element e. If token t was
// already the child of another element, it is first removed from its current
// parent element.
func (e *Element) AddChild(t Token) {
if t.Parent() != nil {
t.Parent().RemoveChild(t)
}
2017-01-09 22:51:47 +00:00
t.setParent(e)
e.addChild(t)
}
// InsertChild inserts the token t before e's existing child token ex. If ex
// is nil or ex is not a child of e, then t is added to the end of e's child
// token list. If token t was already the child of another element, it is
// first removed from its current parent element.
//
// Deprecated: InsertChild is deprecated. Use InsertChildAt instead.
2017-01-09 22:51:47 +00:00
func (e *Element) InsertChild(ex Token, t Token) {
if ex == nil || ex.Parent() != e {
e.AddChild(t)
return
}
2017-01-09 22:51:47 +00:00
if t.Parent() != nil {
t.Parent().RemoveChild(t)
}
2017-01-09 22:51:47 +00:00
t.setParent(e)
i := ex.Index()
e.Child = append(e.Child, nil)
copy(e.Child[i+1:], e.Child[i:])
e.Child[i] = t
for j := i; j < len(e.Child); j++ {
e.Child[j].setIndex(j)
}
}
// InsertChildAt inserts the token t into the element e's list of child tokens
// just before the requested index. If the index is greater than or equal to
// the length of the list of child tokens, the token t is added to the end of
// the list.
func (e *Element) InsertChildAt(index int, t Token) {
if index >= len(e.Child) {
e.AddChild(t)
return
}
if t.Parent() != nil {
if t.Parent() == e && t.Index() > index {
index--
2017-01-09 22:51:47 +00:00
}
t.Parent().RemoveChild(t)
}
t.setParent(e)
e.Child = append(e.Child, nil)
copy(e.Child[index+1:], e.Child[index:])
e.Child[index] = t
for j := index; j < len(e.Child); j++ {
e.Child[j].setIndex(j)
2017-01-09 22:51:47 +00:00
}
}
// RemoveChild attempts to remove the token t from element e's list of
// children. If the token t is a child of e, then it is returned. Otherwise,
// nil is returned.
func (e *Element) RemoveChild(t Token) Token {
if t.Parent() != e {
return nil
2017-01-09 22:51:47 +00:00
}
return e.RemoveChildAt(t.Index())
}
// RemoveChildAt removes the index-th child token from the element e. The
// removed child token is returned. If the index is out of bounds, no child is
// removed and nil is returned.
func (e *Element) RemoveChildAt(index int) Token {
if index >= len(e.Child) {
return nil
}
t := e.Child[index]
for j := index + 1; j < len(e.Child); j++ {
e.Child[j].setIndex(j - 1)
}
e.Child = append(e.Child[:index], e.Child[index+1:]...)
t.setIndex(-1)
t.setParent(nil)
return t
2017-01-09 22:51:47 +00:00
}
// ReadFrom reads XML from the reader r and stores the result as a new child
// of element e.
func (e *Element) readFrom(ri io.Reader, settings ReadSettings) (n int64, err error) {
2017-01-09 22:51:47 +00:00
r := newCountReader(ri)
dec := xml.NewDecoder(r)
dec.CharsetReader = settings.CharsetReader
dec.Strict = !settings.Permissive
dec.Entity = settings.Entity
2017-01-09 22:51:47 +00:00
var stack stack
stack.push(e)
for {
t, err := dec.RawToken()
switch {
case err == io.EOF:
return r.bytes, nil
case err != nil:
return r.bytes, err
case stack.empty():
return r.bytes, ErrXML
}
top := stack.peek().(*Element)
switch t := t.(type) {
case xml.StartElement:
e := newElement(t.Name.Space, t.Name.Local, top)
for _, a := range t.Attr {
e.createAttr(a.Name.Space, a.Name.Local, a.Value, e)
2017-01-09 22:51:47 +00:00
}
stack.push(e)
case xml.EndElement:
stack.pop()
case xml.CharData:
data := string(t)
var flags charDataFlags
if isWhitespace(data) {
flags = whitespaceFlag
}
newCharData(data, flags, top)
2017-01-09 22:51:47 +00:00
case xml.Comment:
newComment(string(t), top)
case xml.Directive:
newDirective(string(t), top)
case xml.ProcInst:
newProcInst(t.Target, string(t.Inst), top)
}
}
}
// SelectAttr finds an element attribute matching the requested key and
// returns it if found. Returns nil if no matching attribute is found. The key
// may be prefixed by a namespace prefix and a colon.
2017-01-09 22:51:47 +00:00
func (e *Element) SelectAttr(key string) *Attr {
space, skey := spaceDecompose(key)
for i, a := range e.Attr {
if spaceMatch(space, a.Space) && skey == a.Key {
return &e.Attr[i]
}
}
return nil
}
// SelectAttrValue finds an element attribute matching the requested key and
// returns its value if found. The key may be prefixed by a namespace prefix
// and a colon. If the key is not found, the dflt value is returned instead.
2017-01-09 22:51:47 +00:00
func (e *Element) SelectAttrValue(key, dflt string) string {
space, skey := spaceDecompose(key)
for _, a := range e.Attr {
if spaceMatch(space, a.Space) && skey == a.Key {
return a.Value
}
}
return dflt
}
// ChildElements returns all elements that are children of element e.
func (e *Element) ChildElements() []*Element {
var elements []*Element
for _, t := range e.Child {
if c, ok := t.(*Element); ok {
elements = append(elements, c)
}
}
return elements
}
// SelectElement returns the first child element with the given tag. The tag
// may be prefixed by a namespace prefix and a colon. Returns nil if no
// element with a matching tag was found.
2017-01-09 22:51:47 +00:00
func (e *Element) SelectElement(tag string) *Element {
space, stag := spaceDecompose(tag)
for _, t := range e.Child {
if c, ok := t.(*Element); ok && spaceMatch(space, c.Space) && stag == c.Tag {
return c
}
}
return nil
}
// SelectElements returns a slice of all child elements with the given tag.
// The tag may be prefixed by a namespace prefix and a colon.
2017-01-09 22:51:47 +00:00
func (e *Element) SelectElements(tag string) []*Element {
space, stag := spaceDecompose(tag)
var elements []*Element
for _, t := range e.Child {
if c, ok := t.(*Element); ok && spaceMatch(space, c.Space) && stag == c.Tag {
elements = append(elements, c)
}
}
return elements
}
// FindElement returns the first element matched by the XPath-like path
// string. Returns nil if no element is found using the path. Panics if an
// invalid path string is supplied.
2017-01-09 22:51:47 +00:00
func (e *Element) FindElement(path string) *Element {
return e.FindElementPath(MustCompilePath(path))
}
// FindElementPath returns the first element matched by the XPath-like path
// string. Returns nil if no element is found using the path.
2017-01-09 22:51:47 +00:00
func (e *Element) FindElementPath(path Path) *Element {
p := newPather()
elements := p.traverse(e, path)
switch {
case len(elements) > 0:
return elements[0]
default:
return nil
}
}
// FindElements returns a slice of elements matched by the XPath-like path
// string. Panics if an invalid path string is supplied.
func (e *Element) FindElements(path string) []*Element {
return e.FindElementsPath(MustCompilePath(path))
}
// FindElementsPath returns a slice of elements matched by the Path object.
func (e *Element) FindElementsPath(path Path) []*Element {
p := newPather()
return p.traverse(e, path)
}
// GetPath returns the absolute path of the element.
func (e *Element) GetPath() string {
path := []string{}
for seg := e; seg != nil; seg = seg.Parent() {
if seg.Tag != "" {
path = append(path, seg.Tag)
}
}
// Reverse the path.
for i, j := 0, len(path)-1; i < j; i, j = i+1, j-1 {
path[i], path[j] = path[j], path[i]
}
return "/" + strings.Join(path, "/")
}
// GetRelativePath returns the path of the element relative to the source
// element. If the two elements are not part of the same element tree, then
// GetRelativePath returns the empty string.
func (e *Element) GetRelativePath(source *Element) string {
var path []*Element
if source == nil {
return ""
}
// Build a reverse path from the element toward the root. Stop if the
// source element is encountered.
var seg *Element
for seg = e; seg != nil && seg != source; seg = seg.Parent() {
path = append(path, seg)
}
// If we found the source element, reverse the path and compose the
// string.
if seg == source {
if len(path) == 0 {
return "."
}
parts := []string{}
for i := len(path) - 1; i >= 0; i-- {
parts = append(parts, path[i].Tag)
}
return "./" + strings.Join(parts, "/")
}
// The source wasn't encountered, so climb from the source element toward
// the root of the tree until an element in the reversed path is
// encountered.
findPathIndex := func(e *Element, path []*Element) int {
for i, ee := range path {
if e == ee {
return i
}
}
return -1
}
climb := 0
for seg = source; seg != nil; seg = seg.Parent() {
i := findPathIndex(seg, path)
if i >= 0 {
path = path[:i] // truncate at found segment
break
}
climb++
}
// No element in the reversed path was encountered, so the two elements
// must not be part of the same tree.
if seg == nil {
return ""
}
// Reverse the (possibly truncated) path and prepend ".." segments to
// climb.
parts := []string{}
for i := 0; i < climb; i++ {
parts = append(parts, "..")
}
for i := len(path) - 1; i >= 0; i-- {
parts = append(parts, path[i].Tag)
}
return strings.Join(parts, "/")
}
2017-01-09 22:51:47 +00:00
// indent recursively inserts proper indentation between an
// XML element's child tokens.
func (e *Element) indent(depth int, indent indentFunc) {
e.stripIndent()
n := len(e.Child)
if n == 0 {
return
}
oldChild := e.Child
e.Child = make([]Token, 0, n*2+1)
isCharData, firstNonCharData := false, true
for _, c := range oldChild {
// Insert NL+indent before child if it's not character data.
2017-01-09 22:51:47 +00:00
// Exceptions: when it's the first non-character-data child, or when
// the child is at root depth.
_, isCharData = c.(*CharData)
if !isCharData {
if !firstNonCharData || depth > 0 {
s := indent(depth)
if s != "" {
newCharData(s, whitespaceFlag, e)
}
2017-01-09 22:51:47 +00:00
}
firstNonCharData = false
}
e.addChild(c)
// Recursively process child elements.
if ce, ok := c.(*Element); ok {
ce.indent(depth+1, indent)
}
}
// Insert NL+indent before the last child.
2017-01-09 22:51:47 +00:00
if !isCharData {
if !firstNonCharData || depth > 0 {
s := indent(depth - 1)
if s != "" {
newCharData(s, whitespaceFlag, e)
}
2017-01-09 22:51:47 +00:00
}
}
}
// stripIndent removes any previously inserted indentation.
func (e *Element) stripIndent() {
// Count the number of non-indent child tokens
n := len(e.Child)
for _, c := range e.Child {
if cd, ok := c.(*CharData); ok && cd.IsWhitespace() {
2017-01-09 22:51:47 +00:00
n--
}
}
if n == len(e.Child) {
return
}
// Strip out indent CharData
newChild := make([]Token, n)
j := 0
for _, c := range e.Child {
if cd, ok := c.(*CharData); ok && cd.IsWhitespace() {
2017-01-09 22:51:47 +00:00
continue
}
newChild[j] = c
newChild[j].setIndex(j)
2017-01-09 22:51:47 +00:00
j++
}
e.Child = newChild
}
// dup duplicates the element.
func (e *Element) dup(parent *Element) Token {
ne := &Element{
Space: e.Space,
Tag: e.Tag,
Attr: make([]Attr, len(e.Attr)),
Child: make([]Token, len(e.Child)),
parent: parent,
index: e.index,
2017-01-09 22:51:47 +00:00
}
for i, t := range e.Child {
ne.Child[i] = t.dup(ne)
}
for i, a := range e.Attr {
ne.Attr[i] = a
}
return ne
}
// Parent returns the element token's parent element, or nil if it has no
// parent.
func (e *Element) Parent() *Element {
return e.parent
}
// Index returns the index of this element within its parent element's
// list of child tokens. If this element has no parent element, the index
// is -1.
func (e *Element) Index() int {
return e.index
}
2017-01-09 22:51:47 +00:00
// setParent replaces the element token's parent.
func (e *Element) setParent(parent *Element) {
e.parent = parent
}
// setIndex sets the element token's index within its parent's Child slice.
func (e *Element) setIndex(index int) {
e.index = index
}
2017-01-09 22:51:47 +00:00
// writeTo serializes the element to the writer w.
func (e *Element) writeTo(w *bufio.Writer, s *WriteSettings) {
w.WriteByte('<')
w.WriteString(e.FullTag())
2017-01-09 22:51:47 +00:00
for _, a := range e.Attr {
w.WriteByte(' ')
a.writeTo(w, s)
}
if len(e.Child) > 0 {
w.WriteString(">")
for _, c := range e.Child {
c.writeTo(w, s)
}
w.Write([]byte{'<', '/'})
w.WriteString(e.FullTag())
2017-01-09 22:51:47 +00:00
w.WriteByte('>')
} else {
if s.CanonicalEndTags {
w.Write([]byte{'>', '<', '/'})
w.WriteString(e.FullTag())
2017-01-09 22:51:47 +00:00
w.WriteByte('>')
} else {
w.Write([]byte{'/', '>'})
}
}
}
// addChild adds a child token to the element e.
func (e *Element) addChild(t Token) {
t.setIndex(len(e.Child))
2017-01-09 22:51:47 +00:00
e.Child = append(e.Child, t)
}
// CreateAttr creates an attribute and adds it to element e. The key may be
// prefixed by a namespace prefix and a colon. If an attribute with the key
// already exists, its value is replaced.
2017-01-09 22:51:47 +00:00
func (e *Element) CreateAttr(key, value string) *Attr {
space, skey := spaceDecompose(key)
return e.createAttr(space, skey, value, e)
2017-01-09 22:51:47 +00:00
}
// createAttr is a helper function that creates attributes.
func (e *Element) createAttr(space, key, value string, parent *Element) *Attr {
2017-01-09 22:51:47 +00:00
for i, a := range e.Attr {
if space == a.Space && key == a.Key {
e.Attr[i].Value = value
return &e.Attr[i]
}
}
a := Attr{
Space: space,
Key: key,
Value: value,
element: parent,
}
2017-01-09 22:51:47 +00:00
e.Attr = append(e.Attr, a)
return &e.Attr[len(e.Attr)-1]
}
// RemoveAttr removes and returns a copy of the first attribute of the element
// whose key matches the given key. The key may be prefixed by a namespace
// prefix and a colon. If a matching attribute does not exist, nil is
// returned.
2017-01-09 22:51:47 +00:00
func (e *Element) RemoveAttr(key string) *Attr {
space, skey := spaceDecompose(key)
for i, a := range e.Attr {
if space == a.Space && skey == a.Key {
e.Attr = append(e.Attr[0:i], e.Attr[i+1:]...)
return &Attr{
Space: a.Space,
Key: a.Key,
Value: a.Value,
element: nil,
}
2017-01-09 22:51:47 +00:00
}
}
return nil
}
// SortAttrs sorts the element's attributes lexicographically by key.
func (e *Element) SortAttrs() {
sort.Sort(byAttr(e.Attr))
}
2017-01-09 22:51:47 +00:00
type byAttr []Attr
2017-01-09 22:51:47 +00:00
func (a byAttr) Len() int {
return len(a)
}
func (a byAttr) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
func (a byAttr) Less(i, j int) bool {
sp := strings.Compare(a[i].Space, a[j].Space)
if sp == 0 {
return strings.Compare(a[i].Key, a[j].Key) < 0
}
return sp < 0
}
// FullKey returns the attribute a's complete key, including namespace prefix
// if present.
func (a *Attr) FullKey() string {
if a.Space == "" {
return a.Key
}
return a.Space + ":" + a.Key
}
// Element returns the element containing the attribute.
func (a *Attr) Element() *Element {
return a.element
}
// NamespaceURI returns the XML namespace URI associated with the attribute.
// If the element is part of the XML default namespace, NamespaceURI returns
// the empty string.
func (a *Attr) NamespaceURI() string {
return a.element.NamespaceURI()
}
2017-01-09 22:51:47 +00:00
// writeTo serializes the attribute to the writer.
func (a *Attr) writeTo(w *bufio.Writer, s *WriteSettings) {
w.WriteString(a.FullKey())
2017-01-09 22:51:47 +00:00
w.WriteString(`="`)
var m escapeMode
2017-01-09 22:51:47 +00:00
if s.CanonicalAttrVal {
m = escapeCanonicalAttr
2017-01-09 22:51:47 +00:00
} else {
m = escapeNormal
2017-01-09 22:51:47 +00:00
}
escapeString(w, a.Value, m)
2017-01-09 22:51:47 +00:00
w.WriteByte('"')
}
// NewText creates a parentless CharData token containing character data.
func NewText(text string) *CharData {
return newCharData(text, 0, nil)
}
// NewCData creates a parentless XML character CDATA section.
func NewCData(data string) *CharData {
return newCharData(data, cdataFlag, nil)
}
// NewCharData creates a parentless CharData token containing character data.
//
// Deprecated: NewCharData is deprecated. Instead, use NewText, which does the
// same thing.
2017-01-09 22:51:47 +00:00
func NewCharData(data string) *CharData {
return newCharData(data, 0, nil)
2017-01-09 22:51:47 +00:00
}
// newCharData creates a character data token and binds it to a parent
2017-01-09 22:51:47 +00:00
// element. If parent is nil, the CharData token remains unbound.
func newCharData(data string, flags charDataFlags, parent *Element) *CharData {
2017-01-09 22:51:47 +00:00
c := &CharData{
Data: data,
parent: parent,
index: -1,
flags: flags,
2017-01-09 22:51:47 +00:00
}
if parent != nil {
parent.addChild(c)
}
return c
}
// CreateText creates a CharData token containing character data and adds it
// as a child of element e.
func (e *Element) CreateText(text string) *CharData {
return newCharData(text, 0, e)
}
// CreateCData creates a CharData token containing a CDATA section and adds it
// as a child of element e.
func (e *Element) CreateCData(data string) *CharData {
return newCharData(data, cdataFlag, e)
}
// CreateCharData creates a CharData token containing character data and adds
// it as a child of element e.
//
// Deprecated: CreateCharData is deprecated. Instead, use CreateText, which
// does the same thing.
2017-01-09 22:51:47 +00:00
func (e *Element) CreateCharData(data string) *CharData {
return newCharData(data, 0, e)
2017-01-09 22:51:47 +00:00
}
// dup duplicates the character data.
func (c *CharData) dup(parent *Element) Token {
return &CharData{
Data: c.Data,
flags: c.flags,
parent: parent,
index: c.index,
2017-01-09 22:51:47 +00:00
}
}
// IsCData returns true if the character data token is to be encoded as a
// CDATA section.
func (c *CharData) IsCData() bool {
return (c.flags & cdataFlag) != 0
}
// IsWhitespace returns true if the character data token was created by one of
// the document Indent methods to contain only whitespace.
func (c *CharData) IsWhitespace() bool {
return (c.flags & whitespaceFlag) != 0
}
2017-01-09 22:51:47 +00:00
// Parent returns the character data token's parent element, or nil if it has
// no parent.
func (c *CharData) Parent() *Element {
return c.parent
}
// Index returns the index of this CharData token within its parent element's
// list of child tokens. If this CharData token has no parent element, the
// index is -1.
func (c *CharData) Index() int {
return c.index
}
2017-01-09 22:51:47 +00:00
// setParent replaces the character data token's parent.
func (c *CharData) setParent(parent *Element) {
c.parent = parent
}
// setIndex sets the CharData token's index within its parent element's Child
// slice.
func (c *CharData) setIndex(index int) {
c.index = index
}
// writeTo serializes character data to the writer.
2017-01-09 22:51:47 +00:00
func (c *CharData) writeTo(w *bufio.Writer, s *WriteSettings) {
if c.IsCData() {
w.WriteString(`<![CDATA[`)
w.WriteString(c.Data)
w.WriteString(`]]>`)
2017-01-09 22:51:47 +00:00
} else {
var m escapeMode
if s.CanonicalText {
m = escapeCanonicalText
} else {
m = escapeNormal
}
escapeString(w, c.Data, m)
2017-01-09 22:51:47 +00:00
}
}
// NewComment creates a parentless XML comment.
func NewComment(comment string) *Comment {
return newComment(comment, nil)
}
// NewComment creates an XML comment and binds it to a parent element. If
// parent is nil, the Comment remains unbound.
func newComment(comment string, parent *Element) *Comment {
c := &Comment{
Data: comment,
parent: parent,
index: -1,
2017-01-09 22:51:47 +00:00
}
if parent != nil {
parent.addChild(c)
}
return c
}
// CreateComment creates an XML comment and adds it as a child of element e.
func (e *Element) CreateComment(comment string) *Comment {
return newComment(comment, e)
}
// dup duplicates the comment.
func (c *Comment) dup(parent *Element) Token {
return &Comment{
Data: c.Data,
parent: parent,
index: c.index,
2017-01-09 22:51:47 +00:00
}
}
// Parent returns comment token's parent element, or nil if it has no parent.
func (c *Comment) Parent() *Element {
return c.parent
}
// Index returns the index of this Comment token within its parent element's
// list of child tokens. If this Comment token has no parent element, the
// index is -1.
func (c *Comment) Index() int {
return c.index
}
2017-01-09 22:51:47 +00:00
// setParent replaces the comment token's parent.
func (c *Comment) setParent(parent *Element) {
c.parent = parent
}
// setIndex sets the Comment token's index within its parent element's Child
// slice.
func (c *Comment) setIndex(index int) {
c.index = index
}
2017-01-09 22:51:47 +00:00
// writeTo serialies the comment to the writer.
func (c *Comment) writeTo(w *bufio.Writer, s *WriteSettings) {
w.WriteString("<!--")
w.WriteString(c.Data)
w.WriteString("-->")
}
// NewDirective creates a parentless XML directive.
func NewDirective(data string) *Directive {
return newDirective(data, nil)
}
// newDirective creates an XML directive and binds it to a parent element. If
// parent is nil, the Directive remains unbound.
func newDirective(data string, parent *Element) *Directive {
d := &Directive{
Data: data,
parent: parent,
index: -1,
2017-01-09 22:51:47 +00:00
}
if parent != nil {
parent.addChild(d)
}
return d
}
// CreateDirective creates an XML directive and adds it as the last child of
// element e.
func (e *Element) CreateDirective(data string) *Directive {
return newDirective(data, e)
}
// dup duplicates the directive.
func (d *Directive) dup(parent *Element) Token {
return &Directive{
Data: d.Data,
parent: parent,
index: d.index,
2017-01-09 22:51:47 +00:00
}
}
// Parent returns directive token's parent element, or nil if it has no
// parent.
func (d *Directive) Parent() *Element {
return d.parent
}
// Index returns the index of this Directive token within its parent element's
// list of child tokens. If this Directive token has no parent element, the
// index is -1.
func (d *Directive) Index() int {
return d.index
}
2017-01-09 22:51:47 +00:00
// setParent replaces the directive token's parent.
func (d *Directive) setParent(parent *Element) {
d.parent = parent
}
// setIndex sets the Directive token's index within its parent element's Child
// slice.
func (d *Directive) setIndex(index int) {
d.index = index
}
2017-01-09 22:51:47 +00:00
// writeTo serializes the XML directive to the writer.
func (d *Directive) writeTo(w *bufio.Writer, s *WriteSettings) {
w.WriteString("<!")
w.WriteString(d.Data)
w.WriteString(">")
}
// NewProcInst creates a parentless XML processing instruction.
func NewProcInst(target, inst string) *ProcInst {
return newProcInst(target, inst, nil)
}
// newProcInst creates an XML processing instruction and binds it to a parent
// element. If parent is nil, the ProcInst remains unbound.
func newProcInst(target, inst string, parent *Element) *ProcInst {
p := &ProcInst{
Target: target,
Inst: inst,
parent: parent,
index: -1,
2017-01-09 22:51:47 +00:00
}
if parent != nil {
parent.addChild(p)
}
return p
}
// CreateProcInst creates a processing instruction and adds it as a child of
// element e.
func (e *Element) CreateProcInst(target, inst string) *ProcInst {
return newProcInst(target, inst, e)
}
// dup duplicates the procinst.
func (p *ProcInst) dup(parent *Element) Token {
return &ProcInst{
Target: p.Target,
Inst: p.Inst,
parent: parent,
index: p.index,
2017-01-09 22:51:47 +00:00
}
}
// Parent returns processing instruction token's parent element, or nil if it
// has no parent.
func (p *ProcInst) Parent() *Element {
return p.parent
}
// Index returns the index of this ProcInst token within its parent element's
// list of child tokens. If this ProcInst token has no parent element, the
// index is -1.
func (p *ProcInst) Index() int {
return p.index
}
2017-01-09 22:51:47 +00:00
// setParent replaces the processing instruction token's parent.
func (p *ProcInst) setParent(parent *Element) {
p.parent = parent
}
// setIndex sets the processing instruction token's index within its parent
// element's Child slice.
func (p *ProcInst) setIndex(index int) {
p.index = index
}
2017-01-09 22:51:47 +00:00
// writeTo serializes the processing instruction to the writer.
func (p *ProcInst) writeTo(w *bufio.Writer, s *WriteSettings) {
w.WriteString("<?")
w.WriteString(p.Target)
if p.Inst != "" {
w.WriteByte(' ')
w.WriteString(p.Inst)
}
w.WriteString("?>")
}