| // Copyright 2015 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 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 and key |
| Value string // The attribute value string |
| } |
| |
| // charDataFlags are used with CharData tokens to store additional settings. |
| type charDataFlags uint8 |
| |
| const ( |
| // The CharData was created 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 carriage returns 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 |
| // entities containing carriage returns 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 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, |
| } |
| 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 { |
| var parent *Element |
| return e.dup(parent).(*Element) |
| } |
| |
| // 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 |
| // 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) |
| } |
| 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 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 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 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 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, |
| } |
| 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('<') |
| if e.Space != "" { |
| w.WriteString(e.Space) |
| w.WriteByte(':') |
| } |
| w.WriteString(e.Tag) |
| 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{'<', '/'}) |
| if e.Space != "" { |
| w.WriteString(e.Space) |
| w.WriteByte(':') |
| } |
| w.WriteString(e.Tag) |
| w.WriteByte('>') |
| } else { |
| if s.CanonicalEndTags { |
| w.Write([]byte{'>', '<', '/'}) |
| if e.Space != "" { |
| w.WriteString(e.Space) |
| w.WriteByte(':') |
| } |
| w.WriteString(e.Tag) |
| 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 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) |
| } |
| |
| // createAttr is a helper function that creates attributes. |
| func (e *Element) createAttr(space, key, value string) *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, key, value} |
| e.Attr = append(e.Attr, a) |
| return &e.Attr[len(e.Attr)-1] |
| } |
| |
| // RemoveAttr removes and returns the first attribute of the element whose key |
| // matches the given key. The key may be prefixed by a namespace and a colon. |
| // If an equal 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 &a |
| } |
| } |
| 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 |
| } |
| |
| // writeTo serializes the attribute to the writer. |
| func (a *Attr) writeTo(w *bufio.Writer, s *WriteSettings) { |
| if a.Space != "" { |
| w.WriteString(a.Space) |
| w.WriteByte(':') |
| } |
| w.WriteString(a.Key) |
| 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, |
| } |
| } |
| |
| // IsWhitespace returns true if the character data token was created to |
| // contain only whitespace. |
| func (c *CharData) IsWhitespace() bool { |
| return (c.flags & whitespaceFlag) != 0 |
| } |
| |
| // 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 |
| } |
| |
| // 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, |
| } |
| } |
| |
| // 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, |
| } |
| } |
| |
| // 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, |
| } |
| } |
| |
| // 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("?>") |
| } |