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 ¶
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 ¶
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.
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.
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.
var ErrBadTree = errors.New("tree cannot be encoded as XML")
ErrBadTree reports a tree that cannot be written as XML.
Functions ¶
func Encode ¶
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 ¶
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.
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 ¶
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 ¶
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.