| // Copyright 2015-2019 Brett Vickers. | |
| // 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" | |
| "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 | |
| } | |
| // 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, | |
| } | |
| } | |
| // 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 | |
| } | |
| // newWriteSettings creates a default WriteSettings record. | |
| func newWriteSettings() WriteSettings { | |
| return WriteSettings{ | |
| CanonicalEndTags: false, | |
| CanonicalText: false, | |
| CanonicalAttrVal: false, | |
| UseCRLF: false, | |
| } | |
| } | |
| // A Token is an empty interface that represents an Element, CharData, | |
| // Comment, Directive, or ProcInst. | |
| type Token interface { | |
| Parent() *Element | |
| Index() int | |
| dup(parent *Element) Token | |
| setParent(parent *Element) | |
| setIndex(index int) | |
| 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 | |
| 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 | |
| } | |
| // 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 | |
| } | |
| // 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. | |
| type CharData struct { | |
| Data string | |
| parent *Element | |
| index int | |
| flags charDataFlags | |
| } | |
| // A Comment represents an XML comment. | |
| type Comment struct { | |
| Data string | |
| parent *Element | |
| index int | |
| } | |
| // A Directive represents an XML directive. | |
| type Directive struct { | |
| Data string | |
| parent *Element | |
| index int | |
| } | |
| // A ProcInst represents an XML processing instruction. | |
| type ProcInst struct { | |
| Target string | |
| Inst string | |
| parent *Element | |
| index int | |
| } | |
| // 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 { | |
| if _, ok := t.(*Element); ok { | |
| t.setParent(nil) | |
| t.setIndex(-1) | |
| p.Child[i] = e | |
| e.setIndex(i) | |
| return | |
| } | |
| } | |
| // No existing root element, so add it. | |
| p.addChild(e) | |
| } | |
| // 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) | |
| } | |
| // 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 | |
| // 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) } | |
| default: | |
| indent = func(depth int) string { return indentLF(depth*spaces, indentSpaces) } | |
| } | |
| 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. | |
| 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) } | |
| } | |
| 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. | |
| 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, | |
| } | |
| 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) | |
| } | |
| // 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. | |
| 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 | |
| } | |
| } | |
| return text | |
| } | |
| // SetText replaces all character data immediately following an element's | |
| // opening tag with the requested string. | |
| 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 | |
| } | |
| } | |
| return len(e.Child) | |
| } | |
| // 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. | |
| 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) | |
| } | |
| 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. | |
| func (e *Element) InsertChild(ex Token, t Token) { | |
| if ex == nil || ex.Parent() != e { | |
| e.AddChild(t) | |
| return | |
| } | |
| if t.Parent() != nil { | |
| t.Parent().RemoveChild(t) | |
| } | |
| 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-- | |
| } | |
| 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) | |
| } | |
| } | |
| // 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 | |
| } | |
| 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 | |
| } | |
| // 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) { | |
| r := newCountReader(ri) | |
| dec := xml.NewDecoder(r) | |
| dec.CharsetReader = settings.CharsetReader | |
| dec.Strict = !settings.Permissive | |
| dec.Entity = settings.Entity | |
| 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) | |
| } | |
| 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) | |
| 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. | |
| 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. | |
| 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. | |
| 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. | |
| 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. | |
| 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. | |
| 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, "/") | |
| } | |
| // 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. | |
| // 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) | |
| } | |
| } | |
| 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. | |
| if !isCharData { | |
| if !firstNonCharData || depth > 0 { | |
| s := indent(depth - 1) | |
| if s != "" { | |
| newCharData(s, whitespaceFlag, e) | |
| } | |
| } | |
| } | |
| } | |
| // 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() { | |
| 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() { | |
| continue | |
| } | |
| newChild[j] = c | |
| newChild[j].setIndex(j) | |
| 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, | |
| } | |
| 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 | |
| } | |
| // 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 | |
| } | |
| // writeTo serializes the element to the writer w. | |
| func (e *Element) writeTo(w *bufio.Writer, s *WriteSettings) { | |
| w.WriteByte('<') | |
| w.WriteString(e.FullTag()) | |
| 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()) | |
| w.WriteByte('>') | |
| } else { | |
| if s.CanonicalEndTags { | |
| w.Write([]byte{'>', '<', '/'}) | |
| w.WriteString(e.FullTag()) | |
| 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)) | |
| 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. | |
| func (e *Element) CreateAttr(key, value string) *Attr { | |
| space, skey := spaceDecompose(key) | |
| return e.createAttr(space, skey, value, e) | |
| } | |
| // createAttr is a helper function that creates attributes. | |
| func (e *Element) createAttr(space, key, value string, parent *Element) *Attr { | |
| 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, | |
| } | |
| 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. | |
| 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, | |
| } | |
| } | |
| } | |
| return nil | |
| } | |
| // SortAttrs sorts the element's attributes lexicographically by key. | |
| func (e *Element) SortAttrs() { | |
| sort.Sort(byAttr(e.Attr)) | |
| } | |
| type byAttr []Attr | |
| 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() | |
| } | |
| // writeTo serializes the attribute to the writer. | |
| func (a *Attr) writeTo(w *bufio.Writer, s *WriteSettings) { | |
| w.WriteString(a.FullKey()) | |
| w.WriteString(`="`) | |
| var m escapeMode | |
| if s.CanonicalAttrVal { | |
| m = escapeCanonicalAttr | |
| } else { | |
| m = escapeNormal | |
| } | |
| escapeString(w, a.Value, m) | |
| 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. | |
| func NewCharData(data string) *CharData { | |
| return newCharData(data, 0, nil) | |
| } | |
| // newCharData creates a character data token and binds it to a parent | |
| // element. If parent is nil, the CharData token remains unbound. | |
| func newCharData(data string, flags charDataFlags, parent *Element) *CharData { | |
| c := &CharData{ | |
| Data: data, | |
| parent: parent, | |
| index: -1, | |
| flags: flags, | |
| } | |
| 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. | |
| func (e *Element) CreateCharData(data string) *CharData { | |
| return newCharData(data, 0, e) | |
| } | |
| // 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, | |
| } | |
| } | |
| // 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 | |
| } | |
| // 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 | |
| } | |
| // 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. | |
| func (c *CharData) writeTo(w *bufio.Writer, s *WriteSettings) { | |
| if c.IsCData() { | |
| w.WriteString(`<![CDATA[`) | |
| w.WriteString(c.Data) | |
| w.WriteString(`]]>`) | |
| } else { | |
| var m escapeMode | |
| if s.CanonicalText { | |
| m = escapeCanonicalText | |
| } else { | |
| m = escapeNormal | |
| } | |
| escapeString(w, c.Data, m) | |
| } | |
| } | |
| // 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, | |
| } | |
| 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, | |
| } | |
| } | |
| // 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 | |
| } | |
| // 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 | |
| } | |
| // 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, | |
| } | |
| 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, | |
| } | |
| } | |
| // 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 | |
| } | |
| // 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 | |
| } | |
| // 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, | |
| } | |
| 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, | |
| } | |
| } | |
| // 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 | |
| } | |
| // 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 | |
| } | |
| // 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("?>") | |
| } |