| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453 |
- // 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("?>")
- }
|