vendoring and caldav
This commit is contained in:
parent
a06aa900b7
commit
fd9092e5ab
96 changed files with 14832 additions and 5 deletions
16
vendor/github.com/beevik/etree/.travis.yml
generated
vendored
Normal file
16
vendor/github.com/beevik/etree/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
language: go
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- 1.4.2
|
||||
- 1.5.1
|
||||
- 1.6
|
||||
- tip
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
script:
|
||||
- go vet ./...
|
||||
- go test -v ./...
|
8
vendor/github.com/beevik/etree/CONTRIBUTORS
generated
vendored
Normal file
8
vendor/github.com/beevik/etree/CONTRIBUTORS
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
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)
|
24
vendor/github.com/beevik/etree/LICENSE
generated
vendored
Normal file
24
vendor/github.com/beevik/etree/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
Copyright 2015 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.
|
203
vendor/github.com/beevik/etree/README.md
generated
vendored
Normal file
203
vendor/github.com/beevik/etree/README.md
generated
vendored
Normal file
|
@ -0,0 +1,203 @@
|
|||
[](https://travis-ci.org/beevik/etree)
|
||||
[](https://godoc.org/github.com/beevik/etree)
|
||||
|
||||
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 features include:
|
||||
|
||||
* 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.
|
||||
|
||||
### 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.SelectElements("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.FindElements("//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.FindElements("./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.FindElementsPath(path) {
|
||||
fmt.Println(e.Text())
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
XQuery Kick Start
|
||||
```
|
||||
|
||||
Note that this example uses the FindElementsPath 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!
|
943
vendor/github.com/beevik/etree/etree.go
generated
vendored
Normal file
943
vendor/github.com/beevik/etree/etree.go
generated
vendored
Normal file
|
@ -0,0 +1,943 @@
|
|||
// Copyright 2015 Brett Vickers.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package etree provides XML services through an Element Tree
|
||||
// abstraction.
|
||||
package etree
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// NoIndent is used with Indent to disable all indenting.
|
||||
NoIndent = -1
|
||||
)
|
||||
|
||||
// ErrXML is returned when XML parsing fails due to incorrect formatting.
|
||||
var ErrXML = errors.New("etree: invalid XML format")
|
||||
|
||||
// ReadSettings allow for changing the default behavior of the ReadFrom*
|
||||
// methods.
|
||||
type ReadSettings struct {
|
||||
// CharsetReader to be passed to standard xml.Decoder. Default: nil.
|
||||
CharsetReader func(charset string, input io.Reader) (io.Reader, error)
|
||||
|
||||
// Permissive allows input containing common mistakes such as missing tags
|
||||
// or attribute values. Default: false.
|
||||
Permissive bool
|
||||
}
|
||||
|
||||
// newReadSettings creates a default ReadSettings record.
|
||||
func newReadSettings() ReadSettings {
|
||||
return ReadSettings{}
|
||||
}
|
||||
|
||||
// WriteSettings allow for changing the serialization behavior of the WriteTo*
|
||||
// methods.
|
||||
type WriteSettings struct {
|
||||
// CanonicalEndTags forces the production of XML end tags, even for
|
||||
// elements that have no child elements. Default: false.
|
||||
CanonicalEndTags bool
|
||||
|
||||
// CanonicalText forces the production of XML character references for
|
||||
// text data characters &, <, and >. If false, XML character references
|
||||
// are also produced for " and '. Default: false.
|
||||
CanonicalText bool
|
||||
|
||||
// CanonicalAttrVal forces the production of XML character references for
|
||||
// attribute value characters &, < and ". If false, XML character
|
||||
// references are also produced for > and '. Default: false.
|
||||
CanonicalAttrVal bool
|
||||
}
|
||||
|
||||
// newWriteSettings creates a default WriteSettings record.
|
||||
func newWriteSettings() WriteSettings {
|
||||
return WriteSettings{
|
||||
CanonicalEndTags: false,
|
||||
CanonicalText: false,
|
||||
CanonicalAttrVal: false,
|
||||
}
|
||||
}
|
||||
|
||||
// A Token is an empty interface that represents an Element, CharData,
|
||||
// Comment, Directive, or ProcInst.
|
||||
type Token interface {
|
||||
Parent() *Element
|
||||
dup(parent *Element) Token
|
||||
setParent(parent *Element)
|
||||
writeTo(w *bufio.Writer, s *WriteSettings)
|
||||
}
|
||||
|
||||
// A Document is a container holding a complete XML hierarchy. Its embedded
|
||||
// element contains zero or more children, one of which is usually the root
|
||||
// element. The embedded element may include other children such as
|
||||
// processing instructions or BOM CharData tokens.
|
||||
type Document struct {
|
||||
Element
|
||||
ReadSettings ReadSettings
|
||||
WriteSettings WriteSettings
|
||||
}
|
||||
|
||||
// An Element represents an XML element, its attributes, and its child tokens.
|
||||
type Element struct {
|
||||
Space, Tag string // namespace and tag
|
||||
Attr []Attr // key-value attribute pairs
|
||||
Child []Token // child tokens (elements, comments, etc.)
|
||||
parent *Element // parent element
|
||||
}
|
||||
|
||||
// An Attr represents a key-value attribute of an XML element.
|
||||
type Attr struct {
|
||||
Space, Key string // The attribute's namespace and key
|
||||
Value string // The attribute value string
|
||||
}
|
||||
|
||||
// CharData represents character data within XML.
|
||||
type CharData struct {
|
||||
Data string
|
||||
parent *Element
|
||||
whitespace bool
|
||||
}
|
||||
|
||||
// A Comment represents an XML comment.
|
||||
type Comment struct {
|
||||
Data string
|
||||
parent *Element
|
||||
}
|
||||
|
||||
// A Directive represents an XML directive.
|
||||
type Directive struct {
|
||||
Data string
|
||||
parent *Element
|
||||
}
|
||||
|
||||
// A ProcInst represents an XML processing instruction.
|
||||
type ProcInst struct {
|
||||
Target string
|
||||
Inst string
|
||||
parent *Element
|
||||
}
|
||||
|
||||
// NewDocument creates an XML document without a root element.
|
||||
func NewDocument() *Document {
|
||||
return &Document{
|
||||
Element{Child: make([]Token, 0)},
|
||||
newReadSettings(),
|
||||
newWriteSettings(),
|
||||
}
|
||||
}
|
||||
|
||||
// Copy returns a recursive, deep copy of the document.
|
||||
func (d *Document) Copy() *Document {
|
||||
return &Document{*(d.dup(nil).(*Element)), d.ReadSettings, d.WriteSettings}
|
||||
}
|
||||
|
||||
// Root returns the root element of the document, or nil if there is no root
|
||||
// element.
|
||||
func (d *Document) Root() *Element {
|
||||
for _, t := range d.Child {
|
||||
if c, ok := t.(*Element); ok {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRoot replaces the document's root element with e. If the document
|
||||
// already has a root when this function is called, then the document's
|
||||
// original root is unbound first. If the element e is bound to another
|
||||
// document (or to another element within a document), then it is unbound
|
||||
// first.
|
||||
func (d *Document) SetRoot(e *Element) {
|
||||
if e.parent != nil {
|
||||
e.parent.RemoveChild(e)
|
||||
}
|
||||
e.setParent(&d.Element)
|
||||
|
||||
for i, t := range d.Child {
|
||||
if _, ok := t.(*Element); ok {
|
||||
t.setParent(nil)
|
||||
d.Child[i] = e
|
||||
return
|
||||
}
|
||||
}
|
||||
d.Child = append(d.Child, e)
|
||||
}
|
||||
|
||||
// ReadFrom reads XML from the reader r into the document d. It returns the
|
||||
// number of bytes read and any error encountered.
|
||||
func (d *Document) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
return d.Element.readFrom(r, d.ReadSettings)
|
||||
}
|
||||
|
||||
// ReadFromFile reads XML from the string s into the document d.
|
||||
func (d *Document) ReadFromFile(filename string) error {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = d.ReadFrom(f)
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadFromBytes reads XML from the byte slice b into the document d.
|
||||
func (d *Document) ReadFromBytes(b []byte) error {
|
||||
_, err := d.ReadFrom(bytes.NewReader(b))
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadFromString reads XML from the string s into the document d.
|
||||
func (d *Document) ReadFromString(s string) error {
|
||||
_, err := d.ReadFrom(strings.NewReader(s))
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteTo serializes an XML document into the writer w. It
|
||||
// returns the number of bytes written and any error encountered.
|
||||
func (d *Document) WriteTo(w io.Writer) (n int64, err error) {
|
||||
cw := newCountWriter(w)
|
||||
b := bufio.NewWriter(cw)
|
||||
for _, c := range d.Child {
|
||||
c.writeTo(b, &d.WriteSettings)
|
||||
}
|
||||
err, n = b.Flush(), cw.bytes
|
||||
return
|
||||
}
|
||||
|
||||
// WriteToFile serializes an XML document into the file named
|
||||
// filename.
|
||||
func (d *Document) WriteToFile(filename string) error {
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = d.WriteTo(f)
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteToBytes serializes the XML document into a slice of
|
||||
// bytes.
|
||||
func (d *Document) WriteToBytes() (b []byte, err error) {
|
||||
var buf bytes.Buffer
|
||||
if _, err = d.WriteTo(&buf); err != nil {
|
||||
return
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// WriteToString serializes the XML document into a string.
|
||||
func (d *Document) WriteToString() (s string, err error) {
|
||||
var b []byte
|
||||
if b, err = d.WriteToBytes(); err != nil {
|
||||
return
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
type indentFunc func(depth int) string
|
||||
|
||||
// Indent modifies the document's element tree by inserting CharData entities
|
||||
// containing carriage returns and indentation. The amount of indentation per
|
||||
// depth level is given as spaces. Pass etree.NoIndent for spaces if you want
|
||||
// no indentation at all.
|
||||
func (d *Document) Indent(spaces int) {
|
||||
var indent indentFunc
|
||||
switch {
|
||||
case spaces < 0:
|
||||
indent = func(depth int) string { return "" }
|
||||
default:
|
||||
indent = func(depth int) string { return crIndent(depth*spaces, crsp) }
|
||||
}
|
||||
d.Element.indent(0, indent)
|
||||
}
|
||||
|
||||
// IndentTabs modifies the document's element tree by inserting CharData
|
||||
// entities containing carriage returns and tabs for indentation. One tab is
|
||||
// used per indentation level.
|
||||
func (d *Document) IndentTabs() {
|
||||
indent := func(depth int) string { return crIndent(depth, crtab) }
|
||||
d.Element.indent(0, indent)
|
||||
}
|
||||
|
||||
// NewElement creates an unparented element with the specified tag. The tag
|
||||
// may be prefixed by a namespace and a colon.
|
||||
func NewElement(tag string) *Element {
|
||||
space, stag := spaceDecompose(tag)
|
||||
return newElement(space, stag, nil)
|
||||
}
|
||||
|
||||
// newElement is a helper function that creates an element and binds it to
|
||||
// a parent element if possible.
|
||||
func newElement(space, tag string, parent *Element) *Element {
|
||||
e := &Element{
|
||||
Space: space,
|
||||
Tag: tag,
|
||||
Attr: make([]Attr, 0),
|
||||
Child: make([]Token, 0),
|
||||
parent: parent,
|
||||
}
|
||||
if parent != nil {
|
||||
parent.addChild(e)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// Copy creates a recursive, deep copy of the element and all its attributes
|
||||
// and children. The returned element has no parent but can be parented to a
|
||||
// another element using AddElement, or to a document using SetRoot.
|
||||
func (e *Element) Copy() *Element {
|
||||
var parent *Element
|
||||
return e.dup(parent).(*Element)
|
||||
}
|
||||
|
||||
// Text returns the characters immediately following the element's
|
||||
// opening tag.
|
||||
func (e *Element) Text() string {
|
||||
if len(e.Child) == 0 {
|
||||
return ""
|
||||
}
|
||||
if cd, ok := e.Child[0].(*CharData); ok {
|
||||
return cd.Data
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// SetText replaces an element's subsidiary CharData text with a new string.
|
||||
func (e *Element) SetText(text string) {
|
||||
if len(e.Child) > 0 {
|
||||
if cd, ok := e.Child[0].(*CharData); ok {
|
||||
cd.Data = text
|
||||
return
|
||||
}
|
||||
}
|
||||
cd := newCharData(text, false, e)
|
||||
copy(e.Child[1:], e.Child[0:])
|
||||
e.Child[0] = cd
|
||||
}
|
||||
|
||||
// CreateElement creates an element with the specified tag and adds it as the
|
||||
// last child element of the element e. The tag may be prefixed by a namespace
|
||||
// and a colon.
|
||||
func (e *Element) CreateElement(tag string) *Element {
|
||||
space, stag := spaceDecompose(tag)
|
||||
return newElement(space, stag, e)
|
||||
}
|
||||
|
||||
// AddChild adds the token t as the last child of element e. If token t was
|
||||
// already the child of another element, it is first removed from its current
|
||||
// parent element.
|
||||
func (e *Element) AddChild(t Token) {
|
||||
if t.Parent() != nil {
|
||||
t.Parent().RemoveChild(t)
|
||||
}
|
||||
t.setParent(e)
|
||||
e.addChild(t)
|
||||
}
|
||||
|
||||
// InsertChild inserts the token t before e's existing child token ex. If ex
|
||||
// is nil (or if ex is not a child of e), then t is added to the end of e's
|
||||
// child token list. If token t was already the child of another element, it
|
||||
// is first removed from its current parent element.
|
||||
func (e *Element) InsertChild(ex Token, t Token) {
|
||||
if t.Parent() != nil {
|
||||
t.Parent().RemoveChild(t)
|
||||
}
|
||||
t.setParent(e)
|
||||
|
||||
for i, c := range e.Child {
|
||||
if c == ex {
|
||||
e.Child = append(e.Child, nil)
|
||||
copy(e.Child[i+1:], e.Child[i:])
|
||||
e.Child[i] = t
|
||||
return
|
||||
}
|
||||
}
|
||||
e.addChild(t)
|
||||
}
|
||||
|
||||
// RemoveChild attempts to remove the token t from element e's list of
|
||||
// children. If the token t is a child of e, then it is returned. Otherwise,
|
||||
// nil is returned.
|
||||
func (e *Element) RemoveChild(t Token) Token {
|
||||
for i, c := range e.Child {
|
||||
if c == t {
|
||||
e.Child = append(e.Child[:i], e.Child[i+1:]...)
|
||||
c.setParent(nil)
|
||||
return t
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadFrom reads XML from the reader r and stores the result as a new child
|
||||
// of element e.
|
||||
func (e *Element) readFrom(ri io.Reader, settings ReadSettings) (n int64, err error) {
|
||||
r := newCountReader(ri)
|
||||
dec := xml.NewDecoder(r)
|
||||
dec.CharsetReader = settings.CharsetReader
|
||||
dec.Strict = !settings.Permissive
|
||||
var stack stack
|
||||
stack.push(e)
|
||||
for {
|
||||
t, err := dec.RawToken()
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
return r.bytes, nil
|
||||
case err != nil:
|
||||
return r.bytes, err
|
||||
case stack.empty():
|
||||
return r.bytes, ErrXML
|
||||
}
|
||||
|
||||
top := stack.peek().(*Element)
|
||||
|
||||
switch t := t.(type) {
|
||||
case xml.StartElement:
|
||||
e := newElement(t.Name.Space, t.Name.Local, top)
|
||||
for _, a := range t.Attr {
|
||||
e.createAttr(a.Name.Space, a.Name.Local, a.Value)
|
||||
}
|
||||
stack.push(e)
|
||||
case xml.EndElement:
|
||||
stack.pop()
|
||||
case xml.CharData:
|
||||
data := string(t)
|
||||
newCharData(data, isWhitespace(data), top)
|
||||
case xml.Comment:
|
||||
newComment(string(t), top)
|
||||
case xml.Directive:
|
||||
newDirective(string(t), top)
|
||||
case xml.ProcInst:
|
||||
newProcInst(t.Target, string(t.Inst), top)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SelectAttr finds an element attribute matching the requested key and
|
||||
// returns it if found. The key may be prefixed by a namespace and a colon.
|
||||
func (e *Element) SelectAttr(key string) *Attr {
|
||||
space, skey := spaceDecompose(key)
|
||||
for i, a := range e.Attr {
|
||||
if spaceMatch(space, a.Space) && skey == a.Key {
|
||||
return &e.Attr[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SelectAttrValue finds an element attribute matching the requested key and
|
||||
// returns its value if found. The key may be prefixed by a namespace and a
|
||||
// colon. If the key is not found, the dflt value is returned instead.
|
||||
func (e *Element) SelectAttrValue(key, dflt string) string {
|
||||
space, skey := spaceDecompose(key)
|
||||
for _, a := range e.Attr {
|
||||
if spaceMatch(space, a.Space) && skey == a.Key {
|
||||
return a.Value
|
||||
}
|
||||
}
|
||||
return dflt
|
||||
}
|
||||
|
||||
// ChildElements returns all elements that are children of element e.
|
||||
func (e *Element) ChildElements() []*Element {
|
||||
var elements []*Element
|
||||
for _, t := range e.Child {
|
||||
if c, ok := t.(*Element); ok {
|
||||
elements = append(elements, c)
|
||||
}
|
||||
}
|
||||
return elements
|
||||
}
|
||||
|
||||
// SelectElement returns the first child element with the given tag. The tag
|
||||
// may be prefixed by a namespace and a colon.
|
||||
func (e *Element) SelectElement(tag string) *Element {
|
||||
space, stag := spaceDecompose(tag)
|
||||
for _, t := range e.Child {
|
||||
if c, ok := t.(*Element); ok && spaceMatch(space, c.Space) && stag == c.Tag {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SelectElements returns a slice of all child elements with the given tag.
|
||||
// The tag may be prefixed by a namespace and a colon.
|
||||
func (e *Element) SelectElements(tag string) []*Element {
|
||||
space, stag := spaceDecompose(tag)
|
||||
var elements []*Element
|
||||
for _, t := range e.Child {
|
||||
if c, ok := t.(*Element); ok && spaceMatch(space, c.Space) && stag == c.Tag {
|
||||
elements = append(elements, c)
|
||||
}
|
||||
}
|
||||
return elements
|
||||
}
|
||||
|
||||
// FindElement returns the first element matched by the XPath-like path
|
||||
// string. Panics if an invalid path string is supplied.
|
||||
func (e *Element) FindElement(path string) *Element {
|
||||
return e.FindElementPath(MustCompilePath(path))
|
||||
}
|
||||
|
||||
// FindElementPath returns the first element matched by the XPath-like path
|
||||
// string.
|
||||
func (e *Element) FindElementPath(path Path) *Element {
|
||||
p := newPather()
|
||||
elements := p.traverse(e, path)
|
||||
switch {
|
||||
case len(elements) > 0:
|
||||
return elements[0]
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// FindElements returns a slice of elements matched by the XPath-like path
|
||||
// string. Panics if an invalid path string is supplied.
|
||||
func (e *Element) FindElements(path string) []*Element {
|
||||
return e.FindElementsPath(MustCompilePath(path))
|
||||
}
|
||||
|
||||
// FindElementsPath returns a slice of elements matched by the Path object.
|
||||
func (e *Element) FindElementsPath(path Path) []*Element {
|
||||
p := newPather()
|
||||
return p.traverse(e, path)
|
||||
}
|
||||
|
||||
// indent recursively inserts proper indentation between an
|
||||
// XML element's child tokens.
|
||||
func (e *Element) indent(depth int, indent indentFunc) {
|
||||
e.stripIndent()
|
||||
n := len(e.Child)
|
||||
if n == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
oldChild := e.Child
|
||||
e.Child = make([]Token, 0, n*2+1)
|
||||
isCharData, firstNonCharData := false, true
|
||||
for _, c := range oldChild {
|
||||
|
||||
// Insert CR+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 {
|
||||
newCharData(indent(depth), true, e)
|
||||
}
|
||||
firstNonCharData = false
|
||||
}
|
||||
|
||||
e.addChild(c)
|
||||
|
||||
// Recursively process child elements.
|
||||
if ce, ok := c.(*Element); ok {
|
||||
ce.indent(depth+1, indent)
|
||||
}
|
||||
}
|
||||
|
||||
// Insert CR+indent before the last child.
|
||||
if !isCharData {
|
||||
if !firstNonCharData || depth > 0 {
|
||||
newCharData(indent(depth-1), true, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stripIndent removes any previously inserted indentation.
|
||||
func (e *Element) stripIndent() {
|
||||
// Count the number of non-indent child tokens
|
||||
n := len(e.Child)
|
||||
for _, c := range e.Child {
|
||||
if cd, ok := c.(*CharData); ok && cd.whitespace {
|
||||
n--
|
||||
}
|
||||
}
|
||||
if n == len(e.Child) {
|
||||
return
|
||||
}
|
||||
|
||||
// Strip out indent CharData
|
||||
newChild := make([]Token, n)
|
||||
j := 0
|
||||
for _, c := range e.Child {
|
||||
if cd, ok := c.(*CharData); ok && cd.whitespace {
|
||||
continue
|
||||
}
|
||||
newChild[j] = c
|
||||
j++
|
||||
}
|
||||
e.Child = newChild
|
||||
}
|
||||
|
||||
// dup duplicates the element.
|
||||
func (e *Element) dup(parent *Element) Token {
|
||||
ne := &Element{
|
||||
Space: e.Space,
|
||||
Tag: e.Tag,
|
||||
Attr: make([]Attr, len(e.Attr)),
|
||||
Child: make([]Token, len(e.Child)),
|
||||
parent: parent,
|
||||
}
|
||||
for i, t := range e.Child {
|
||||
ne.Child[i] = t.dup(ne)
|
||||
}
|
||||
for i, a := range e.Attr {
|
||||
ne.Attr[i] = a
|
||||
}
|
||||
return ne
|
||||
}
|
||||
|
||||
// Parent returns the element token's parent element, or nil if it has no
|
||||
// parent.
|
||||
func (e *Element) Parent() *Element {
|
||||
return e.parent
|
||||
}
|
||||
|
||||
// setParent replaces the element token's parent.
|
||||
func (e *Element) setParent(parent *Element) {
|
||||
e.parent = parent
|
||||
}
|
||||
|
||||
// writeTo serializes the element to the writer w.
|
||||
func (e *Element) writeTo(w *bufio.Writer, s *WriteSettings) {
|
||||
w.WriteByte('<')
|
||||
if e.Space != "" {
|
||||
w.WriteString(e.Space)
|
||||
w.WriteByte(':')
|
||||
}
|
||||
w.WriteString(e.Tag)
|
||||
for _, a := range e.Attr {
|
||||
w.WriteByte(' ')
|
||||
a.writeTo(w, s)
|
||||
}
|
||||
if len(e.Child) > 0 {
|
||||
w.WriteString(">")
|
||||
for _, c := range e.Child {
|
||||
c.writeTo(w, s)
|
||||
}
|
||||
w.Write([]byte{'<', '/'})
|
||||
if e.Space != "" {
|
||||
w.WriteString(e.Space)
|
||||
w.WriteByte(':')
|
||||
}
|
||||
w.WriteString(e.Tag)
|
||||
w.WriteByte('>')
|
||||
} else {
|
||||
if s.CanonicalEndTags {
|
||||
w.Write([]byte{'>', '<', '/'})
|
||||
if e.Space != "" {
|
||||
w.WriteString(e.Space)
|
||||
w.WriteByte(':')
|
||||
}
|
||||
w.WriteString(e.Tag)
|
||||
w.WriteByte('>')
|
||||
} else {
|
||||
w.Write([]byte{'/', '>'})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addChild adds a child token to the element e.
|
||||
func (e *Element) addChild(t Token) {
|
||||
e.Child = append(e.Child, t)
|
||||
}
|
||||
|
||||
// CreateAttr creates an attribute and adds it to element e. The key may be
|
||||
// prefixed by a namespace and a colon. If an attribute with the key already
|
||||
// exists, its value is replaced.
|
||||
func (e *Element) CreateAttr(key, value string) *Attr {
|
||||
space, skey := spaceDecompose(key)
|
||||
return e.createAttr(space, skey, value)
|
||||
}
|
||||
|
||||
// createAttr is a helper function that creates attributes.
|
||||
func (e *Element) createAttr(space, key, value string) *Attr {
|
||||
for i, a := range e.Attr {
|
||||
if space == a.Space && key == a.Key {
|
||||
e.Attr[i].Value = value
|
||||
return &e.Attr[i]
|
||||
}
|
||||
}
|
||||
a := Attr{space, key, value}
|
||||
e.Attr = append(e.Attr, a)
|
||||
return &e.Attr[len(e.Attr)-1]
|
||||
}
|
||||
|
||||
// RemoveAttr removes and returns the first attribute of the element whose key
|
||||
// matches the given key. The key may be prefixed by a namespace and a colon.
|
||||
// If an equal attribute does not exist, nil is returned.
|
||||
func (e *Element) RemoveAttr(key string) *Attr {
|
||||
space, skey := spaceDecompose(key)
|
||||
for i, a := range e.Attr {
|
||||
if space == a.Space && skey == a.Key {
|
||||
e.Attr = append(e.Attr[0:i], e.Attr[i+1:]...)
|
||||
return &a
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var xmlReplacerNormal = strings.NewReplacer(
|
||||
"&", "&",
|
||||
"<", "<",
|
||||
">", ">",
|
||||
"'", "'",
|
||||
`"`, """,
|
||||
)
|
||||
|
||||
var xmlReplacerCanonicalText = strings.NewReplacer(
|
||||
"&", "&",
|
||||
"<", "<",
|
||||
">", ">",
|
||||
"\r", "
",
|
||||
)
|
||||
|
||||
var xmlReplacerCanonicalAttrVal = strings.NewReplacer(
|
||||
"&", "&",
|
||||
"<", "<",
|
||||
`"`, """,
|
||||
"\t", "	",
|
||||
"\n", "
",
|
||||
"\r", "
",
|
||||
)
|
||||
|
||||
// writeTo serializes the attribute to the writer.
|
||||
func (a *Attr) writeTo(w *bufio.Writer, s *WriteSettings) {
|
||||
if a.Space != "" {
|
||||
w.WriteString(a.Space)
|
||||
w.WriteByte(':')
|
||||
}
|
||||
w.WriteString(a.Key)
|
||||
w.WriteString(`="`)
|
||||
var r *strings.Replacer
|
||||
if s.CanonicalAttrVal {
|
||||
r = xmlReplacerCanonicalAttrVal
|
||||
} else {
|
||||
r = xmlReplacerNormal
|
||||
}
|
||||
w.WriteString(r.Replace(a.Value))
|
||||
w.WriteByte('"')
|
||||
}
|
||||
|
||||
// NewCharData creates a parentless XML character data entity.
|
||||
func NewCharData(data string) *CharData {
|
||||
return newCharData(data, false, nil)
|
||||
}
|
||||
|
||||
// newCharData creates an XML character data entity and binds it to a parent
|
||||
// element. If parent is nil, the CharData token remains unbound.
|
||||
func newCharData(data string, whitespace bool, parent *Element) *CharData {
|
||||
c := &CharData{
|
||||
Data: data,
|
||||
whitespace: whitespace,
|
||||
parent: parent,
|
||||
}
|
||||
if parent != nil {
|
||||
parent.addChild(c)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// CreateCharData creates an XML character data entity and adds it as a child
|
||||
// of element e.
|
||||
func (e *Element) CreateCharData(data string) *CharData {
|
||||
return newCharData(data, false, e)
|
||||
}
|
||||
|
||||
// dup duplicates the character data.
|
||||
func (c *CharData) dup(parent *Element) Token {
|
||||
return &CharData{
|
||||
Data: c.Data,
|
||||
whitespace: c.whitespace,
|
||||
parent: parent,
|
||||
}
|
||||
}
|
||||
|
||||
// Parent returns the character data token's parent element, or nil if it has
|
||||
// no parent.
|
||||
func (c *CharData) Parent() *Element {
|
||||
return c.parent
|
||||
}
|
||||
|
||||
// setParent replaces the character data token's parent.
|
||||
func (c *CharData) setParent(parent *Element) {
|
||||
c.parent = parent
|
||||
}
|
||||
|
||||
// writeTo serializes the character data entity to the writer.
|
||||
func (c *CharData) writeTo(w *bufio.Writer, s *WriteSettings) {
|
||||
var r *strings.Replacer
|
||||
if s.CanonicalText {
|
||||
r = xmlReplacerCanonicalText
|
||||
} else {
|
||||
r = xmlReplacerNormal
|
||||
}
|
||||
w.WriteString(r.Replace(c.Data))
|
||||
}
|
||||
|
||||
// NewComment creates a parentless XML comment.
|
||||
func NewComment(comment string) *Comment {
|
||||
return newComment(comment, nil)
|
||||
}
|
||||
|
||||
// NewComment creates an XML comment and binds it to a parent element. If
|
||||
// parent is nil, the Comment remains unbound.
|
||||
func newComment(comment string, parent *Element) *Comment {
|
||||
c := &Comment{
|
||||
Data: comment,
|
||||
parent: parent,
|
||||
}
|
||||
if parent != nil {
|
||||
parent.addChild(c)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// CreateComment creates an XML comment and adds it as a child of element e.
|
||||
func (e *Element) CreateComment(comment string) *Comment {
|
||||
return newComment(comment, e)
|
||||
}
|
||||
|
||||
// dup duplicates the comment.
|
||||
func (c *Comment) dup(parent *Element) Token {
|
||||
return &Comment{
|
||||
Data: c.Data,
|
||||
parent: parent,
|
||||
}
|
||||
}
|
||||
|
||||
// Parent returns comment token's parent element, or nil if it has no parent.
|
||||
func (c *Comment) Parent() *Element {
|
||||
return c.parent
|
||||
}
|
||||
|
||||
// setParent replaces the comment token's parent.
|
||||
func (c *Comment) setParent(parent *Element) {
|
||||
c.parent = parent
|
||||
}
|
||||
|
||||
// writeTo serialies the comment to the writer.
|
||||
func (c *Comment) writeTo(w *bufio.Writer, s *WriteSettings) {
|
||||
w.WriteString("<!--")
|
||||
w.WriteString(c.Data)
|
||||
w.WriteString("-->")
|
||||
}
|
||||
|
||||
// NewDirective creates a parentless XML directive.
|
||||
func NewDirective(data string) *Directive {
|
||||
return newDirective(data, nil)
|
||||
}
|
||||
|
||||
// newDirective creates an XML directive and binds it to a parent element. If
|
||||
// parent is nil, the Directive remains unbound.
|
||||
func newDirective(data string, parent *Element) *Directive {
|
||||
d := &Directive{
|
||||
Data: data,
|
||||
parent: parent,
|
||||
}
|
||||
if parent != nil {
|
||||
parent.addChild(d)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// CreateDirective creates an XML directive and adds it as the last child of
|
||||
// element e.
|
||||
func (e *Element) CreateDirective(data string) *Directive {
|
||||
return newDirective(data, e)
|
||||
}
|
||||
|
||||
// dup duplicates the directive.
|
||||
func (d *Directive) dup(parent *Element) Token {
|
||||
return &Directive{
|
||||
Data: d.Data,
|
||||
parent: parent,
|
||||
}
|
||||
}
|
||||
|
||||
// Parent returns directive token's parent element, or nil if it has no
|
||||
// parent.
|
||||
func (d *Directive) Parent() *Element {
|
||||
return d.parent
|
||||
}
|
||||
|
||||
// setParent replaces the directive token's parent.
|
||||
func (d *Directive) setParent(parent *Element) {
|
||||
d.parent = parent
|
||||
}
|
||||
|
||||
// writeTo serializes the XML directive to the writer.
|
||||
func (d *Directive) writeTo(w *bufio.Writer, s *WriteSettings) {
|
||||
w.WriteString("<!")
|
||||
w.WriteString(d.Data)
|
||||
w.WriteString(">")
|
||||
}
|
||||
|
||||
// NewProcInst creates a parentless XML processing instruction.
|
||||
func NewProcInst(target, inst string) *ProcInst {
|
||||
return newProcInst(target, inst, nil)
|
||||
}
|
||||
|
||||
// newProcInst creates an XML processing instruction and binds it to a parent
|
||||
// element. If parent is nil, the ProcInst remains unbound.
|
||||
func newProcInst(target, inst string, parent *Element) *ProcInst {
|
||||
p := &ProcInst{
|
||||
Target: target,
|
||||
Inst: inst,
|
||||
parent: parent,
|
||||
}
|
||||
if parent != nil {
|
||||
parent.addChild(p)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// CreateProcInst creates a processing instruction and adds it as a child of
|
||||
// element e.
|
||||
func (e *Element) CreateProcInst(target, inst string) *ProcInst {
|
||||
return newProcInst(target, inst, e)
|
||||
}
|
||||
|
||||
// dup duplicates the procinst.
|
||||
func (p *ProcInst) dup(parent *Element) Token {
|
||||
return &ProcInst{
|
||||
Target: p.Target,
|
||||
Inst: p.Inst,
|
||||
parent: parent,
|
||||
}
|
||||
}
|
||||
|
||||
// Parent returns processing instruction token's parent element, or nil if it
|
||||
// has no parent.
|
||||
func (p *ProcInst) Parent() *Element {
|
||||
return p.parent
|
||||
}
|
||||
|
||||
// setParent replaces the processing instruction token's parent.
|
||||
func (p *ProcInst) setParent(parent *Element) {
|
||||
p.parent = parent
|
||||
}
|
||||
|
||||
// writeTo serializes the processing instruction to the writer.
|
||||
func (p *ProcInst) writeTo(w *bufio.Writer, s *WriteSettings) {
|
||||
w.WriteString("<?")
|
||||
w.WriteString(p.Target)
|
||||
if p.Inst != "" {
|
||||
w.WriteByte(' ')
|
||||
w.WriteString(p.Inst)
|
||||
}
|
||||
w.WriteString("?>")
|
||||
}
|
188
vendor/github.com/beevik/etree/helpers.go
generated
vendored
Normal file
188
vendor/github.com/beevik/etree/helpers.go
generated
vendored
Normal file
|
@ -0,0 +1,188 @@
|
|||
// Copyright 2015 Brett Vickers.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package etree
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A simple stack
|
||||
type stack struct {
|
||||
data []interface{}
|
||||
}
|
||||
|
||||
func (s *stack) empty() bool {
|
||||
return len(s.data) == 0
|
||||
}
|
||||
|
||||
func (s *stack) push(value interface{}) {
|
||||
s.data = append(s.data, value)
|
||||
}
|
||||
|
||||
func (s *stack) pop() interface{} {
|
||||
value := s.data[len(s.data)-1]
|
||||
s.data[len(s.data)-1] = nil
|
||||
s.data = s.data[:len(s.data)-1]
|
||||
return value
|
||||
}
|
||||
|
||||
func (s *stack) peek() interface{} {
|
||||
return s.data[len(s.data)-1]
|
||||
}
|
||||
|
||||
// A fifo is a simple first-in-first-out queue.
|
||||
type fifo struct {
|
||||
data []interface{}
|
||||
head, tail int
|
||||
}
|
||||
|
||||
func (f *fifo) add(value interface{}) {
|
||||
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 *fifo) remove() interface{} {
|
||||
value := f.data[f.head]
|
||||
f.data[f.head] = nil
|
||||
if f.head++; f.head == len(f.data) {
|
||||
f.head = 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (f *fifo) len() int {
|
||||
if f.tail >= f.head {
|
||||
return f.tail - f.head
|
||||
}
|
||||
return len(f.data) - f.head + f.tail
|
||||
}
|
||||
|
||||
func (f *fifo) grow() {
|
||||
c := len(f.data) * 2
|
||||
if c == 0 {
|
||||
c = 4
|
||||
}
|
||||
buf, count := make([]interface{}, c), f.len()
|
||||
if f.tail >= f.head {
|
||||
copy(buf[0:count], f.data[f.head:f.tail])
|
||||
} else {
|
||||
hindex := len(f.data) - f.head
|
||||
copy(buf[0:hindex], f.data[f.head:])
|
||||
copy(buf[hindex:count], f.data[:f.tail])
|
||||
}
|
||||
f.data, f.head, f.tail = buf, 0, count
|
||||
}
|
||||
|
||||
// countReader implements a proxy reader that counts the number of
|
||||
// bytes read from its encapsulated reader.
|
||||
type countReader struct {
|
||||
r io.Reader
|
||||
bytes int64
|
||||
}
|
||||
|
||||
func newCountReader(r io.Reader) *countReader {
|
||||
return &countReader{r: r}
|
||||
}
|
||||
|
||||
func (cr *countReader) Read(p []byte) (n int, err error) {
|
||||
b, err := cr.r.Read(p)
|
||||
cr.bytes += int64(b)
|
||||
return b, err
|
||||
}
|
||||
|
||||
// countWriter implements a proxy writer that counts the number of
|
||||
// bytes written by its encapsulated writer.
|
||||
type countWriter struct {
|
||||
w io.Writer
|
||||
bytes int64
|
||||
}
|
||||
|
||||
func newCountWriter(w io.Writer) *countWriter {
|
||||
return &countWriter{w: w}
|
||||
}
|
||||
|
||||
func (cw *countWriter) Write(p []byte) (n int, err error) {
|
||||
b, err := cw.w.Write(p)
|
||||
cw.bytes += int64(b)
|
||||
return b, 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 crIndent
|
||||
const (
|
||||
crsp = "\n "
|
||||
crtab = "\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"
|
||||
)
|
||||
|
||||
// crIndent returns a carriage return followed by n copies of the
|
||||
// first non-CR character in the source string.
|
||||
func crIndent(n int, source string) string {
|
||||
switch {
|
||||
case n < 0:
|
||||
return source[:1]
|
||||
case n < len(source):
|
||||
return source[:n+1]
|
||||
default:
|
||||
return source + strings.Repeat(source[1:2], n-len(source)+1)
|
||||
}
|
||||
}
|
||||
|
||||
// nextIndex returns the index of the next occurrence of sep in s,
|
||||
// starting from offset. It returns -1 if the sep string is not found.
|
||||
func nextIndex(s, sep string, offset int) int {
|
||||
switch i := strings.Index(s[offset:], sep); 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
|
||||
}
|
516
vendor/github.com/beevik/etree/path.go
generated
vendored
Normal file
516
vendor/github.com/beevik/etree/path.go
generated
vendored
Normal file
|
@ -0,0 +1,516 @@
|
|||
// Copyright 2015 Brett Vickers.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package etree
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
A Path is an object that represents an optimized version of an
|
||||
XPath-like search string. Although path strings are XPath-like,
|
||||
only the following limited syntax is supported:
|
||||
|
||||
. Selects the current element
|
||||
.. Selects the parent of the current element
|
||||
* Selects all child elements
|
||||
// Selects all descendants of the current element
|
||||
tag Selects all child elements with the given tag
|
||||
[#] Selects the element of the given index (1-based,
|
||||
negative starts from the end)
|
||||
[@attrib] Selects all elements with the given attribute
|
||||
[@attrib='val'] Selects all elements with the given attribute set to val
|
||||
[tag] Selects all elements with a child element named tag
|
||||
[tag='val'] Selects all elements with a child element named tag
|
||||
and text matching val
|
||||
[text()] Selects all elements with non-empty text
|
||||
[text()='val'] Selects all elements whose text matches val
|
||||
|
||||
Examples:
|
||||
|
||||
Select the title elements of all descendant book elements having a
|
||||
'category' attribute of 'WEB':
|
||||
//book[@category='WEB']/title
|
||||
|
||||
Select the first book element with a title child containing the text
|
||||
'Great Expectations':
|
||||
.//book[title='Great Expectations'][1]
|
||||
|
||||
Starting from the current element, select all children of book elements
|
||||
with an attribute 'language' set to 'english':
|
||||
./book/*[@language='english']
|
||||
|
||||
Starting from the current element, select all children of book elements
|
||||
containing the text 'special':
|
||||
./book/*[text()='special']
|
||||
|
||||
Select all descendant book elements whose title element has an attribute
|
||||
'language' set to 'french':
|
||||
//book/title[@language='french']/..
|
||||
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
// 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 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 fifo
|
||||
results []*Element
|
||||
inResults map[*Element]bool
|
||||
candidates []*Element
|
||||
scratch []*Element // used by filters
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func newPather() *pather {
|
||||
return &pather{
|
||||
results: make([]*Element, 0),
|
||||
inResults: make(map[*Element]bool),
|
||||
candidates: make([]*Element, 0),
|
||||
scratch: make([]*Element, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// traverse follows the path from the element e, collecting
|
||||
// and then returning all elements that match the path's selectors
|
||||
// and filters.
|
||||
func (p *pather) traverse(e *Element, path Path) []*Element {
|
||||
for p.queue.add(node{e, path.segments}); p.queue.len() > 0; {
|
||||
p.eval(p.queue.remove().(node))
|
||||
}
|
||||
return p.results
|
||||
}
|
||||
|
||||
// eval evalutes the current path node by applying the remaining
|
||||
// path's selector rules against the node's element.
|
||||
func (p *pather) eval(n node) {
|
||||
p.candidates = p.candidates[0: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
|
||||
p.results = append(p.results, c)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, c := range p.candidates {
|
||||
p.queue.add(node{c, remain})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 starts or ends with //, fix it
|
||||
if strings.HasPrefix(path, "//") {
|
||||
path = "." + path
|
||||
}
|
||||
if strings.HasSuffix(path, "//") {
|
||||
path = path + "*"
|
||||
}
|
||||
|
||||
// Paths cannot be absolute
|
||||
if strings.HasPrefix(path, "/") {
|
||||
c.err = ErrPath("paths cannot be absolute.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Split path into segment objects
|
||||
var segments []segment
|
||||
for _, s := range splitPath(path) {
|
||||
segments = append(segments, c.parseSegment(s))
|
||||
if c.err != ErrPath("") {
|
||||
break
|
||||
}
|
||||
}
|
||||
return segments
|
||||
}
|
||||
|
||||
func splitPath(path string) []string {
|
||||
pieces := make([]string, 0)
|
||||
start := 0
|
||||
inquote := false
|
||||
for i := 0; i+1 <= len(path); i++ {
|
||||
if path[i] == '\'' {
|
||||
inquote = !inquote
|
||||
} else if path[i] == '/' && !inquote {
|
||||
pieces = append(pieces, path[start:i])
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
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: make([]filter, 0),
|
||||
}
|
||||
for i := 1; i < len(pieces); i++ {
|
||||
fpath := pieces[i]
|
||||
if 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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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'], [text()='val'], or [tag='val']?
|
||||
eqindex := strings.Index(path, "='")
|
||||
if eqindex >= 0 {
|
||||
rindex := nextIndex(path, "'", eqindex+2)
|
||||
if rindex != len(path)-1 {
|
||||
c.err = ErrPath("path has mismatched filter quotes.")
|
||||
return nil
|
||||
}
|
||||
switch {
|
||||
case path[0] == '@':
|
||||
return newFilterAttrVal(path[1:eqindex], path[eqindex+2:rindex])
|
||||
case strings.HasPrefix(path, "text()"):
|
||||
return newFilterTextVal(path[eqindex+2 : rindex])
|
||||
default:
|
||||
return newFilterChildText(path[:eqindex], path[eqindex+2:rindex])
|
||||
}
|
||||
}
|
||||
|
||||
// Filter contains [@attr], [N], [tag] or [text()]
|
||||
switch {
|
||||
case path[0] == '@':
|
||||
return newFilterAttr(path[1:])
|
||||
case path == "text()":
|
||||
return newFilterText()
|
||||
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)
|
||||
}
|
||||
|
||||
// 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 fifo
|
||||
for queue.add(e); queue.len() > 0; {
|
||||
e := queue.remove().(*Element)
|
||||
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]
|
||||
}
|
||||
|
||||
// filterText filters the candidate list for elements having text.
|
||||
type filterText struct{}
|
||||
|
||||
func newFilterText() *filterText {
|
||||
return &filterText{}
|
||||
}
|
||||
|
||||
func (f *filterText) apply(p *pather) {
|
||||
for _, c := range p.candidates {
|
||||
if c.Text() != "" {
|
||||
p.scratch = append(p.scratch, c)
|
||||
}
|
||||
}
|
||||
p.candidates, p.scratch = p.scratch, p.candidates[0:0]
|
||||
}
|
||||
|
||||
// filterTextVal filters the candidate list for elements having
|
||||
// text equal to the specified value.
|
||||
type filterTextVal struct {
|
||||
val string
|
||||
}
|
||||
|
||||
func newFilterTextVal(value string) *filterTextVal {
|
||||
return &filterTextVal{value}
|
||||
}
|
||||
|
||||
func (f *filterTextVal) apply(p *pather) {
|
||||
for _, c := range p.candidates {
|
||||
if c.Text() == 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]
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue