xmltree

package
v1.132.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 17, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package xmltree is the platform's one XML reader: a bounded decoder from a document to a tree of elements, a small path language over that tree, and an encoder back to a document.

It exists as its own package because two callers need the same answer and must not fork it (#1735): a managed script reads an XML or SOAP response through the predeclared `xml` module, and api_invoke_endpoint decodes an XML-typed response body into the same tree. Neither Starlark nor HTTP appears here, which is what lets the tree, the limits and the path subset be tested against hand-written documents with no engine and no upstream.

Everything in the package is a pure function of its input: no I/O, no clock, no entity resolution, no network. A document is read exactly as it was handed over.

Index

Constants

View Source
const PathSyntax = `supported paths are child steps (a/b/c), a descendant step (//c), ` +
	`the wildcard *, an attribute predicate [@name='value'] and a positional predicate [n]; ` +
	`names match on local name, so an upstream's namespace prefix does not matter`

PathSyntax is the whole of the supported language, in the words an error message uses. It is a constant so the refusal and the documentation cannot drift apart.

Variables

View Source
var (
	// ErrTooLarge reports a document past Limits.MaxBytes.
	ErrTooLarge = errors.New("document exceeds the XML size limit")
	// ErrTooDeep reports nesting past Limits.MaxDepth.
	ErrTooDeep = errors.New("document exceeds the XML nesting limit")
	// ErrTooManyNodes reports an element count past Limits.MaxNodes.
	ErrTooManyNodes = errors.New("document exceeds the XML element limit")
	// ErrDoctype reports a document type declaration. See Decode.
	ErrDoctype = errors.New("document type declarations are not accepted")
	// ErrNotElement reports a document with no root element.
	ErrNotElement = errors.New("document has no root element")
)

Errors a caller can act on. They are sentinels because the script module turns each into its own message and the gateway decides per-error whether to fall back to the raw body.

View Source
var DefaultLimits = Limits{
	MaxBytes: 8 << 20,
	MaxDepth: 200,
	MaxNodes: 200_000,
}

DefaultLimits are the bounds both callers use unless a deployment's own budget is smaller. They are sized to hold the responses the gateway already buffers inline while refusing a document that could only be an attack: a SOAP envelope 200 levels deep or with 200k elements is not a payload anyone meant to send.

View Source
var ErrBadPath = errors.New("unsupported path")

ErrBadPath reports a path outside the supported subset. It is a sentinel because a bad path is a caller mistake to be surfaced, never an empty result: a path language that answers "no matches" to a construct it does not implement teaches the caller that the data is missing.

View Source
var ErrBadTree = errors.New("tree cannot be encoded as XML")

ErrBadTree reports a tree that cannot be written as XML.

Functions

func Encode

func Encode(n *Node, lim Limits) (string, error)

Encode writes a tree as an XML document.

Each element is written with its own text before its children, which is the shape Decode produces for a data document. An element in a namespace carries xmlns only where that namespace differs from the one it inherits, so a SOAP envelope reads the way its sender wrote it rather than repeating the declaration on every node.

The same depth and element limits apply as on the way in: a tree assembled in a script is caller-supplied data like any other, and a list that holds itself would otherwise be an unbounded walk.

Types

type Limits

type Limits struct {
	// MaxBytes caps the document as handed to Decode.
	MaxBytes int
	// MaxDepth caps element nesting. A document nested past it is refused
	// rather than recursed into.
	MaxDepth int
	// MaxNodes caps the element count. It is the bound that stops a wide
	// document, which MaxDepth does not.
	MaxNodes int
}

Limits bound what a decode will accept. Every field is required: a caller that wants the platform's own bounds asks for DefaultLimits rather than leaving a field zero, so "no limit" can never be reached by forgetting one.

type Node

type Node struct {
	// Tag is the element's local name, with any namespace prefix resolved
	// away. Matching on it is what lets a caller write "Envelope/Body"
	// without knowing whether the upstream spells the prefix soap, soapenv
	// or S.
	Tag string
	// NS is the namespace URI the element was in, empty when none.
	NS string
	// Attrs are the element's attributes keyed by local name. An attribute
	// in a namespace keeps only its local name, for the same reason Tag
	// does.
	Attrs map[string]string
	// Text is the element's own character data, concatenated in document
	// order and trimmed of surrounding whitespace.
	Text string
	// Children are the child elements in document order.
	Children []*Node
}

Node is one element of a decoded document.

Text is the element's own character data, concatenated and trimmed; the character data of descendants belongs to those descendants. Children are the child elements in document order, which is what keeps repeated tags and their sequence readable — a map keyed by tag would lose both. The position of text relative to children in mixed content is not recorded, so Encode of a decoded prose document emits the element's text before its children rather than interleaved. Data documents, which is what the platform's callers read, round-trip exactly.

func Decode

func Decode(doc string, lim Limits) (*Node, error)

Decode reads a document into its root element.

A document type declaration is refused outright. Go's decoder resolves no external entity, but an internal DTD can still declare entities that expand into each other, and a caller with a legitimate reason to send a DOCTYPE to this platform has not turned up. Refusing it is one rule with no parser state behind it, which is the kind of rule that stays correct.

Decoding is strict: an unknown entity, a mismatched end tag or malformed markup is an error rather than a best guess, so a caller never acts on a tree the document did not describe.

func Find

func Find(n *Node, path string) (*Node, error)

Find returns the first node the path matches, or nil when nothing matches. A path outside the supported subset is an error, never a nil result: the two answers mean different things and a caller has to be able to tell them apart.

func FindAll

func FindAll(n *Node, path string) ([]*Node, error)

FindAll returns every node the path matches.

type Path

type Path struct {
	// contains filtered or unexported fields
}

Path is a compiled path. Compiling is separated from matching so a caller that evaluates the same path against many documents pays for the parse once, and so a refusal happens where the path was written.

func Compile

func Compile(path string) (Path, error)

Compile parses a path, refusing anything outside the subset.

A path is relative to the node it is evaluated against, so it does not begin with a single "/": a leading "//" is the descendant step and is accepted, but a lone leading slash would have to mean the document root, which the caller did not pass in.

func (Path) Select

func (p Path) Select(n *Node) []*Node

Select evaluates a compiled path against a node.

Each step is applied to every node the previous step produced, and a node reached by more than one of them is returned once, in the order it was first reached. That can only happen when a descendant step runs from nested context nodes; for the child axis the result is document order.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL