diff --git a/go.mod b/go.mod index 015abb01b..c026f9674 100644 --- a/go.mod +++ b/go.mod @@ -79,7 +79,7 @@ require ( require ( github.com/ajeddeloh/go-json v0.0.0-20200220154158-5ae607161559 // indirect - github.com/antchfx/xmlquery v1.4.3 + github.com/antchfx/xmlquery v1.4.4 github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 github.com/cespare/xxhash/v2 v2.3.0 // indirect diff --git a/go.sum b/go.sum index ce024f1b5..357146a91 100644 --- a/go.sum +++ b/go.sum @@ -37,6 +37,8 @@ github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vS github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/antchfx/xmlquery v1.4.3 h1:f6jhxCzANrWfa93O+NmRWvieVyLs+R2Szfpy+YrZaww= github.com/antchfx/xmlquery v1.4.3/go.mod h1:AEPEEPYE9GnA2mj5Ur2L5Q5/2PycJ0N9Fusrx9b12fc= +github.com/antchfx/xmlquery v1.4.4 h1:mxMEkdYP3pjKSftxss4nUHfjBhnMk4imGoR96FRY2dg= +github.com/antchfx/xmlquery v1.4.4/go.mod h1:AEPEEPYE9GnA2mj5Ur2L5Q5/2PycJ0N9Fusrx9b12fc= github.com/antchfx/xpath v1.3.3 h1:tmuPQa1Uye0Ym1Zn65vxPgfltWb/Lxu2jeqIGteJSRs= github.com/antchfx/xpath v1.3.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= diff --git a/vendor/github.com/antchfx/xmlquery/cached_reader.go b/vendor/github.com/antchfx/xmlquery/cached_reader.go index fe389c5d5..eb35d1be2 100644 --- a/vendor/github.com/antchfx/xmlquery/cached_reader.go +++ b/vendor/github.com/antchfx/xmlquery/cached_reader.go @@ -5,45 +5,48 @@ import ( ) type cachedReader struct { - buffer *bufio.Reader - cache []byte - cacheCap int - cacheLen int + buffer *bufio.Reader + cache []byte caching bool } func newCachedReader(r *bufio.Reader) *cachedReader { return &cachedReader{ - buffer: r, - cache: make([]byte, 4096), - cacheCap: 4096, - cacheLen: 0, - caching: false, + buffer: r, + cache: make([]byte, 0, 4096), + caching: false, } } func (c *cachedReader) StartCaching() { - c.cacheLen = 0 + c.cache = c.cache[:0] c.caching = true } -func (c *cachedReader) ReadByte() (byte, error) { - if !c.caching { - return c.buffer.ReadByte() - } - b, err := c.buffer.ReadByte() +func (c *cachedReader) ReadByte() (b byte, err error) { + b, err = c.buffer.ReadByte() if err != nil { - return b, err + return } - if c.cacheLen < c.cacheCap { - c.cache[c.cacheLen] = b - c.cacheLen++ + if c.caching { + c.cacheByte(b) } - return b, err + return } func (c *cachedReader) Cache() []byte { - return c.cache[:c.cacheLen] + return c.cache +} + +func (c *cachedReader) CacheWithLimit(n int) []byte { + if n < 1 { + return nil + } + l := len(c.cache) + if n > l { + n = l + } + return c.cache[:n] } func (c *cachedReader) StopCaching() { @@ -55,11 +58,9 @@ func (c *cachedReader) Read(p []byte) (int, error) { if err != nil { return n, err } - if c.caching && c.cacheLen < c.cacheCap { + if c.caching { for i := 0; i < n; i++ { - c.cache[c.cacheLen] = p[i] - c.cacheLen++ - if c.cacheLen >= c.cacheCap { + if !c.cacheByte(p[i]) { break } } @@ -67,3 +68,12 @@ func (c *cachedReader) Read(p []byte) (int, error) { return n, err } +func (c *cachedReader) cacheByte(b byte) bool { + n := len(c.cache) + if n == cap(c.cache) { + return false + } + c.cache = c.cache[:n+1] + c.cache[n] = b + return true +} diff --git a/vendor/github.com/antchfx/xmlquery/node.go b/vendor/github.com/antchfx/xmlquery/node.go index 03e0ce912..f864bc6ae 100644 --- a/vendor/github.com/antchfx/xmlquery/node.go +++ b/vendor/github.com/antchfx/xmlquery/node.go @@ -92,6 +92,13 @@ func WithPreserveSpace() OutputOption { } } +// WithoutPreserveSpace will not preserve spaces in output +func WithoutPreserveSpace() OutputOption { + return func(oc *outputConfiguration) { + oc.preserveSpaces = false + } +} + // WithIndentation sets the indentation string used for formatting the output. func WithIndentation(indentation string) OutputOption { return func(oc *outputConfiguration) { @@ -155,7 +162,7 @@ type indentation struct { level int hasChild bool indent string - w io.Writer + w io.Writer } func newIndentation(indent string, w io.Writer) *indentation { @@ -168,101 +175,139 @@ func newIndentation(indent string, w io.Writer) *indentation { } } -func (i *indentation) NewLine() { +func (i *indentation) NewLine() (err error) { if i == nil { return } - io.WriteString(i.w, "\n") + _, err = io.WriteString(i.w, "\n") + return } -func (i *indentation) Open() { +func (i *indentation) Open() (err error) { if i == nil { return } - io.WriteString(i.w, "\n") - io.WriteString(i.w, strings.Repeat(i.indent, i.level)) + if err = i.writeIndent(); err != nil { + return + } i.level++ i.hasChild = false + return } -func (i *indentation) Close() { +func (i *indentation) Close() (err error) { if i == nil { return } i.level-- if i.hasChild { - io.WriteString(i.w, "\n") - io.WriteString(i.w, strings.Repeat(i.indent, i.level)) + if err = i.writeIndent(); err != nil { + return + } } i.hasChild = true + return +} + +func (i *indentation) writeIndent() (err error) { + _, err = io.WriteString(i.w, "\n") + if err != nil { + return + } + _, err = io.WriteString(i.w, strings.Repeat(i.indent, i.level)) + return } -func outputXML(w io.Writer, n *Node, preserveSpaces bool, config *outputConfiguration, indent *indentation) { +func outputXML(w io.Writer, n *Node, preserveSpaces bool, config *outputConfiguration, indent *indentation) (err error) { preserveSpaces = calculatePreserveSpaces(n, preserveSpaces) switch n.Type { case TextNode: - io.WriteString(w, html.EscapeString(n.sanitizedData(preserveSpaces))) + _, err = io.WriteString(w, html.EscapeString(n.sanitizedData(preserveSpaces))) return case CharDataNode: - io.WriteString(w, "") + _, err = fmt.Fprintf(w, "", n.Data) return case CommentNode: if !config.skipComments { - io.WriteString(w, "") + _, err = fmt.Fprintf(w, "", n.Data) } return case NotationNode: - indent.NewLine() - fmt.Fprintf(w, "", n.Data) + if err = indent.NewLine(); err != nil { + return + } + _, err = fmt.Fprintf(w, "", n.Data) return case DeclarationNode: - io.WriteString(w, "") + _, err = io.WriteString(w, "?>") } else { if n.FirstChild != nil || !config.emptyElementTagSupport { - io.WriteString(w, ">") + _, err = io.WriteString(w, ">") } else { - io.WriteString(w, "/>") - indent.Close() + _, err = io.WriteString(w, "/>") + if err != nil { + return + } + err = indent.Close() return } } + if err != nil { + return + } for child := n.FirstChild; child != nil; child = child.NextSibling { - outputXML(w, child, preserveSpaces, config, indent) + err = outputXML(w, child, preserveSpaces, config, indent) + if err != nil { + return + } } if n.Type != DeclarationNode { - indent.Close() + if err = indent.Close(); err != nil { + return + } if n.Prefix == "" { - fmt.Fprintf(w, "", n.Data) + _, err = fmt.Fprintf(w, "", n.Data) } else { - fmt.Fprintf(w, "", n.Prefix, n.Data) + _, err = fmt.Fprintf(w, "", n.Prefix, n.Data) } } + return } // OutputXML returns the text that including tags name. @@ -281,16 +326,18 @@ func (n *Node) OutputXMLWithOptions(opts ...OutputOption) string { } // Write writes xml to given writer. -func (n *Node) Write(writer io.Writer, self bool) { +func (n *Node) Write(writer io.Writer, self bool) error { if self { - n.WriteWithOptions(writer, WithOutputSelf()) + return n.WriteWithOptions(writer, WithOutputSelf()) } - n.WriteWithOptions(writer) + return n.WriteWithOptions(writer) } // WriteWithOptions writes xml with given options to given writer. -func (n *Node) WriteWithOptions(writer io.Writer, opts ...OutputOption) { - config := &outputConfiguration{} +func (n *Node) WriteWithOptions(writer io.Writer, opts ...OutputOption) (err error) { + config := &outputConfiguration{ + preserveSpaces: true, + } // Set the options for _, opt := range opts { opt(config) @@ -300,13 +347,18 @@ func (n *Node) WriteWithOptions(writer io.Writer, opts ...OutputOption) { b := bufio.NewWriter(writer) defer b.Flush() + ident := newIndentation(config.useIndentation, b) if config.printSelf && n.Type != DocumentNode { - outputXML(b, n, preserveSpaces, config, newIndentation(config.useIndentation, b)) + err = outputXML(b, n, preserveSpaces, config, ident) } else { for n := n.FirstChild; n != nil; n = n.NextSibling { - outputXML(b, n, preserveSpaces, config, newIndentation(config.useIndentation, b)) + err = outputXML(b, n, preserveSpaces, config, ident) + if err != nil { + break + } } } + return } // AddAttr adds a new attribute specified by 'key' and 'val' to a node 'n'. @@ -357,11 +409,7 @@ func AddChild(parent, n *Node) { parent.LastChild = n } -// AddSibling adds a new node 'n' as a sibling of a given node 'sibling'. -// Note it is not necessarily true that the new node 'n' would be added -// immediately after 'sibling'. If 'sibling' isn't the last child of its -// parent, then the new node 'n' will be added at the end of the sibling -// chain of their parent. +// AddSibling adds a new node 'n' as a last node of sibling chain for a given node 'sibling'. func AddSibling(sibling, n *Node) { for t := sibling.NextSibling; t != nil; t = t.NextSibling { sibling = t @@ -375,6 +423,19 @@ func AddSibling(sibling, n *Node) { } } +// AddImmediateSibling adds a new node 'n' as immediate sibling a given node 'sibling'. +func AddImmediateSibling(sibling, n *Node) { + n.Parent = sibling.Parent + n.NextSibling = sibling.NextSibling + sibling.NextSibling = n + n.PrevSibling = sibling + if n.NextSibling != nil { + n.NextSibling.PrevSibling = n + } else if n.Parent != nil { + sibling.Parent.LastChild = n + } +} + // RemoveFromTree removes a node and its subtree from the document // tree it is in. If the node is the root of the tree, then it's no-op. func RemoveFromTree(n *Node) { @@ -402,3 +463,15 @@ func RemoveFromTree(n *Node) { n.PrevSibling = nil n.NextSibling = nil } + +// GetRoot returns a root of the tree where 'n' is a node. +func GetRoot(n *Node) *Node { + if n == nil { + return nil + } + root := n + for root.Parent != nil { + root = root.Parent + } + return root +} diff --git a/vendor/github.com/antchfx/xmlquery/parse.go b/vendor/github.com/antchfx/xmlquery/parse.go index 7627f4650..d359b50c5 100644 --- a/vendor/github.com/antchfx/xmlquery/parse.go +++ b/vendor/github.com/antchfx/xmlquery/parse.go @@ -2,6 +2,7 @@ package xmlquery import ( "bufio" + "bytes" "encoding/xml" "fmt" "io" @@ -39,15 +40,31 @@ func Parse(r io.Reader) (*Node, error) { func ParseWithOptions(r io.Reader, options ParserOptions) (*Node, error) { p := createParser(r) options.apply(p) - for { - _, err := p.parse() - if err == io.EOF { - return p.doc, nil + var err error + for err == nil { + _, err = p.parse() + } + + if err == io.EOF { + // additional check for validity + // according to: https://www.w3.org/TR/xml + // the document MUST contain at least ONE element + valid := false + for doc := p.doc; doc != nil; doc = doc.NextSibling { + for node := doc.FirstChild; node != nil; node = node.NextSibling { + if node.Type == ElementNode { + valid = true + break + } + } } - if err != nil { - return nil, err + if !valid { + return nil, fmt.Errorf("xmlquery: invalid XML document") } + return p.doc, nil } + + return nil, err } type parser struct { @@ -168,7 +185,7 @@ func (p *parser) parse() (*Node, error) { if node.NamespaceURI != "" { if v, ok := p.space2prefix[node.NamespaceURI]; ok { - cached := string(p.reader.Cache()) + cached := string(p.reader.CacheWithLimit(len(v.name) + len(node.Data) + 2)) if strings.HasPrefix(cached, fmt.Sprintf("%s:%s", v.name, node.Data)) || strings.HasPrefix(cached, fmt.Sprintf("<%s:%s", v.name, node.Data)) { node.Prefix = v.name } @@ -228,12 +245,11 @@ func (p *parser) parse() (*Node, error) { } case xml.CharData: // First, normalize the cache... - cached := strings.ToUpper(string(p.reader.Cache())) + cached := bytes.ToUpper(p.reader.CacheWithLimit(9)) nodeType := TextNode - if strings.HasPrefix(cached, "