summaryrefslogtreecommitdiff
path: root/vendor/github.com/beevik/etree
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/beevik/etree')
-rw-r--r--vendor/github.com/beevik/etree/CONTRIBUTORS15
-rw-r--r--vendor/github.com/beevik/etree/LICENSE24
-rw-r--r--vendor/github.com/beevik/etree/README.md207
-rw-r--r--vendor/github.com/beevik/etree/RELEASE_NOTES.md212
-rw-r--r--vendor/github.com/beevik/etree/etree.go1857
-rw-r--r--vendor/github.com/beevik/etree/helpers.go394
-rw-r--r--vendor/github.com/beevik/etree/path.go605
7 files changed, 3314 insertions, 0 deletions
diff --git a/vendor/github.com/beevik/etree/CONTRIBUTORS b/vendor/github.com/beevik/etree/CONTRIBUTORS
new file mode 100644
index 0000000..0bb95df
--- /dev/null
+++ b/vendor/github.com/beevik/etree/CONTRIBUTORS
@@ -0,0 +1,15 @@
+Brett Vickers (beevik)
+Felix Geisendörfer (felixge)
+Kamil Kisiel (kisielk)
+Graham King (grahamking)
+Matt Smith (ma314smith)
+Michal Jemala (michaljemala)
+Nicolas Piganeau (npiganeau)
+Chris Brown (ccbrown)
+Earncef Sequeira (earncef)
+Gabriel de Labachelerie (wuzuf)
+Martin Dosch (mdosch)
+Hugo Wetterberg (hugowetterberg)
+Tobias Theel (nerzal)
+Daniel Potapov (dpotapov)
+Mikhail Ferapontow (MikhailFerapontow)
diff --git a/vendor/github.com/beevik/etree/LICENSE b/vendor/github.com/beevik/etree/LICENSE
new file mode 100644
index 0000000..4ebd856
--- /dev/null
+++ b/vendor/github.com/beevik/etree/LICENSE
@@ -0,0 +1,24 @@
+Copyright 2015-2024 Brett Vickers. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/beevik/etree/README.md b/vendor/github.com/beevik/etree/README.md
new file mode 100644
index 0000000..6d15736
--- /dev/null
+++ b/vendor/github.com/beevik/etree/README.md
@@ -0,0 +1,207 @@
+[![GoDoc](https://godoc.org/github.com/beevik/etree?status.svg)](https://godoc.org/github.com/beevik/etree)
+[![Go](https://github.com/beevik/etree/actions/workflows/go.yml/badge.svg)](https://github.com/beevik/etree/actions/workflows/go.yml)
+
+etree
+=====
+
+The etree package is a lightweight, pure go package that expresses XML in
+the form of an element tree. Its design was inspired by the Python
+[ElementTree](http://docs.python.org/2/library/xml.etree.elementtree.html)
+module.
+
+Some of the package's capabilities and features:
+
+* Represents XML documents as trees of elements for easy traversal.
+* Imports, serializes, modifies or creates XML documents from scratch.
+* Writes and reads XML to/from files, byte slices, strings and io interfaces.
+* Performs simple or complex searches with lightweight XPath-like query APIs.
+* Auto-indents XML using spaces or tabs for better readability.
+* Implemented in pure go; depends only on standard go libraries.
+* Built on top of the go [encoding/xml](http://golang.org/pkg/encoding/xml)
+ package.
+
+The etree package is compatible with go versions 1.23 and later.
+
+### Creating an XML document
+
+The following example creates an XML document from scratch using the etree
+package and outputs its indented contents to stdout.
+```go
+doc := etree.NewDocument()
+doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
+doc.CreateProcInst("xml-stylesheet", `type="text/xsl" href="style.xsl"`)
+
+people := doc.CreateElement("People")
+people.CreateComment("These are all known people")
+
+jon := people.CreateElement("Person")
+jon.CreateAttr("name", "Jon")
+
+sally := people.CreateElement("Person")
+sally.CreateAttr("name", "Sally")
+
+doc.Indent(2)
+doc.WriteTo(os.Stdout)
+```
+
+Output:
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet type="text/xsl" href="style.xsl"?>
+<People>
+ <!--These are all known people-->
+ <Person name="Jon"/>
+ <Person name="Sally"/>
+</People>
+```
+
+### Reading an XML file
+
+Suppose you have a file on disk called `bookstore.xml` containing the
+following data:
+
+```xml
+<bookstore xmlns:p="urn:schemas-books-com:prices">
+
+ <book category="COOKING">
+ <title lang="en">Everyday Italian</title>
+ <author>Giada De Laurentiis</author>
+ <year>2005</year>
+ <p:price>30.00</p:price>
+ </book>
+
+ <book category="CHILDREN">
+ <title lang="en">Harry Potter</title>
+ <author>J K. Rowling</author>
+ <year>2005</year>
+ <p:price>29.99</p:price>
+ </book>
+
+ <book category="WEB">
+ <title lang="en">XQuery Kick Start</title>
+ <author>James McGovern</author>
+ <author>Per Bothner</author>
+ <author>Kurt Cagle</author>
+ <author>James Linn</author>
+ <author>Vaidyanathan Nagarajan</author>
+ <year>2003</year>
+ <p:price>49.99</p:price>
+ </book>
+
+ <book category="WEB">
+ <title lang="en">Learning XML</title>
+ <author>Erik T. Ray</author>
+ <year>2003</year>
+ <p:price>39.95</p:price>
+ </book>
+
+</bookstore>
+```
+
+This code reads the file's contents into an etree document.
+```go
+doc := etree.NewDocument()
+if err := doc.ReadFromFile("bookstore.xml"); err != nil {
+ panic(err)
+}
+```
+
+You can also read XML from a string, a byte slice, or an `io.Reader`.
+
+### Processing elements and attributes
+
+This example illustrates several ways to access elements and attributes using
+etree selection queries.
+```go
+root := doc.SelectElement("bookstore")
+fmt.Println("ROOT element:", root.Tag)
+
+for _, book := range root.SelectElementsSeq("book") {
+ fmt.Println("CHILD element:", book.Tag)
+ if title := book.SelectElement("title"); title != nil {
+ lang := title.SelectAttrValue("lang", "unknown")
+ fmt.Printf(" TITLE: %s (%s)\n", title.Text(), lang)
+ }
+ for _, attr := range book.Attr {
+ fmt.Printf(" ATTR: %s=%s\n", attr.Key, attr.Value)
+ }
+}
+```
+Output:
+```
+ROOT element: bookstore
+CHILD element: book
+ TITLE: Everyday Italian (en)
+ ATTR: category=COOKING
+CHILD element: book
+ TITLE: Harry Potter (en)
+ ATTR: category=CHILDREN
+CHILD element: book
+ TITLE: XQuery Kick Start (en)
+ ATTR: category=WEB
+CHILD element: book
+ TITLE: Learning XML (en)
+ ATTR: category=WEB
+```
+
+### Path queries
+
+This example uses etree's path functions to select all book titles that fall
+into the category of 'WEB'. The double-slash prefix in the path causes the
+search for book elements to occur recursively; book elements may appear at any
+level of the XML hierarchy.
+```go
+for _, t := range doc.FindElementsSeq("//book[@category='WEB']/title") {
+ fmt.Println("Title:", t.Text())
+}
+```
+
+Output:
+```
+Title: XQuery Kick Start
+Title: Learning XML
+```
+
+This example finds the first book element under the root bookstore element and
+outputs the tag and text of each of its child elements.
+```go
+for _, e := range doc.FindElementsSeq("./bookstore/book[1]/*") {
+ fmt.Printf("%s: %s\n", e.Tag, e.Text())
+}
+```
+
+Output:
+```
+title: Everyday Italian
+author: Giada De Laurentiis
+year: 2005
+price: 30.00
+```
+
+This example finds all books with a price of 49.99 and outputs their titles.
+```go
+path := etree.MustCompilePath("./bookstore/book[p:price='49.99']/title")
+for _, e := range doc.FindElementsPathSeq(path) {
+ fmt.Println(e.Text())
+}
+```
+
+Output:
+```
+XQuery Kick Start
+```
+
+Note that this example uses the `FindElementsPathSeq` function, which takes as
+an argument a pre-compiled path object. Use precompiled paths when you plan to
+search with the same path more than once.
+
+### Other features
+
+These are just a few examples of the things the etree package can do. See the
+[documentation](http://godoc.org/github.com/beevik/etree) for a complete
+description of its capabilities.
+
+### Contributing
+
+This project accepts contributions. Just fork the repo and submit a pull
+request!
diff --git a/vendor/github.com/beevik/etree/RELEASE_NOTES.md b/vendor/github.com/beevik/etree/RELEASE_NOTES.md
new file mode 100644
index 0000000..96fe151
--- /dev/null
+++ b/vendor/github.com/beevik/etree/RELEASE_NOTES.md
@@ -0,0 +1,212 @@
+Release 1.6.0
+=============
+
+**Changes**
+
+* Added new iterator versions of existing functions that return slices of
+ `Element` pointers: `ChildElementsSeq`, `SelectElementsSeq`,
+ `FindElementsSeq`, and `FindElementsPathSeq`.
+* Improved performance of functions that return a single element.
+* Because of its use of iterators, this package now requires go 1.23 or later.
+
+Release 1.5.1
+=============
+
+**Fixes**
+
+* Fixed a bug in `InsertChildAt`.
+
+Release 1.5.0
+=============
+
+**Changes**
+
+* Added `Element` function `CreateChild`, which calls a continuation function
+ after creating and adding a child element.
+
+**Fixes**
+
+* Removed a potential conflict between two `ReadSettings` values. When
+ `AttrSingleQuote` is true, `CanonicalAttrVal` is forced to be false.
+
+Release 1.4.1
+=============
+
+**Changes**
+
+* Minimal go version updated to 1.21.
+* Default-initialized CharsetReader causes same result as NewDocument().
+* When reading an XML document, attributes are parsed more efficiently.
+
+Release v1.4.0
+==============
+
+**New Features**
+
+* Add `AutoClose` option to `ReadSettings`.
+* Add `ValidateInput` to `ReadSettings`.
+* Add `NotNil` function to `Element`.
+* Add `NextSibling` and `PrevSibling` functions to `Element`.
+
+Release v1.3.0
+==============
+
+**New Features**
+
+* Add support for double-quotes in filter path queries.
+* Add `PreserveDuplicateAttrs` to `ReadSettings`.
+* Add `ReindexChildren` to `Element`.
+
+Release v1.2.0
+==============
+
+**New Features**
+
+* Add the ability to write XML fragments using Token WriteTo functions.
+* Add the ability to re-indent an XML element as though it were the root of
+ the document.
+* Add a ReadSettings option to preserve CDATA blocks when reading and XML
+ document.
+
+Release v1.1.4
+==============
+
+**New Features**
+
+* Add the ability to preserve whitespace in leaf elements during indent.
+* Add the ability to suppress a document-trailing newline during indent.
+* Add choice of XML attribute quoting style (single-quote or double-quote).
+
+**Removed Features**
+
+* Removed the CDATA preservation change introduced in v1.1.3. It was
+ implemented in a way that broke the ability to process XML documents
+ encoded using non-UTF8 character sets.
+
+Release v1.1.3
+==============
+
+* XML reads now preserve CDATA sections instead of converting them to
+ standard character data.
+
+Release v1.1.2
+==============
+
+* Fixed a path parsing bug.
+* The `Element.Text` function now handles comments embedded between
+ character data spans.
+
+Release v1.1.1
+==============
+
+* Updated go version in `go.mod` to 1.20
+
+Release v1.1.0
+==============
+
+**New Features**
+
+* New attribute helpers.
+ * Added the `Element.SortAttrs` method, which lexicographically sorts an
+ element's attributes by key.
+* New `ReadSettings` properties.
+ * Added `Entity` for the support of custom entity maps.
+* New `WriteSettings` properties.
+ * Added `UseCRLF` to allow the output of CR-LF newlines instead of the
+ default LF newlines. This is useful on Windows systems.
+* Additional support for text and CDATA sections.
+ * The `Element.Text` method now returns the concatenation of all consecutive
+ character data tokens immediately following an element's opening tag.
+ * Added `Element.SetCData` to replace the character data immediately
+ following an element's opening tag with a CDATA section.
+ * Added `Element.CreateCData` to create and add a CDATA section child
+ `CharData` token to an element.
+ * Added `Element.CreateText` to create and add a child text `CharData` token
+ to an element.
+ * Added `NewCData` to create a parentless CDATA section `CharData` token.
+ * Added `NewText` to create a parentless text `CharData`
+ token.
+ * Added `CharData.IsCData` to detect if the token contains a CDATA section.
+ * Added `CharData.IsWhitespace` to detect if the token contains whitespace
+ inserted by one of the document Indent functions.
+ * Modified `Element.SetText` so that it replaces a run of consecutive
+ character data tokens following the element's opening tag (instead of just
+ the first one).
+* New "tail text" support.
+ * Added the `Element.Tail` method, which returns the text immediately
+ following an element's closing tag.
+ * Added the `Element.SetTail` method, which modifies the text immediately
+ following an element's closing tag.
+* New element child insertion and removal methods.
+ * Added the `Element.InsertChildAt` method, which inserts a new child token
+ before the specified child token index.
+ * Added the `Element.RemoveChildAt` method, which removes the child token at
+ the specified child token index.
+* New element and attribute queries.
+ * Added the `Element.Index` method, which returns the element's index within
+ its parent element's child token list.
+ * Added the `Element.NamespaceURI` method to return the namespace URI
+ associated with an element.
+ * Added the `Attr.NamespaceURI` method to return the namespace URI
+ associated with an element.
+ * Added the `Attr.Element` method to return the element that an attribute
+ belongs to.
+* New Path filter functions.
+ * Added `[local-name()='val']` to keep elements whose unprefixed tag matches
+ the desired value.
+ * Added `[name()='val']` to keep elements whose full tag matches the desired
+ value.
+ * Added `[namespace-prefix()='val']` to keep elements whose namespace prefix
+ matches the desired value.
+ * Added `[namespace-uri()='val']` to keep elements whose namespace URI
+ matches the desired value.
+
+**Bug Fixes**
+
+* A default XML `CharSetReader` is now used to prevent failed parsing of XML
+ documents using certain encodings.
+ ([Issue](https://github.com/beevik/etree/issues/53)).
+* All characters are now properly escaped according to XML parsing rules.
+ ([Issue](https://github.com/beevik/etree/issues/55)).
+* The `Document.Indent` and `Document.IndentTabs` functions no longer insert
+ empty string `CharData` tokens.
+
+**Deprecated**
+
+* `Element`
+ * The `InsertChild` method is deprecated. Use `InsertChildAt` instead.
+ * The `CreateCharData` method is deprecated. Use `CreateText` instead.
+* `CharData`
+ * The `NewCharData` method is deprecated. Use `NewText` instead.
+
+
+Release v1.0.1
+==============
+
+**Changes**
+
+* Added support for absolute etree Path queries. An absolute path begins with
+ `/` or `//` and begins its search from the element's document root.
+* Added [`GetPath`](https://godoc.org/github.com/beevik/etree#Element.GetPath)
+ and [`GetRelativePath`](https://godoc.org/github.com/beevik/etree#Element.GetRelativePath)
+ functions to the [`Element`](https://godoc.org/github.com/beevik/etree#Element)
+ type.
+
+**Breaking changes**
+
+* A path starting with `//` is now interpreted as an absolute path.
+ Previously, it was interpreted as a relative path starting from the element
+ whose
+ [`FindElement`](https://godoc.org/github.com/beevik/etree#Element.FindElement)
+ method was called. To remain compatible with this release, all paths
+ prefixed with `//` should be prefixed with `.//` when called from any
+ element other than the document's root.
+* [**edit 2/1/2019**]: Minor releases should not contain breaking changes.
+ Even though this breaking change was very minor, it was a mistake to include
+ it in this minor release. In the future, all breaking changes will be
+ limited to major releases (e.g., version 2.0.0).
+
+Release v1.0.0
+==============
+
+Initial release.
diff --git a/vendor/github.com/beevik/etree/etree.go b/vendor/github.com/beevik/etree/etree.go
new file mode 100644
index 0000000..bfe1f06
--- /dev/null
+++ b/vendor/github.com/beevik/etree/etree.go
@@ -0,0 +1,1857 @@
+// 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"
+ "iter"
+ "os"
+ "slices"
+ "strings"
+)
+
+const (
+ // NoIndent is used with the IndentSettings record to remove all
+ // indenting.
+ NoIndent = -1
+)
+
+// ErrXML is returned when XML parsing fails due to incorrect formatting.
+var ErrXML = errors.New("etree: invalid XML format")
+
+// cdataPrefix is used to detect CDATA text when ReadSettings.PreserveCData is
+// true.
+var cdataPrefix = []byte("<![CDATA[")
+
+// ReadSettings determine the default behavior of the Document's ReadFrom*
+// functions.
+type ReadSettings struct {
+ // CharsetReader, if non-nil, defines a function to generate
+ // charset-conversion readers, converting from the provided non-UTF-8
+ // charset into UTF-8. If nil, the ReadFrom* functions will use a
+ // "pass-through" CharsetReader that performs no conversion on the reader's
+ // data regardless of the value of the "charset" encoding string. 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
+
+ // Preserve CDATA character data blocks when decoding XML (instead of
+ // converting it to normal character text). This entails additional
+ // processing and memory usage during ReadFrom* operations. Default:
+ // false.
+ PreserveCData bool
+
+ // When an element has two or more attributes with the same name,
+ // preserve them instead of keeping only one. Default: false.
+ PreserveDuplicateAttrs bool
+
+ // ValidateInput forces all ReadFrom* functions to validate that the
+ // provided input is composed of "well-formed"(*) XML before processing it.
+ // If invalid XML is detected, the ReadFrom* functions return an error.
+ // Because this option requires the input to be processed twice, it incurs a
+ // significant performance penalty. Default: false.
+ //
+ // (*) Note that this definition of "well-formed" is in the context of the
+ // go standard library's encoding/xml package. Go's encoding/xml package
+ // does not, in fact, guarantee well-formed XML as specified by the W3C XML
+ // recommendation. See: https://github.com/golang/go/issues/68299
+ ValidateInput bool
+
+ // Entity to be passed to standard xml.Decoder. Default: nil.
+ Entity map[string]string
+
+ // When Permissive is true, AutoClose indicates a set of elements to
+ // consider closed immediately after they are opened, regardless of
+ // whether an end element is present. Commonly set to xml.HTMLAutoClose.
+ // Default: nil.
+ AutoClose []string
+}
+
+// defaultCharsetReader is used by the xml decoder when the ReadSettings
+// CharsetReader value is nil. It behaves as a "pass-through", ignoring
+// the requested charset parameter and skipping conversion altogether.
+func defaultCharsetReader(charset string, input io.Reader) (io.Reader, error) {
+ return input, nil
+}
+
+// dup creates a duplicate of the ReadSettings object.
+func (s *ReadSettings) dup() ReadSettings {
+ var entityCopy map[string]string
+ if s.Entity != nil {
+ entityCopy = make(map[string]string)
+ for k, v := range s.Entity {
+ entityCopy[k] = v
+ }
+ }
+ return ReadSettings{
+ CharsetReader: s.CharsetReader,
+ Permissive: s.Permissive,
+ Entity: entityCopy,
+ }
+}
+
+// WriteSettings determine the behavior of the Document's WriteTo* functions.
+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 '. Ignored when AttrSingleQuote
+ // is true. Default: false.
+ CanonicalAttrVal bool
+
+ // AttrSingleQuote causes attributes to use single quotes (attr='example')
+ // instead of double quotes (attr = "example") when set to true. Default:
+ // false.
+ AttrSingleQuote bool
+
+ // UseCRLF causes the document's Indent* functions to use a carriage return
+ // followed by a linefeed ("\r\n") when outputting a newline. If false,
+ // only a linefeed is used ("\n"). Default: false.
+ //
+ // Deprecated: UseCRLF is deprecated. Use IndentSettings.UseCRLF instead.
+ UseCRLF bool
+}
+
+// dup creates a duplicate of the WriteSettings object.
+func (s *WriteSettings) dup() WriteSettings {
+ return *s
+}
+
+// IndentSettings determine the behavior of the Document's Indent* functions.
+type IndentSettings struct {
+ // Spaces indicates the number of spaces to insert for each level of
+ // indentation. Set to etree.NoIndent to remove all indentation. Ignored
+ // when UseTabs is true. Default: 4.
+ Spaces int
+
+ // UseTabs causes tabs to be used instead of spaces when indenting.
+ // Default: false.
+ UseTabs bool
+
+ // UseCRLF causes newlines to be written as a carriage return followed by
+ // a linefeed ("\r\n"). If false, only a linefeed character is output
+ // for a newline ("\n"). Default: false.
+ UseCRLF bool
+
+ // PreserveLeafWhitespace causes indent functions to preserve whitespace
+ // within XML elements containing only non-CDATA character data. Default:
+ // false.
+ PreserveLeafWhitespace bool
+
+ // SuppressTrailingWhitespace suppresses the generation of a trailing
+ // whitespace characters (such as newlines) at the end of the indented
+ // document. Default: false.
+ SuppressTrailingWhitespace bool
+}
+
+// NewIndentSettings creates a default IndentSettings record.
+func NewIndentSettings() *IndentSettings {
+ return &IndentSettings{
+ Spaces: 4,
+ UseTabs: false,
+ UseCRLF: false,
+ PreserveLeafWhitespace: false,
+ SuppressTrailingWhitespace: false,
+ }
+}
+
+type indentFunc func(depth int) string
+
+func getIndentFunc(s *IndentSettings) indentFunc {
+ if s.UseTabs {
+ if s.UseCRLF {
+ return func(depth int) string { return indentCRLF(depth, indentTabs) }
+ } else {
+ return func(depth int) string { return indentLF(depth, indentTabs) }
+ }
+ } else {
+ if s.Spaces < 0 {
+ return func(depth int) string { return "" }
+ } else if s.UseCRLF {
+ return func(depth int) string { return indentCRLF(depth*s.Spaces, indentSpaces) }
+ } else {
+ return func(depth int) string { return indentLF(depth*s.Spaces, indentSpaces) }
+ }
+ }
+}
+
+// Writer is the interface that wraps the Write* functions called by each token
+// type's WriteTo function.
+type Writer interface {
+ io.StringWriter
+ io.ByteWriter
+ io.Writer
+}
+
+// A Token is an interface type used to represent XML elements, character
+// data, CDATA sections, XML comments, XML directives, and XML processing
+// instructions.
+type Token interface {
+ Parent() *Element
+ Index() int
+ WriteTo(w Writer, s *WriteSettings)
+ dup(parent *Element) Token
+ setParent(parent *Element)
+ setIndex(index int)
+}
+
+// A Document is a container holding a complete XML tree.
+//
+// A document has a single embedded element, which contains zero or more child
+// tokens, one of which is usually the root element. The embedded element may
+// include other children such as processing instruction tokens or character
+// data tokens. The document's embedded element is never directly serialized;
+// only its children are.
+//
+// A document also contains read and write settings, which influence the way
+// the document is deserialized, serialized, and indented.
+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 within 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 contains only whitespace.
+ whitespaceFlag charDataFlags = 1 << iota
+
+ // The CharData contains a CDATA section.
+ cdataFlag
+)
+
+// CharData may be used to represent simple text data or a CDATA section
+// within an XML document. The Data property should never be modified
+// directly; use the SetData function instead.
+type CharData struct {
+ Data string // the simple text or CDATA section content
+ parent *Element
+ index int
+ flags charDataFlags
+}
+
+// A Comment represents an XML comment.
+type Comment struct {
+ Data string // the comment's text
+ parent *Element
+ index int
+}
+
+// A Directive represents an XML directive.
+type Directive struct {
+ Data string // the directive string
+ parent *Element
+ index int
+}
+
+// A ProcInst represents an XML processing instruction.
+type ProcInst struct {
+ Target string // the processing instruction target
+ Inst string // the processing instruction value
+ parent *Element
+ index int
+}
+
+// NewDocument creates an XML document without a root element.
+func NewDocument() *Document {
+ return &Document{
+ Element: Element{Child: make([]Token, 0)},
+ }
+}
+
+// NewDocumentWithRoot creates an XML document and sets the element 'e' as its
+// root element. If the element 'e' is already part of another document, it is
+// first removed from its existing document.
+func NewDocumentWithRoot(e *Element) *Document {
+ d := NewDocument()
+ d.SetRoot(e)
+ return d
+}
+
+// Copy returns a recursive, deep copy of the document.
+func (d *Document) Copy() *Document {
+ return &Document{
+ Element: *(d.Element.dup(nil).(*Element)),
+ ReadSettings: d.ReadSettings.dup(),
+ WriteSettings: d.WriteSettings.dup(),
+ }
+}
+
+// Root returns the root element of the document. It returns 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 the element 'e'. If the
+// document already has a root element when this function is called, then the
+// existing root element is unbound from the document. If the element 'e' is
+// part of another document, then it is unbound from the other document.
+func (d *Document) SetRoot(e *Element) {
+ if e.parent != nil {
+ e.parent.RemoveChild(e)
+ }
+
+ // If there is already a root element, replace it.
+ p := &d.Element
+ for i, t := range p.Child {
+ if _, ok := t.(*Element); ok {
+ t.setParent(nil)
+ t.setIndex(-1)
+ p.Child[i] = e
+ e.setParent(p)
+ e.setIndex(i)
+ return
+ }
+ }
+
+ // No existing root element, so add it.
+ p.addChild(e)
+}
+
+// ReadFrom reads XML from the reader 'r' into this document. The function
+// returns the number of bytes read and any error encountered.
+func (d *Document) ReadFrom(r io.Reader) (n int64, err error) {
+ if d.ReadSettings.ValidateInput {
+ b, err := io.ReadAll(r)
+ if err != nil {
+ return 0, err
+ }
+ if err := validateXML(bytes.NewReader(b), d.ReadSettings); err != nil {
+ return 0, err
+ }
+ r = bytes.NewReader(b)
+ }
+ return d.Element.readFrom(r, d.ReadSettings)
+}
+
+// ReadFromFile reads XML from a local file at path 'filepath' into this
+// document.
+func (d *Document) ReadFromFile(filepath string) error {
+ f, err := os.Open(filepath)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ _, err = d.ReadFrom(f)
+ return err
+}
+
+// ReadFromBytes reads XML from the byte slice 'b' into the this document.
+func (d *Document) ReadFromBytes(b []byte) error {
+ if d.ReadSettings.ValidateInput {
+ if err := validateXML(bytes.NewReader(b), d.ReadSettings); err != nil {
+ return err
+ }
+ }
+ _, err := d.Element.readFrom(bytes.NewReader(b), d.ReadSettings)
+ return err
+}
+
+// ReadFromString reads XML from the string 's' into this document.
+func (d *Document) ReadFromString(s string) error {
+ if d.ReadSettings.ValidateInput {
+ if err := validateXML(strings.NewReader(s), d.ReadSettings); err != nil {
+ return err
+ }
+ }
+ _, err := d.Element.readFrom(strings.NewReader(s), d.ReadSettings)
+ return err
+}
+
+// validateXML determines if the data read from the reader 'r' contains
+// well-formed XML according to the rules set by the go xml package.
+func validateXML(r io.Reader, settings ReadSettings) error {
+ dec := newDecoder(r, settings)
+ err := dec.Decode(new(interface{}))
+ if err != nil {
+ return err
+ }
+
+ // If there are any trailing tokens after unmarshalling with Decode(),
+ // then the XML input didn't terminate properly.
+ _, err = dec.Token()
+ if err == io.EOF {
+ return nil
+ }
+ return ErrXML
+}
+
+// newDecoder creates an XML decoder for the reader 'r' configured using
+// the provided read settings.
+func newDecoder(r io.Reader, settings ReadSettings) *xml.Decoder {
+ d := xml.NewDecoder(r)
+ d.CharsetReader = settings.CharsetReader
+ if d.CharsetReader == nil {
+ d.CharsetReader = defaultCharsetReader
+ }
+ d.Strict = !settings.Permissive
+ d.Entity = settings.Entity
+ d.AutoClose = settings.AutoClose
+ return d
+}
+
+// WriteTo serializes the document out to the writer 'w'. The function returns
+// the number of bytes written and any error encountered.
+func (d *Document) WriteTo(w io.Writer) (n int64, err error) {
+ xw := newXmlWriter(w)
+ b := bufio.NewWriter(xw)
+ for _, c := range d.Child {
+ c.WriteTo(b, &d.WriteSettings)
+ }
+ err, n = b.Flush(), xw.bytes
+ return
+}
+
+// WriteToFile serializes the document out to the file at path 'filepath'.
+func (d *Document) WriteToFile(filepath string) error {
+ f, err := os.Create(filepath)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ _, err = d.WriteTo(f)
+ return err
+}
+
+// WriteToBytes serializes this 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 this 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
+}
+
+// Indent modifies the document's element tree by inserting character data
+// tokens containing newlines and spaces for indentation. The amount of
+// indentation per depth level is given by the 'spaces' parameter. Other than
+// the number of spaces, default IndentSettings are used.
+func (d *Document) Indent(spaces int) {
+ s := NewIndentSettings()
+ s.Spaces = spaces
+ d.IndentWithSettings(s)
+}
+
+// IndentTabs modifies the document's element tree by inserting CharData
+// tokens containing newlines and tabs for indentation. One tab is used per
+// indentation level. Other than the use of tabs, default IndentSettings
+// are used.
+func (d *Document) IndentTabs() {
+ s := NewIndentSettings()
+ s.UseTabs = true
+ d.IndentWithSettings(s)
+}
+
+// IndentWithSettings modifies the document's element tree by inserting
+// character data tokens containing newlines and indentation. The behavior
+// of the indentation algorithm is configured by the indent settings.
+func (d *Document) IndentWithSettings(s *IndentSettings) {
+ // WriteSettings.UseCRLF is deprecated. Until removed from the package, it
+ // overrides IndentSettings.UseCRLF when true.
+ if d.WriteSettings.UseCRLF {
+ s.UseCRLF = true
+ }
+
+ d.Element.indent(0, getIndentFunc(s), s)
+
+ if s.SuppressTrailingWhitespace {
+ d.Element.stripTrailingWhitespace()
+ }
+}
+
+// Unindent modifies the document's element tree by removing character data
+// tokens containing only whitespace. Other than the removal of indentation,
+// default IndentSettings are used.
+func (d *Document) Unindent() {
+ s := NewIndentSettings()
+ s.Spaces = NoIndent
+ d.IndentWithSettings(s)
+}
+
+// NewElement creates an unparented element with the specified tag (i.e.,
+// name). The tag may include a namespace prefix followed by 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 AddChild, or added to a document with SetRoot or
+// NewDocumentWithRoot.
+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()
+}
+
+// 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
+}
+
+// ReindexChildren recalculates the index values of the element's child
+// tokens. This is necessary only if you have manually manipulated the
+// element's `Child` array.
+func (e *Element) ReindexChildren() {
+ for i := 0; i < len(e.Child); i++ {
+ e.Child[i].setIndex(i)
+ }
+}
+
+// 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 += cd.Data
+ }
+ } else if _, ok := ch.(*Comment); ok {
+ // ignore
+ } 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 += 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 a new element with the specified tag (i.e., name) and
+// adds it as the last child of element 'e'. The tag may include a prefix
+// followed by a colon.
+func (e *Element) CreateElement(tag string) *Element {
+ space, stag := spaceDecompose(tag)
+ return newElement(space, stag, e)
+}
+
+// CreateChild performs the same task as CreateElement but calls a
+// continuation function after the child element is created, allowing
+// additional actions to be performed on the child element before returning.
+//
+// This method of element creation is particularly useful when building nested
+// XML documents from code. For example:
+//
+// org := doc.CreateChild("organization", func(e *Element) {
+// e.CreateComment("Mary")
+// e.CreateChild("person", func(e *Element) {
+// e.CreateAttr("name", "Mary")
+// e.CreateAttr("age", "30")
+// e.CreateAttr("hair", "brown")
+// })
+// })
+func (e *Element) CreateChild(tag string, cont func(e *Element)) *Element {
+ child := e.CreateElement(tag)
+ cont(child)
+ return child
+}
+
+// AddChild adds the token 't' as the last child of the element. If token 't'
+// was already the child of another element, it is first removed from its
+// parent element.
+func (e *Element) AddChild(t Token) {
+ if t.Parent() != nil {
+ t.Parent().RemoveChild(t)
+ }
+ e.addChild(t)
+}
+
+// InsertChild inserts the token 't' into this element's list of children just
+// before the element's existing child token 'ex'. If the existing element
+// 'ex' does not appear in this element's list of child tokens, then 't' is
+// added to the end of this element's list of child tokens. If token 't' is
+// already the child of another element, it is first removed from the other
+// element's list of child tokens.
+//
+// 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 this element'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, then the token 't' is
+// added to the end of the list of child tokens.
+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 this element's list of
+// child tokens. If the token 't' was a child of this element, then it is
+// removed and 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 child token appearing in slot 'index' of this
+// element's list of child tokens. The removed child token is then 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
+}
+
+// autoClose analyzes the stack's top element and the current token to decide
+// whether the top element should be closed.
+func (e *Element) autoClose(stack *stack[*Element], t xml.Token, tags []string) {
+ if stack.empty() {
+ return
+ }
+
+ top := stack.peek()
+
+ for _, tag := range tags {
+ if strings.EqualFold(tag, top.FullTag()) {
+ if e, ok := t.(xml.EndElement); !ok ||
+ !strings.EqualFold(e.Name.Space, top.Space) ||
+ !strings.EqualFold(e.Name.Local, top.Tag) {
+ stack.pop()
+ }
+ break
+ }
+ }
+}
+
+// ReadFrom reads XML from the reader 'ri' and stores the result as a new
+// child of this element.
+func (e *Element) readFrom(ri io.Reader, settings ReadSettings) (n int64, err error) {
+ var r xmlReader
+ var pr *xmlPeekReader
+ if settings.PreserveCData {
+ pr = newXmlPeekReader(ri)
+ r = pr
+ } else {
+ r = newXmlSimpleReader(ri)
+ }
+
+ attrCheck := make(map[xml.Name]int)
+ dec := newDecoder(r, settings)
+
+ var stack stack[*Element]
+ stack.push(e)
+ for {
+ if pr != nil {
+ pr.PeekPrepare(dec.InputOffset(), len(cdataPrefix))
+ }
+
+ t, err := dec.RawToken()
+
+ if settings.Permissive && settings.AutoClose != nil {
+ e.autoClose(&stack, t, settings.AutoClose)
+ }
+
+ switch {
+ case err == io.EOF:
+ if len(stack.data) != 1 {
+ return r.Bytes(), ErrXML
+ }
+ return r.Bytes(), nil
+ case err != nil:
+ return r.Bytes(), err
+ case stack.empty():
+ return r.Bytes(), ErrXML
+ }
+
+ top := stack.peek()
+
+ switch t := t.(type) {
+ case xml.StartElement:
+ e := newElement(t.Name.Space, t.Name.Local, top)
+ if settings.PreserveDuplicateAttrs || len(t.Attr) < 2 {
+ for _, a := range t.Attr {
+ e.addAttr(a.Name.Space, a.Name.Local, a.Value)
+ }
+ } else {
+ for _, a := range t.Attr {
+ if i, contains := attrCheck[a.Name]; contains {
+ e.Attr[i].Value = a.Value
+ } else {
+ attrCheck[a.Name] = e.addAttr(a.Name.Space, a.Name.Local, a.Value)
+ }
+ }
+ clear(attrCheck)
+ }
+ stack.push(e)
+ case xml.EndElement:
+ if top.Tag != t.Name.Local || top.Space != t.Name.Space {
+ return r.Bytes(), ErrXML
+ }
+ stack.pop()
+ case xml.CharData:
+ data := string(t)
+ var flags charDataFlags
+ if pr != nil {
+ peekBuf := pr.PeekFinalize()
+ if bytes.Equal(peekBuf, cdataPrefix) {
+ flags = cdataFlag
+ } else if isWhitespace(data) {
+ flags = whitespaceFlag
+ }
+ } else {
+ 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, if
+// found, returns a pointer to the matching attribute. The function returns
+// nil if no matching attribute is found. The key may include a namespace
+// prefix followed by 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. If no matching attribute is found, the function
+// returns the 'dflt' value instead. The key may include a namespace prefix
+// followed by a colon.
+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 this element.
+func (e *Element) ChildElements() []*Element {
+ return slices.Collect(e.ChildElementsSeq())
+}
+
+// ChildElementsSeq returns an iterator over all child elements of this
+// element.
+func (e *Element) ChildElementsSeq() iter.Seq[*Element] {
+ return func(yield func(*Element) bool) {
+ for _, t := range e.Child {
+ if c, ok := t.(*Element); ok {
+ if !yield(c) {
+ return
+ }
+ }
+ }
+ }
+}
+
+// SelectElement returns the first child element with the given 'tag' (i.e.,
+// name). The function returns nil if no child element matching the tag is
+// found. The tag may include a namespace prefix followed by a colon.
+func (e *Element) SelectElement(tag string) *Element {
+ for element := range e.SelectElementsSeq(tag) {
+ return element
+ }
+ return nil
+}
+
+// SelectElements returns a slice of all child elements with the given 'tag'
+// (i.e., name). The tag may include a namespace prefix followed by a colon.
+func (e *Element) SelectElements(tag string) []*Element {
+ return slices.Collect(e.SelectElementsSeq(tag))
+}
+
+// SelectElementsSeq returns an iterator over all child elements with the
+// given 'tag' (i.e., name). The tag may include a namespace prefix followed
+// by a colon.
+func (e *Element) SelectElementsSeq(tag string) iter.Seq[*Element] {
+ return func(yield func(*Element) bool) {
+ space, stag := spaceDecompose(tag)
+ for _, t := range e.Child {
+ if c, ok := t.(*Element); ok && spaceMatch(space, c.Space) && stag == c.Tag {
+ if !yield(c) {
+ return
+ }
+ }
+ }
+ }
+}
+
+// FindElement returns the first element matched by the XPath-like 'path'
+// string. The function returns nil if no child element is found using the
+// path. It 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 'path' object. The
+// function returns nil if no element is found using the path.
+func (e *Element) FindElementPath(path Path) *Element {
+ for element := range path.traverse(e) {
+ return element
+ }
+ return nil
+}
+
+// FindElements returns a slice of elements matched by the XPath-like 'path'
+// string. The function returns nil if no child element is found using the
+// path. It panics if an invalid path string is supplied.
+func (e *Element) FindElements(path string) []*Element {
+ return slices.Collect(e.FindElementsSeq(path))
+}
+
+// FindElementsSeq returns an iterator over elements matched by the XPath-like
+// 'path' string. This function uses Go's iterator support for
+// memory-efficient traversal. It panics if an invalid path string is
+// supplied.
+func (e *Element) FindElementsSeq(path string) iter.Seq[*Element] {
+ return e.FindElementsPathSeq(MustCompilePath(path))
+}
+
+// FindElementsPath returns a slice of elements matched by the 'path' object.
+func (e *Element) FindElementsPath(path Path) []*Element {
+ return slices.Collect(e.FindElementsPathSeq(path))
+}
+
+// FindElementsPathSeq returns an iterator over elements matched by the 'path'
+// object.
+func (e *Element) FindElementsPathSeq(path Path) iter.Seq[*Element] {
+ return path.traverse(e)
+}
+
+// NotNil returns the receiver element if it isn't nil; otherwise, it returns
+// an unparented element with an empty string tag. This function simplifies
+// the task of writing code to ignore not-found results from element queries.
+// For example, instead of writing this:
+//
+// if e := doc.SelectElement("enabled"); e != nil {
+// e.SetText("true")
+// }
+//
+// You could write this:
+//
+// doc.SelectElement("enabled").NotNil().SetText("true")
+func (e *Element) NotNil() *Element {
+ if e == nil {
+ return NewElement("")
+ }
+ return e
+}
+
+// GetPath returns the absolute path of the element. The absolute path is the
+// full path from the document's root.
+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 this element relative to the 'source'
+// element. If the two elements are not part of the same element tree, then
+// the function 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, "/")
+}
+
+// IndentWithSettings modifies the element and its child tree by inserting
+// character data tokens containing newlines and indentation. The behavior of
+// the indentation algorithm is configured by the indent settings. Because
+// this function indents the element as if it were at the root of a document,
+// it is most useful when called just before writing the element as an XML
+// fragment using WriteTo.
+func (e *Element) IndentWithSettings(s *IndentSettings) {
+ e.indent(1, getIndentFunc(s), s)
+}
+
+// indent recursively inserts proper indentation between an XML element's
+// child tokens.
+func (e *Element) indent(depth int, indent indentFunc, s *IndentSettings) {
+ e.stripIndent(s)
+ 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, s)
+ }
+ }
+
+ // 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(s *IndentSettings) {
+ // 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
+ }
+ if n == 0 && len(e.Child) == 1 && s.PreserveLeafWhitespace {
+ 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
+}
+
+// stripTrailingWhitespace removes any trailing whitespace CharData tokens
+// from the element's children.
+func (e *Element) stripTrailingWhitespace() {
+ for i := len(e.Child) - 1; i >= 0; i-- {
+ if cd, ok := e.Child[i].(*CharData); !ok || !cd.IsWhitespace() {
+ e.Child = e.Child[:i+1]
+ return
+ }
+ }
+}
+
+// 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)
+ }
+ copy(ne.Attr, e.Attr)
+ return ne
+}
+
+// NextSibling returns this element's next sibling element. It returns nil if
+// there is no next sibling element.
+func (e *Element) NextSibling() *Element {
+ if e.parent == nil {
+ return nil
+ }
+ for i := e.index + 1; i < len(e.parent.Child); i++ {
+ if s, ok := e.parent.Child[i].(*Element); ok {
+ return s
+ }
+ }
+ return nil
+}
+
+// PrevSibling returns this element's preceding sibling element. It returns
+// nil if there is no preceding sibling element.
+func (e *Element) PrevSibling() *Element {
+ if e.parent == nil {
+ return nil
+ }
+ for i := e.index - 1; i >= 0; i-- {
+ if s, ok := e.parent.Child[i].(*Element); ok {
+ return s
+ }
+ }
+ return nil
+}
+
+// Parent returns this element's parent element. It returns nil if this
+// element 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, then the function
+// returns -1.
+func (e *Element) Index() int {
+ return e.index
+}
+
+// WriteTo serializes the element to the writer w.
+func (e *Element) WriteTo(w 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.WriteByte('>')
+ 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{'/', '>'})
+ }
+ }
+}
+
+// setParent replaces this element token's parent.
+func (e *Element) setParent(parent *Element) {
+ e.parent = parent
+}
+
+// setIndex sets this element token's index within its parent's Child slice.
+func (e *Element) setIndex(index int) {
+ e.index = index
+}
+
+// addChild adds a child token to the element e.
+func (e *Element) addChild(t Token) {
+ t.setParent(e)
+ t.setIndex(len(e.Child))
+ e.Child = append(e.Child, t)
+}
+
+// CreateAttr creates an attribute with the specified 'key' and 'value' and
+// adds it to this element. If an attribute with same key already exists on
+// this element, then its value is replaced. The key may include a namespace
+// prefix followed by a colon.
+func (e *Element) CreateAttr(key, value string) *Attr {
+ space, skey := spaceDecompose(key)
+
+ for i, a := range e.Attr {
+ if space == a.Space && skey == a.Key {
+ e.Attr[i].Value = value
+ return &e.Attr[i]
+ }
+ }
+
+ i := e.addAttr(space, skey, value)
+ return &e.Attr[i]
+}
+
+// addAttr is a helper function that adds an attribute to an element. Returns
+// the index of the added attribute.
+func (e *Element) addAttr(space, key, value string) int {
+ a := Attr{
+ Space: space,
+ Key: key,
+ Value: value,
+ element: e,
+ }
+ e.Attr = append(e.Attr, a)
+ return len(e.Attr) - 1
+}
+
+// RemoveAttr removes the first attribute of this element whose key matches
+// 'key'. It returns a copy of the removed attribute if a match is found. If
+// no match is found, it returns nil. The key may include a namespace prefix
+// followed by a colon.
+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 this element's attributes lexicographically by key.
+func (e *Element) SortAttrs() {
+ slices.SortFunc(e.Attr, func(a, b Attr) int {
+ if v := strings.Compare(a.Space, b.Space); v != 0 {
+ return v
+ }
+ return strings.Compare(a.Key, b.Key)
+ })
+}
+
+// FullKey returns this attribute'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 a pointer to the element containing this attribute.
+func (a *Attr) Element() *Element {
+ return a.element
+}
+
+// NamespaceURI returns the XML namespace URI associated with this attribute.
+// The function returns the empty string if the attribute is unprefixed or
+// if the attribute is part of the XML default namespace.
+func (a *Attr) NamespaceURI() string {
+ if a.Space == "" {
+ return ""
+ }
+ return a.element.findLocalNamespaceURI(a.Space)
+}
+
+// WriteTo serializes the attribute to the writer.
+func (a *Attr) WriteTo(w Writer, s *WriteSettings) {
+ w.WriteString(a.FullKey())
+ if s.AttrSingleQuote {
+ w.WriteString(`='`)
+ } else {
+ w.WriteString(`="`)
+ }
+ var m escapeMode
+ if s.CanonicalAttrVal && !s.AttrSingleQuote {
+ m = escapeCanonicalAttr
+ } else {
+ m = escapeNormal
+ }
+ escapeString(w, a.Value, m)
+ if s.AttrSingleQuote {
+ w.WriteByte('\'')
+ } else {
+ w.WriteByte('"')
+ }
+}
+
+// NewText creates an unparented CharData token containing simple text data.
+func NewText(text string) *CharData {
+ return newCharData(text, 0, nil)
+}
+
+// NewCData creates an unparented XML character CDATA section with 'data' as
+// its content.
+func NewCData(data string) *CharData {
+ return newCharData(data, cdataFlag, nil)
+}
+
+// NewCharData creates an unparented CharData token containing simple text
+// 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: nil,
+ index: -1,
+ flags: flags,
+ }
+ if parent != nil {
+ parent.addChild(c)
+ }
+ return c
+}
+
+// CreateText creates a CharData token containing simple text data and adds it
+// to the end of this element's list of child tokens.
+func (e *Element) CreateText(text string) *CharData {
+ return newCharData(text, 0, e)
+}
+
+// CreateCData creates a CharData token containing a CDATA section with 'data'
+// as its content and adds it to the end of this element's list of child
+// tokens.
+func (e *Element) CreateCData(data string) *CharData {
+ return newCharData(data, cdataFlag, e)
+}
+
+// CreateCharData creates a CharData token containing simple text data and
+// adds it to the end of this element's list of child tokens.
+//
+// Deprecated: CreateCharData is deprecated. Instead, use CreateText, which
+// does the same thing.
+func (e *Element) CreateCharData(data string) *CharData {
+ return e.CreateText(data)
+}
+
+// SetData modifies the content of the CharData token. In the case of a
+// CharData token containing simple text, the simple text is modified. In the
+// case of a CharData token containing a CDATA section, the CDATA section's
+// content is modified.
+func (c *CharData) SetData(text string) {
+ c.Data = text
+ if isWhitespace(text) {
+ c.flags |= whitespaceFlag
+ } else {
+ c.flags &= ^whitespaceFlag
+ }
+}
+
+// IsCData returns true if this CharData token is contains a CDATA section. It
+// returns false if the CharData token contains simple text.
+func (c *CharData) IsCData() bool {
+ return (c.flags & cdataFlag) != 0
+}
+
+// IsWhitespace returns true if this CharData token contains only whitespace.
+func (c *CharData) IsWhitespace() bool {
+ return (c.flags & whitespaceFlag) != 0
+}
+
+// Parent returns this CharData 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, then the
+// function returns -1.
+func (c *CharData) Index() int {
+ return c.index
+}
+
+// WriteTo serializes character data to the writer.
+func (c *CharData) WriteTo(w 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)
+ }
+}
+
+// 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,
+ }
+}
+
+// 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
+}
+
+// NewComment creates an unparented comment token.
+func NewComment(comment string) *Comment {
+ return newComment(comment, nil)
+}
+
+// NewComment creates a comment token and sets its parent element to 'parent'.
+func newComment(comment string, parent *Element) *Comment {
+ c := &Comment{
+ Data: comment,
+ parent: nil,
+ index: -1,
+ }
+ if parent != nil {
+ parent.addChild(c)
+ }
+ return c
+}
+
+// CreateComment creates a comment token using the specified 'comment' string
+// and adds it as the last child token of this element.
+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, then the
+// function returns -1.
+func (c *Comment) Index() int {
+ return c.index
+}
+
+// WriteTo serialies the comment to the writer.
+func (c *Comment) WriteTo(w Writer, s *WriteSettings) {
+ w.WriteString("<!--")
+ w.WriteString(c.Data)
+ w.WriteString("-->")
+}
+
+// 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
+}
+
+// NewDirective creates an unparented XML directive token.
+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: nil,
+ index: -1,
+ }
+ if parent != nil {
+ parent.addChild(d)
+ }
+ return d
+}
+
+// CreateDirective creates an XML directive token with the specified 'data'
+// value and adds it as the last child token of this element.
+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, then the
+// function returns -1.
+func (d *Directive) Index() int {
+ return d.index
+}
+
+// WriteTo serializes the XML directive to the writer.
+func (d *Directive) WriteTo(w Writer, s *WriteSettings) {
+ w.WriteString("<!")
+ w.WriteString(d.Data)
+ w.WriteString(">")
+}
+
+// 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
+}
+
+// NewProcInst creates an unparented 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: nil,
+ index: -1,
+ }
+ if parent != nil {
+ parent.addChild(p)
+ }
+ return p
+}
+
+// CreateProcInst creates an XML processing instruction token with the
+// specified 'target' and instruction 'inst'. It is then added as the last
+// child token of this element.
+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, then the
+// function returns -1.
+func (p *ProcInst) Index() int {
+ return p.index
+}
+
+// WriteTo serializes the processing instruction to the writer.
+func (p *ProcInst) WriteTo(w Writer, s *WriteSettings) {
+ w.WriteString("<?")
+ w.WriteString(p.Target)
+ if p.Inst != "" {
+ w.WriteByte(' ')
+ w.WriteString(p.Inst)
+ }
+ w.WriteString("?>")
+}
+
+// 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
+}
diff --git a/vendor/github.com/beevik/etree/helpers.go b/vendor/github.com/beevik/etree/helpers.go
new file mode 100644
index 0000000..ea789b6
--- /dev/null
+++ b/vendor/github.com/beevik/etree/helpers.go
@@ -0,0 +1,394 @@
+// 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
+
+import (
+ "io"
+ "strings"
+ "unicode/utf8"
+)
+
+type stack[E any] struct {
+ data []E
+}
+
+func (s *stack[E]) empty() bool {
+ return len(s.data) == 0
+}
+
+func (s *stack[E]) push(value E) {
+ s.data = append(s.data, value)
+}
+
+func (s *stack[E]) pop() E {
+ value := s.data[len(s.data)-1]
+ var empty E
+ s.data[len(s.data)-1] = empty
+ s.data = s.data[:len(s.data)-1]
+ return value
+}
+
+func (s *stack[E]) peek() E {
+ return s.data[len(s.data)-1]
+}
+
+type queue[E any] struct {
+ data []E
+ head, tail int
+}
+
+func (f *queue[E]) add(value E) {
+ if f.len()+1 >= len(f.data) {
+ f.grow()
+ }
+ f.data[f.tail] = value
+ if f.tail++; f.tail == len(f.data) {
+ f.tail = 0
+ }
+}
+
+func (f *queue[E]) remove() E {
+ value := f.data[f.head]
+ var empty E
+ f.data[f.head] = empty
+ if f.head++; f.head == len(f.data) {
+ f.head = 0
+ }
+ return value
+}
+
+func (f *queue[E]) len() int {
+ if f.tail >= f.head {
+ return f.tail - f.head
+ }
+ return len(f.data) - f.head + f.tail
+}
+
+func (f *queue[E]) grow() {
+ c := len(f.data) * 2
+ if c == 0 {
+ c = 4
+ }
+ buf, count := make([]E, c), f.len()
+ if f.tail >= f.head {
+ copy(buf[:count], f.data[f.head:f.tail])
+ } else {
+ hindex := len(f.data) - f.head
+ copy(buf[:hindex], f.data[f.head:])
+ copy(buf[hindex:count], f.data[:f.tail])
+ }
+ f.data, f.head, f.tail = buf, 0, count
+}
+
+// xmlReader provides the interface by which an XML byte stream is
+// processed and decoded.
+type xmlReader interface {
+ Bytes() int64
+ Read(p []byte) (n int, err error)
+}
+
+// xmlSimpleReader implements a proxy reader that counts the number of
+// bytes read from its encapsulated reader.
+type xmlSimpleReader struct {
+ r io.Reader
+ bytes int64
+}
+
+func newXmlSimpleReader(r io.Reader) xmlReader {
+ return &xmlSimpleReader{r, 0}
+}
+
+func (xr *xmlSimpleReader) Bytes() int64 {
+ return xr.bytes
+}
+
+func (xr *xmlSimpleReader) Read(p []byte) (n int, err error) {
+ n, err = xr.r.Read(p)
+ xr.bytes += int64(n)
+ return n, err
+}
+
+// xmlPeekReader implements a proxy reader that counts the number of
+// bytes read from its encapsulated reader. It also allows the caller to
+// "peek" at the previous portions of the buffer after they have been
+// parsed.
+type xmlPeekReader struct {
+ r io.Reader
+ bytes int64 // total bytes read by the Read function
+ buf []byte // internal read buffer
+ bufSize int // total bytes used in the read buffer
+ bufOffset int64 // total bytes read when buf was last filled
+ window []byte // current read buffer window
+ peekBuf []byte // buffer used to store data to be peeked at later
+ peekOffset int64 // total read offset of the start of the peek buffer
+}
+
+func newXmlPeekReader(r io.Reader) *xmlPeekReader {
+ buf := make([]byte, 4096)
+ return &xmlPeekReader{
+ r: r,
+ bytes: 0,
+ buf: buf,
+ bufSize: 0,
+ bufOffset: 0,
+ window: buf[0:0],
+ peekBuf: make([]byte, 0),
+ peekOffset: -1,
+ }
+}
+
+func (xr *xmlPeekReader) Bytes() int64 {
+ return xr.bytes
+}
+
+func (xr *xmlPeekReader) Read(p []byte) (n int, err error) {
+ if len(xr.window) == 0 {
+ err = xr.fill()
+ if err != nil {
+ return 0, err
+ }
+ if len(xr.window) == 0 {
+ return 0, nil
+ }
+ }
+
+ if len(xr.window) < len(p) {
+ n = len(xr.window)
+ } else {
+ n = len(p)
+ }
+
+ copy(p, xr.window)
+ xr.window = xr.window[n:]
+ xr.bytes += int64(n)
+
+ return n, err
+}
+
+func (xr *xmlPeekReader) PeekPrepare(offset int64, maxLen int) {
+ if maxLen > cap(xr.peekBuf) {
+ xr.peekBuf = make([]byte, 0, maxLen)
+ }
+ xr.peekBuf = xr.peekBuf[0:0]
+ xr.peekOffset = offset
+ xr.updatePeekBuf()
+}
+
+func (xr *xmlPeekReader) PeekFinalize() []byte {
+ xr.updatePeekBuf()
+ return xr.peekBuf
+}
+
+func (xr *xmlPeekReader) fill() error {
+ xr.bufOffset = xr.bytes
+ xr.bufSize = 0
+ n, err := xr.r.Read(xr.buf)
+ if err != nil {
+ xr.window, xr.bufSize = xr.buf[0:0], 0
+ return err
+ }
+ xr.window, xr.bufSize = xr.buf[:n], n
+ xr.updatePeekBuf()
+ return nil
+}
+
+func (xr *xmlPeekReader) updatePeekBuf() {
+ peekRemain := cap(xr.peekBuf) - len(xr.peekBuf)
+ if xr.peekOffset >= 0 && peekRemain > 0 {
+ rangeMin := xr.peekOffset
+ rangeMax := xr.peekOffset + int64(cap(xr.peekBuf))
+ bufMin := xr.bufOffset
+ bufMax := xr.bufOffset + int64(xr.bufSize)
+ if rangeMin < bufMin {
+ rangeMin = bufMin
+ }
+ if rangeMax > bufMax {
+ rangeMax = bufMax
+ }
+ if rangeMax > rangeMin {
+ rangeMin -= xr.bufOffset
+ rangeMax -= xr.bufOffset
+ if int(rangeMax-rangeMin) > peekRemain {
+ rangeMax = rangeMin + int64(peekRemain)
+ }
+ xr.peekBuf = append(xr.peekBuf, xr.buf[rangeMin:rangeMax]...)
+ }
+ }
+}
+
+// xmlWriter implements a proxy writer that counts the number of
+// bytes written by its encapsulated writer.
+type xmlWriter struct {
+ w io.Writer
+ bytes int64
+}
+
+func newXmlWriter(w io.Writer) *xmlWriter {
+ return &xmlWriter{w: w}
+}
+
+func (xw *xmlWriter) Write(p []byte) (n int, err error) {
+ n, err = xw.w.Write(p)
+ xw.bytes += int64(n)
+ return n, err
+}
+
+// isWhitespace returns true if the byte slice contains only
+// whitespace characters.
+func isWhitespace(s string) bool {
+ for i := 0; i < len(s); i++ {
+ if c := s[i]; c != ' ' && c != '\t' && c != '\n' && c != '\r' {
+ return false
+ }
+ }
+ return true
+}
+
+// spaceMatch returns true if namespace a is the empty string
+// or if namespace a equals namespace b.
+func spaceMatch(a, b string) bool {
+ switch {
+ case a == "":
+ return true
+ default:
+ return a == b
+ }
+}
+
+// spaceDecompose breaks a namespace:tag identifier at the ':'
+// and returns the two parts.
+func spaceDecompose(str string) (space, key string) {
+ colon := strings.IndexByte(str, ':')
+ if colon == -1 {
+ return "", str
+ }
+ return str[:colon], str[colon+1:]
+}
+
+// Strings used by indentCRLF and indentLF
+const (
+ indentSpaces = "\r\n "
+ indentTabs = "\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"
+)
+
+// indentCRLF returns a CRLF newline followed by n copies of the first
+// non-CRLF character in the source string.
+func indentCRLF(n int, source string) string {
+ switch {
+ case n < 0:
+ return source[:2]
+ case n < len(source)-1:
+ return source[:n+2]
+ default:
+ return source + strings.Repeat(source[2:3], n-len(source)+2)
+ }
+}
+
+// indentLF returns a LF newline followed by n copies of the first non-LF
+// character in the source string.
+func indentLF(n int, source string) string {
+ switch {
+ case n < 0:
+ return source[1:2]
+ case n < len(source)-1:
+ return source[1 : n+2]
+ default:
+ return source[1:] + strings.Repeat(source[2:3], n-len(source)+2)
+ }
+}
+
+// nextIndex returns the index of the next occurrence of byte ch in s,
+// starting from offset. It returns -1 if the byte is not found.
+func nextIndex(s string, ch byte, offset int) int {
+ switch i := strings.IndexByte(s[offset:], ch); i {
+ case -1:
+ return -1
+ default:
+ return offset + i
+ }
+}
+
+// isInteger returns true if the string s contains an integer.
+func isInteger(s string) bool {
+ for i := 0; i < len(s); i++ {
+ if (s[i] < '0' || s[i] > '9') && !(i == 0 && s[i] == '-') {
+ return false
+ }
+ }
+ return true
+}
+
+type escapeMode byte
+
+const (
+ escapeNormal escapeMode = iota
+ escapeCanonicalText
+ escapeCanonicalAttr
+)
+
+// escapeString writes an escaped version of a string to the writer.
+func escapeString(w Writer, s string, m escapeMode) {
+ var esc []byte
+ last := 0
+ for i := 0; i < len(s); {
+ r, width := utf8.DecodeRuneInString(s[i:])
+ i += width
+ switch r {
+ case '&':
+ esc = []byte("&amp;")
+ case '<':
+ esc = []byte("&lt;")
+ case '>':
+ if m == escapeCanonicalAttr {
+ continue
+ }
+ esc = []byte("&gt;")
+ case '\'':
+ if m != escapeNormal {
+ continue
+ }
+ esc = []byte("&apos;")
+ case '"':
+ if m == escapeCanonicalText {
+ continue
+ }
+ esc = []byte("&quot;")
+ case '\t':
+ if m != escapeCanonicalAttr {
+ continue
+ }
+ esc = []byte("&#x9;")
+ case '\n':
+ if m != escapeCanonicalAttr {
+ continue
+ }
+ esc = []byte("&#xA;")
+ case '\r':
+ if m == escapeNormal {
+ continue
+ }
+ esc = []byte("&#xD;")
+ default:
+ if !isInCharacterRange(r) || (r == 0xFFFD && width == 1) {
+ esc = []byte("\uFFFD")
+ break
+ }
+ continue
+ }
+ w.WriteString(s[last : i-width])
+ w.Write(esc)
+ last = i
+ }
+ w.WriteString(s[last:])
+}
+
+func isInCharacterRange(r rune) bool {
+ return r == 0x09 ||
+ r == 0x0A ||
+ r == 0x0D ||
+ r >= 0x20 && r <= 0xD7FF ||
+ r >= 0xE000 && r <= 0xFFFD ||
+ r >= 0x10000 && r <= 0x10FFFF
+}
diff --git a/vendor/github.com/beevik/etree/path.go b/vendor/github.com/beevik/etree/path.go
new file mode 100644
index 0000000..21760d3
--- /dev/null
+++ b/vendor/github.com/beevik/etree/path.go
@@ -0,0 +1,605 @@
+// 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
+
+import (
+ "iter"
+ "strconv"
+ "strings"
+)
+
+/*
+A Path is a string that represents a search path through an etree starting
+from the document root or an arbitrary element. Paths are used with the
+Element object's Find* methods to locate and return desired elements.
+
+A Path consists of a series of slash-separated "selectors", each of which may
+be modified by one or more bracket-enclosed "filters". Selectors are used to
+traverse the etree from element to element, while filters are used to narrow
+the list of candidate elements at each node.
+
+Although etree Path strings are structurally and behaviorally similar to XPath
+strings (https://www.w3.org/TR/1999/REC-xpath-19991116/), they have a more
+limited set of selectors and filtering options.
+
+The following selectors are supported by etree paths:
+
+ . Select the current element.
+ .. Select the parent of the current element.
+ * Select all child elements of the current element.
+ / Select the root element when used at the start of a path.
+ // Select all descendants of the current element.
+ tag Select all child elements with a name matching the tag.
+
+The following basic filters are supported:
+
+ [@attrib] Keep elements with an attribute named attrib.
+ [@attrib='val'] Keep elements with an attribute named attrib and value matching val.
+ [tag] Keep elements with a child element named tag.
+ [tag='val'] Keep elements with a child element named tag and text matching val.
+ [n] Keep the n-th element, where n is a numeric index starting from 1.
+
+The following function-based filters are supported:
+
+ [text()] Keep elements with non-empty text.
+ [text()='val'] Keep elements whose text matches val.
+ [local-name()='val'] Keep elements whose un-prefixed tag matches val.
+ [name()='val'] Keep elements whose full tag exactly matches val.
+ [namespace-prefix()] Keep elements with non-empty namespace prefixes.
+ [namespace-prefix()='val'] Keep elements whose namespace prefix matches val.
+ [namespace-uri()] Keep elements with non-empty namespace URIs.
+ [namespace-uri()='val'] Keep elements whose namespace URI matches val.
+
+Below are some examples of etree path strings.
+
+Select the bookstore child element of the root element:
+
+ /bookstore
+
+Beginning from the root element, select the title elements of all descendant
+book elements having a 'category' attribute of 'WEB':
+
+ //book[@category='WEB']/title
+
+Beginning from the current element, select the first descendant book element
+with a title child element containing the text 'Great Expectations':
+
+ .//book[title='Great Expectations'][1]
+
+Beginning from the current element, select all child elements of book elements
+with an attribute 'language' set to 'english':
+
+ ./book/*[@language='english']
+
+Beginning from the current element, select all child elements of book elements
+containing the text 'special':
+
+ ./book/*[text()='special']
+
+Beginning from the current element, select all descendant book elements whose
+title child element has a 'language' attribute of 'french':
+
+ .//book/title[@language='french']/..
+
+Beginning from the current element, select all descendant book elements
+belonging to the http://www.w3.org/TR/html4/ namespace:
+
+ .//book[namespace-uri()='http://www.w3.org/TR/html4/']
+*/
+type Path struct {
+ segments []segment
+}
+
+// ErrPath is returned by path functions when an invalid etree path is provided.
+type ErrPath string
+
+// Error returns the string describing a path error.
+func (err ErrPath) Error() string {
+ return "etree: " + string(err)
+}
+
+// CompilePath creates an optimized version of an XPath-like string that
+// can be used to query elements in an element tree.
+func CompilePath(path string) (Path, error) {
+ var comp compiler
+ segments := comp.parsePath(path)
+ if comp.err != ErrPath("") {
+ return Path{nil}, comp.err
+ }
+ return Path{segments}, nil
+}
+
+// MustCompilePath creates an optimized version of an XPath-like string that
+// can be used to query elements in an element tree. Panics if an error
+// occurs. Use this function to create Paths when you know the path is
+// valid (i.e., if it's hard-coded).
+func MustCompilePath(path string) Path {
+ p, err := CompilePath(path)
+ if err != nil {
+ panic(err)
+ }
+ return p
+}
+
+// traverse follows the path from the element e, yielding elements that match
+// the path's selectors and filters using iterators.
+func (p Path) traverse(e *Element) iter.Seq[*Element] {
+ pather := newPather()
+ return func(yield func(*Element) bool) {
+ pather.queue.add(node{e, p.segments})
+ for pather.queue.len() > 0 {
+ if cont := pather.eval(pather.queue.remove(), yield); !cont {
+ return
+ }
+ }
+ }
+}
+
+// A segment is a portion of a path between "/" characters.
+// It contains one selector and zero or more [filters].
+type segment struct {
+ sel selector
+ filters []filter
+}
+
+func (seg *segment) apply(e *Element, p *pather) {
+ seg.sel.apply(e, p)
+ for _, f := range seg.filters {
+ f.apply(p)
+ }
+}
+
+// A selector selects XML elements for consideration by the
+// path traversal.
+type selector interface {
+ apply(e *Element, p *pather)
+}
+
+// A filter pares down a list of candidate XML elements based
+// on a path filter in [brackets].
+type filter interface {
+ apply(p *pather)
+}
+
+// A node represents an element and the remaining path segments that
+// should be applied against it by the pather.
+type node struct {
+ e *Element
+ segments []segment
+}
+
+// A pather is helper object that traverses an element tree using
+// a Path object. It collects and deduplicates all elements matching
+// the path query.
+type pather struct {
+ queue queue[node]
+ results []*Element
+ inResults map[*Element]bool
+ candidates []*Element
+ scratch []*Element // used by filters
+}
+
+// newPather creates a new pather instance.
+func newPather() *pather {
+ return &pather{
+ results: make([]*Element, 0),
+ inResults: make(map[*Element]bool),
+ candidates: make([]*Element, 0),
+ scratch: make([]*Element, 0),
+ }
+}
+
+// eval evaluates the current path node by applying the remaining path's
+// selector rules against the node's element, yielding results via iterator.
+// Returns false if early termination is requested.
+func (p *pather) eval(n node, yield func(*Element) bool) bool {
+ p.candidates = p.candidates[:0]
+ seg, remain := n.segments[0], n.segments[1:]
+ seg.apply(n.e, p)
+
+ if len(remain) == 0 {
+ for _, c := range p.candidates {
+ if in := p.inResults[c]; !in {
+ p.inResults[c] = true
+ if !yield(c) {
+ return false
+ }
+ }
+ }
+ } else {
+ for _, c := range p.candidates {
+ p.queue.add(node{c, remain})
+ }
+ }
+ return true
+}
+
+// A compiler generates a compiled path from a path string.
+type compiler struct {
+ err ErrPath
+}
+
+// parsePath parses an XPath-like string describing a path
+// through an element tree and returns a slice of segment
+// descriptors.
+func (c *compiler) parsePath(path string) []segment {
+ // If path ends with //, fix it
+ if strings.HasSuffix(path, "//") {
+ path += "*"
+ }
+
+ var segments []segment
+
+ // Check for an absolute path
+ if strings.HasPrefix(path, "/") {
+ segments = append(segments, segment{new(selectRoot), []filter{}})
+ path = path[1:]
+ }
+
+ // Split path into segments
+ for _, s := range splitPath(path) {
+ segments = append(segments, c.parseSegment(s))
+ if c.err != ErrPath("") {
+ break
+ }
+ }
+ return segments
+}
+
+func splitPath(path string) []string {
+ var pieces []string
+ start := 0
+ inquote := false
+ var quote byte
+ for i := 0; i+1 <= len(path); i++ {
+ if !inquote {
+ if path[i] == '\'' || path[i] == '"' {
+ inquote, quote = true, path[i]
+ } else if path[i] == '/' {
+ pieces = append(pieces, path[start:i])
+ start = i + 1
+ }
+ } else if path[i] == quote {
+ inquote = false
+ }
+ }
+ return append(pieces, path[start:])
+}
+
+// parseSegment parses a path segment between / characters.
+func (c *compiler) parseSegment(path string) segment {
+ pieces := strings.Split(path, "[")
+ seg := segment{
+ sel: c.parseSelector(pieces[0]),
+ filters: []filter{},
+ }
+ for i := 1; i < len(pieces); i++ {
+ fpath := pieces[i]
+ if len(fpath) == 0 || fpath[len(fpath)-1] != ']' {
+ c.err = ErrPath("path has invalid filter [brackets].")
+ break
+ }
+ seg.filters = append(seg.filters, c.parseFilter(fpath[:len(fpath)-1]))
+ }
+ return seg
+}
+
+// parseSelector parses a selector at the start of a path segment.
+func (c *compiler) parseSelector(path string) selector {
+ switch path {
+ case ".":
+ return new(selectSelf)
+ case "..":
+ return new(selectParent)
+ case "*":
+ return new(selectChildren)
+ case "":
+ return new(selectDescendants)
+ default:
+ return newSelectChildrenByTag(path)
+ }
+}
+
+var fnTable = map[string]func(e *Element) string{
+ "local-name": (*Element).name,
+ "name": (*Element).FullTag,
+ "namespace-prefix": (*Element).namespacePrefix,
+ "namespace-uri": (*Element).NamespaceURI,
+ "text": (*Element).Text,
+}
+
+// parseFilter parses a path filter contained within [brackets].
+func (c *compiler) parseFilter(path string) filter {
+ if len(path) == 0 {
+ c.err = ErrPath("path contains an empty filter expression.")
+ return nil
+ }
+
+ // Filter contains [@attr='val'], [@attr="val"], [fn()='val'],
+ // [fn()="val"], [tag='val'] or [tag="val"]?
+ eqindex := strings.IndexByte(path, '=')
+ if eqindex >= 0 && eqindex+1 < len(path) {
+ quote := path[eqindex+1]
+ if quote == '\'' || quote == '"' {
+ rindex := nextIndex(path, quote, eqindex+2)
+ if rindex != len(path)-1 {
+ c.err = ErrPath("path has mismatched filter quotes.")
+ return nil
+ }
+
+ key := path[:eqindex]
+ value := path[eqindex+2 : rindex]
+
+ switch {
+ case key[0] == '@':
+ return newFilterAttrVal(key[1:], value)
+ case strings.HasSuffix(key, "()"):
+ name := key[:len(key)-2]
+ if fn, ok := fnTable[name]; ok {
+ return newFilterFuncVal(fn, value)
+ }
+ c.err = ErrPath("path has unknown function " + name)
+ return nil
+ default:
+ return newFilterChildText(key, value)
+ }
+ }
+ }
+
+ // Filter contains [@attr], [N], [tag] or [fn()]
+ switch {
+ case path[0] == '@':
+ return newFilterAttr(path[1:])
+ case strings.HasSuffix(path, "()"):
+ name := path[:len(path)-2]
+ if fn, ok := fnTable[name]; ok {
+ return newFilterFunc(fn)
+ }
+ c.err = ErrPath("path has unknown function " + name)
+ return nil
+ case isInteger(path):
+ pos, _ := strconv.Atoi(path)
+ switch {
+ case pos > 0:
+ return newFilterPos(pos - 1)
+ default:
+ return newFilterPos(pos)
+ }
+ default:
+ return newFilterChild(path)
+ }
+}
+
+// selectSelf selects the current element into the candidate list.
+type selectSelf struct{}
+
+func (s *selectSelf) apply(e *Element, p *pather) {
+ p.candidates = append(p.candidates, e)
+}
+
+// selectRoot selects the element's root node.
+type selectRoot struct{}
+
+func (s *selectRoot) apply(e *Element, p *pather) {
+ root := e
+ for root.parent != nil {
+ root = root.parent
+ }
+ p.candidates = append(p.candidates, root)
+}
+
+// selectParent selects the element's parent into the candidate list.
+type selectParent struct{}
+
+func (s *selectParent) apply(e *Element, p *pather) {
+ if e.parent != nil {
+ p.candidates = append(p.candidates, e.parent)
+ }
+}
+
+// selectChildren selects the element's child elements into the
+// candidate list.
+type selectChildren struct{}
+
+func (s *selectChildren) apply(e *Element, p *pather) {
+ for _, c := range e.Child {
+ if c, ok := c.(*Element); ok {
+ p.candidates = append(p.candidates, c)
+ }
+ }
+}
+
+// selectDescendants selects all descendant child elements
+// of the element into the candidate list.
+type selectDescendants struct{}
+
+func (s *selectDescendants) apply(e *Element, p *pather) {
+ var queue queue[*Element]
+ for queue.add(e); queue.len() > 0; {
+ e := queue.remove()
+ p.candidates = append(p.candidates, e)
+ for _, c := range e.Child {
+ if c, ok := c.(*Element); ok {
+ queue.add(c)
+ }
+ }
+ }
+}
+
+// selectChildrenByTag selects into the candidate list all child
+// elements of the element having the specified tag.
+type selectChildrenByTag struct {
+ space, tag string
+}
+
+func newSelectChildrenByTag(path string) *selectChildrenByTag {
+ s, l := spaceDecompose(path)
+ return &selectChildrenByTag{s, l}
+}
+
+func (s *selectChildrenByTag) apply(e *Element, p *pather) {
+ for _, c := range e.Child {
+ if c, ok := c.(*Element); ok && spaceMatch(s.space, c.Space) && s.tag == c.Tag {
+ p.candidates = append(p.candidates, c)
+ }
+ }
+}
+
+// filterPos filters the candidate list, keeping only the
+// candidate at the specified index.
+type filterPos struct {
+ index int
+}
+
+func newFilterPos(pos int) *filterPos {
+ return &filterPos{pos}
+}
+
+func (f *filterPos) apply(p *pather) {
+ if f.index >= 0 {
+ if f.index < len(p.candidates) {
+ p.scratch = append(p.scratch, p.candidates[f.index])
+ }
+ } else {
+ if -f.index <= len(p.candidates) {
+ p.scratch = append(p.scratch, p.candidates[len(p.candidates)+f.index])
+ }
+ }
+ p.candidates, p.scratch = p.scratch, p.candidates[0:0]
+}
+
+// filterAttr filters the candidate list for elements having
+// the specified attribute.
+type filterAttr struct {
+ space, key string
+}
+
+func newFilterAttr(str string) *filterAttr {
+ s, l := spaceDecompose(str)
+ return &filterAttr{s, l}
+}
+
+func (f *filterAttr) apply(p *pather) {
+ for _, c := range p.candidates {
+ for _, a := range c.Attr {
+ if spaceMatch(f.space, a.Space) && f.key == a.Key {
+ p.scratch = append(p.scratch, c)
+ break
+ }
+ }
+ }
+ p.candidates, p.scratch = p.scratch, p.candidates[0:0]
+}
+
+// filterAttrVal filters the candidate list for elements having
+// the specified attribute with the specified value.
+type filterAttrVal struct {
+ space, key, val string
+}
+
+func newFilterAttrVal(str, value string) *filterAttrVal {
+ s, l := spaceDecompose(str)
+ return &filterAttrVal{s, l, value}
+}
+
+func (f *filterAttrVal) apply(p *pather) {
+ for _, c := range p.candidates {
+ for _, a := range c.Attr {
+ if spaceMatch(f.space, a.Space) && f.key == a.Key && f.val == a.Value {
+ p.scratch = append(p.scratch, c)
+ break
+ }
+ }
+ }
+ p.candidates, p.scratch = p.scratch, p.candidates[0:0]
+}
+
+// filterFunc filters the candidate list for elements satisfying a custom
+// boolean function.
+type filterFunc struct {
+ fn func(e *Element) string
+}
+
+func newFilterFunc(fn func(e *Element) string) *filterFunc {
+ return &filterFunc{fn}
+}
+
+func (f *filterFunc) apply(p *pather) {
+ for _, c := range p.candidates {
+ if f.fn(c) != "" {
+ p.scratch = append(p.scratch, c)
+ }
+ }
+ p.candidates, p.scratch = p.scratch, p.candidates[0:0]
+}
+
+// filterFuncVal filters the candidate list for elements containing a value
+// matching the result of a custom function.
+type filterFuncVal struct {
+ fn func(e *Element) string
+ val string
+}
+
+func newFilterFuncVal(fn func(e *Element) string, value string) *filterFuncVal {
+ return &filterFuncVal{fn, value}
+}
+
+func (f *filterFuncVal) apply(p *pather) {
+ for _, c := range p.candidates {
+ if f.fn(c) == f.val {
+ p.scratch = append(p.scratch, c)
+ }
+ }
+ p.candidates, p.scratch = p.scratch, p.candidates[0:0]
+}
+
+// filterChild filters the candidate list for elements having
+// a child element with the specified tag.
+type filterChild struct {
+ space, tag string
+}
+
+func newFilterChild(str string) *filterChild {
+ s, l := spaceDecompose(str)
+ return &filterChild{s, l}
+}
+
+func (f *filterChild) apply(p *pather) {
+ for _, c := range p.candidates {
+ for _, cc := range c.Child {
+ if cc, ok := cc.(*Element); ok &&
+ spaceMatch(f.space, cc.Space) &&
+ f.tag == cc.Tag {
+ p.scratch = append(p.scratch, c)
+ }
+ }
+ }
+ p.candidates, p.scratch = p.scratch, p.candidates[0:0]
+}
+
+// filterChildText filters the candidate list for elements having
+// a child element with the specified tag and text.
+type filterChildText struct {
+ space, tag, text string
+}
+
+func newFilterChildText(str, text string) *filterChildText {
+ s, l := spaceDecompose(str)
+ return &filterChildText{s, l, text}
+}
+
+func (f *filterChildText) apply(p *pather) {
+ for _, c := range p.candidates {
+ for _, cc := range c.Child {
+ if cc, ok := cc.(*Element); ok &&
+ spaceMatch(f.space, cc.Space) &&
+ f.tag == cc.Tag &&
+ f.text == cc.Text() {
+ p.scratch = append(p.scratch, c)
+ }
+ }
+ }
+ p.candidates, p.scratch = p.scratch, p.candidates[0:0]
+}