xdmbuild

package
v1.4.0 Latest Latest
Warning

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

Go to latest
Published: Sep 26, 2026 License: MIT Imports: 5 Imported by: 0

Documentation

Overview

Package xdmbuild constructs XDM sequences and trees from the results of a sequence constructor.

XSLT and XQuery build result trees the same way. The rules in XSLT 3.0 §5.7.1 and XQuery 3.1 §3.9.1.3 are, for the part that matters here, the same text: arrays are flattened, a run of adjacent atomic values becomes one text node with a single space between each, adjacent text nodes are merged with no separator, zero-length text nodes are dropped, a document node is replaced by its children, and an attribute or namespace node may not follow a node that is neither.

What differs between the two languages is small and enumerable: five error codes, one genuine difference in behaviour, and the namespace and type policies a copy is made under. Those arrive through a Policy rather than being written into the builder, so this package names neither language.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CountSubtree added in v1.3.0

func CountSubtree(n *xdm.Node) int

detach returns a node safe to re-parent: n itself when it is freshly constructed, a deep copy when it belongs to a tree already. CountSubtree returns the number of nodes DeepCopy would make for n, counting the attributes and namespaces that travel with each element.

A copy is charged for what it builds, not for one node: xsl:copy-of of a large element inside a loop reaches the same runaway as a nested for-each, and charging it a single node would leave that route open.

It is exported because a host may copy a node BEFORE handing it to the builder -- xsl:copy-of does, so that it can rebase and strip namespaces on its own copy -- and such a host has to charge what it is about to allocate itself. Keeping the counting rule here is what stops the two charges disagreeing about what a subtree costs.

func DeepCopy

func DeepCopy(n *xdm.Node) *xdm.Node

DeepCopy clones a subtree, detached from its original parent.

The type annotation travels with the copy. validation="preserve" is defined as keeping the types the source carried, and dropping them here left a preserved copy untyped, so "$v instance of element(e, xs:anyURI)" answered false for a node that had just been copied from a validated document. Stripping is done by the validation spec, which is the thing that knows whether the instruction asked for it.

func Rebase

func Rebase(n *xdm.Node, parentBase string)

Rebase recomputes the base URIs of a subtree that has just been re-parented.

A copied element keeps whatever xml:base attribute it carried, and XSLT 2.0 section 11.9 makes the base URI of the copy a function of that attribute and of the *new* parent, not of the document it came from. So a source element written xml:base="/xml/" under a document based at http://a.example/ becomes based at http://b.example/xml/ once copied under a parent based at http://b.example/main/ — carrying the resolved http://a.example/xml/ across unchanged is the one answer that is wrong in every case.

An element with no xml:base of its own simply inherits, which is the same rule with an empty reference.

func RebaseDetached

func RebaseDetached(n *xdm.Node, instrBase string)

RebaseDetached recomputes the base URI of a copy that has no parent.

Section 11.9: "the base URI of a node is copied, except in the case of an element node having an xml:base attribute, in which case the base URI of the new node is taken as the value of the xml:base attribute, resolved if it is relative against the base URI of the xsl:copy/xsl:copy-of instruction". So an element without its own xml:base keeps the source's base URI unchanged — which is why this cannot just call Rebase, whose empty-reference case makes the node inherit from its new parent.

func ResolveAgainst

func ResolveAgainst(base, ref string) string

ResolveAgainst resolves a possibly-relative reference against a base URI, returning the reference unchanged when the base is unusable.

Types

type Builder

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

Builder accumulates the result of a sequence constructor.

XSLT output is a sequence of nodes and atomic values, not a string: an attribute added after an element has children is an error, adjacent text must be merged, and the result may be a temporary tree that later instructions navigate. Building a string directly would make all of that impossible.

func New

func New(p Policy) *Builder

New returns a builder that reports faults through p.

p must not be nil: every fault the builder detects is named by the caller's language, and there is no default to fall back on.

func (*Builder) AddAttribute

func (b *Builder) AddAttribute(name xdm.QName, value string) error

AddAttribute attaches an attribute to the element under construction.

func (*Builder) AddAttributeTyped

func (b *Builder) AddAttributeTyped(name xdm.QName, value string,
	typeAnnotation string) error

AddAttributeTyped adds an attribute carrying a type annotation.

The annotation has to travel with the attribute rather than be applied to a throwaway node: xsl:attribute assesses the value it is about to write, and a pattern such as schema-attribute(A) matches only a node that was actually validated against the declaration, so an attribute that lost its annotation on the way into the element could never match however it was named.

It is a convenience wrapper over AddAttributeWithTyping for the callers that genuinely hold nothing but a name -- a DTD attribute type, a hand-built node, a test. A NAME IS NOT THE WHOLE OF AN ANNOTATION: the other seven PSVI properties are left unset here, so atomisation of the resulting attribute falls back to the process-global derivation registries, which are keyed by QName alone and answer for whichever schema loaded last. A caller that knows what the name means -- above all a validator, which does -- must use AddAttributeWithTyping and say so, or a union-typed attribute atomises to xs:untypedAtomic and a list type erases to another schema's primitive.

func (*Builder) AddAttributeWithTyping added in v1.3.0

func (b *Builder) AddAttributeWithTyping(name xdm.QName, value string,
	typing xdm.Typing) error

AddAttributeWithTyping adds an attribute carrying every PSVI property its assessment concluded, rather than only the name of its type.

This is the typed entry point finding 24 asks for. The properties are RECORDED as given and nothing is derived from the annotation name, which is the whole point: deriving means asking derivedPrimitives, unionMembers and listItems, and those are process-global, keyed by QName alone, and hold whatever schema registered the name most recently. A caller holding a resolved xdm.Typing -- xsl:attribute after assessing its value, a copy of an already-assessed attribute -- has the right answer in hand and must not have it replaced by a possibly different schema's. An attribute built through this method for a type NO schema ever registered still atomises, casts and compares correctly, because the node carries its own meaning.

Typing's zero value is the unassessed node, so AddAttributeWithTyping with an empty Typing is exactly AddAttribute.

func (*Builder) AddNamespace

func (b *Builder) AddNamespace(prefix, uri string) error

AddNamespace attaches a namespace node to the element under construction.

It is shared by xsl:namespace and by xsl:copy-of of a namespace node: both add a binding to the element being built, and a namespace node appended as if it were a child would silently vanish from the result.

func (*Builder) AddOwnNameNamespace added in v1.2.1

func (b *Builder) AddOwnNameNamespace(prefix, uri string) error

AddOwnNameNamespace binds the prefix of the element's own name, without recording the binding as a namespace node the result sequence produced.

The distinction is the one the declared field documents. A prefix carried only by the element's NAME is not a namespace node a sequence constructor wrote, so a later binding of that same prefix is not two namespace nodes in conflict: section 11.7, and XQuery §3.9.3.1 with it, resolve the clash by renaming the element's prefix and keeping both URIs reachable.

Routing the own-name binding through AddNamespace recorded it in declared and so turned exactly that case into the error the rename exists to avoid. nscons-011 is the shape: element {QName(two, 'p:e')} { namespace p {one} } must rename the element's prefix and keep p bound to one, and it reported XQDY0102 instead.

func (*Builder) AppendNode

func (b *Builder) AppendNode(n *xdm.Node)

AppendNode adds a node to the current output position.

A node that already belongs to a tree is copied first. AppendChild rewrites the node's Parent and tree pointers and Finalize renumbers its document order, so adopting a source node in place *mutates the source document* — evaluating an unused variable containing xsl:sequence was enough to reorder the input, and two goroutines transforming a shared parsed tree raced on it. xsl:copy-of already copied; xsl:sequence and xsl:perform-sort did not, and the guard belongs here where every caller is covered.

func (*Builder) AppendOpaque

func (b *Builder) AppendOpaque(it xdm.Item) error

AppendOpaque adds an item that is neither a node nor an atomic value — a map, an array, or a function item.

Such an item is a legal member of a sequence but has no representation inside element content, so it is accepted at the top level and refused under an open element. Both languages refuse it; they differ only in the code, which is why the fault is reported rather than named here. An ARRAY is the exception, and only under an open element. XTDE0450 is worded against a function item — "It is a dynamic error if the result sequence contains a function item" (XSLT 3.0 §5.7.1) — and an array is not one for this rule: content construction flattens it and contributes its members. The XSLT 3.0 test suite says so in as many words, naming output-0713/0714/0715 "An array is flattened by the XML output method", and arrays-304/305 build element content from xsl:sequence over an array and assert the members' values. Flattening is recursive, because a member may itself be an array, which is exactly what xdm.Flatten does.

At the TOP level the array stays an array: that is what lets an xsl:variable declared as="array(*)" be built by a sequence constructor, and what an xsl:function returning an array depends on.

func (*Builder) AppendText

func (b *Builder) AppendText(s string)

AppendText adds text, merging with a preceding text node so that the XDM invariant of no adjacent text nodes holds in constructed trees too.

func (*Builder) AppendValue

func (b *Builder) AppendValue(a *xdm.Atomic)

AppendValue adds an atomic value to the output sequence.

func (*Builder) EndAtomicRun

func (b *Builder) EndAtomicRun()

EndAtomicRun declares that the values appended so far form a complete run, so that the next atomic value starts a new one and takes no separator.

Both languages separate a run of adjacent atomic values with single spaces, and both scope the run to one sequence — XSLT §5.7.1 to the sequence an instruction returns, XQuery §3.9.1.3 to the value of one enclosed expression. The builder cannot see either boundary, because a caller appends a sequence item by item and nothing in the calls says where one ended and the next began.

XSLT does not call this: it appends the whole of an instruction's sequence before anything else can intervene, so its runs already end where they should. XQuery needs it because "<e>{1}{2}</e>" is two enclosed expressions, each a one-item sequence, and the two items must abut rather than be separated.

func (*Builder) Items

func (b *Builder) Items() xdm.Sequence

Items returns the accumulated items without finishing the builder.

Sequence is the ordinary way to read the result. This exists for the section 8.4.4 algorithm, which has to look at what a constructor produced so far in order to decide whether xsl:on-empty fires, and then carry on appending to the same builder.

func (*Builder) NoteDeclared

func (b *Builder) NoteDeclared(prefix, uri string)

NoteDeclared records a namespace node the element was constructed with, so that a later xsl:namespace binding the same prefix elsewhere is seen as the XTDE0430 conflict it is.

The distinction is which namespaces reach "the result sequence". A prefix carried only by the element's *name* is not a namespace node the sequence constructor produced, and section 11.7 resolves a clash with it by renaming — namespace-alias-1903 writes xsl:element name="ns:e" with xmlns:ns on the instruction, which is not copied to the result, and requires the rename. A literal result element is the other case: 11.7 copies its namespace nodes into the result, so two conflicting bindings for one prefix really are two namespace nodes with the same name and different values. namespace-2618 is the spec's own "Conflicting Namespace Prefixes" example and expects the error.

func (*Builder) Open

func (b *Builder) Open() *xdm.Node

Open returns the element currently being built, or nil at the top level.

A caller needs it for the work that happens after the content is in place but before the element is finished: namespace fixup, schema validation, and stamping a base URI all act on the element itself rather than on what the builder appended to it. It is the live node, not a copy — mutating it is the point.

func (*Builder) Refused added in v1.3.0

func (b *Builder) Refused() error

Refused reports the first budget refusal this construction met, or nil.

A host calls it from the loop that drives the sequence constructor, which is where an error can still be returned; see the refused field.

func (*Builder) Sequence

func (b *Builder) Sequence() xdm.Sequence

Sequence returns the accumulated items.

func (*Builder) SetItemSeparator

func (b *Builder) SetItemSeparator(sep *string)

SetItemSeparator records the item-separator that applies to the tree this builder produces. A nil argument leaves the default 5.7.1 rules in force.

func (*Builder) StartElement

func (b *Builder) StartElement(name xdm.QName) *Builder

StartElement opens a new element, returning a builder scoped to it.

func (*Builder) ToDocument

func (b *Builder) ToDocument() (*xdm.Node, error)

ToTree wraps the accumulated items in a document node, which is what a variable with content produces. ToDocument is ToTree with the check XTDE0420 requires.

"It is a non-recoverable dynamic error if the result sequence used to construct the content of a document node contains a namespace node or attribute node." A document node has no attributes and carries no namespace declarations of its own, so such an item has nowhere to go: appending it silently discarded it, and the stylesheet saw a document that was missing what it had just built.

It is separate from ToTree because not every temporary builder becomes a document. A sequence constructor producing a bare attribute is legitimate — xsl:function as="attribute()" is written that way — and only wrapping the result in a document node makes it wrong.

func (*Builder) ToTree

func (b *Builder) ToTree() *xdm.Node

type Fault

type Fault int

A Fault is a structural fault detected while building content.

The builder reports what went wrong and leaves naming it to the caller, because the two languages that use this give the same fault different codes — and, for FaultDuplicateAttribute, different behaviour. Reporting the condition rather than the code is what lets one builder serve both.

const (
	// FaultAttrAfterChild is an attribute or namespace node added to an
	// element that already has a child which is neither.
	//
	// XSLT: XTDE0410. XQuery: XQTY0024.
	FaultAttrAfterChild Fault = iota

	// FaultAttrOnDocument is an attribute or namespace node appearing as the
	// content of a document node.
	//
	// XSLT: XTDE0420. XQuery: XPTY0004.
	FaultAttrOnDocument

	// FaultConflictingPrefix is one prefix bound to two different URIs on the
	// same element by the sequence being constructed.
	//
	// XSLT: XTDE0430. XQuery: XQDY0102.
	FaultConflictingPrefix

	// FaultDefaultNSOnNoNS is a default namespace declared on an element whose
	// own name is in no namespace.
	//
	// XSLT: XTDE0440. XQuery: XQDY0102.
	FaultDefaultNSOnNoNS

	// FaultFunctionItem is a function item, map or array appearing where the
	// content model admits only nodes and atomic values.
	//
	// XSLT: XTDE0450. XQuery: XQTY0105. In a serialization context both
	// languages raise SENR0001 instead, which is why this is a fault rather
	// than a fixed code.
	FaultFunctionItem

	// FaultDuplicateAttribute is two attributes with the same name on one
	// element.
	//
	// This is the one fault where the languages disagree about more than the
	// code. XSLT 3.0 §5.7.1 discards the earlier attribute silently — "if an
	// attribute A in the sequence has the same name as another attribute B
	// that appears later in the sequence, then attribute A is discarded".
	// XQuery 3.1 §3.9.1.3 raises XQDY0025.
	//
	// A Policy selects XSLT's behaviour by returning nil for this fault: the
	// builder then replaces the earlier attribute and carries on. Returning an
	// error selects XQuery's.
	FaultDuplicateAttribute
)

func (Fault) String

func (f Fault) String() string

String names a fault, for a message that has to describe one.

type Policy

type Policy interface {
	// Err names a structural fault, or returns nil to accept it.
	//
	// Only FaultDuplicateAttribute is meaningfully acceptable: returning nil
	// there selects XSLT's rule that a later attribute replaces an earlier one
	// of the same name. Returning nil for any other fault lets the builder
	// carry on with content the data model does not admit, so a Policy should
	// name them all.
	//
	// detail describes the particular node or prefix at fault and is meant to
	// be quoted in the message.
	Err(f Fault, detail string) error

	// InheritNamespaces reports whether the namespaces in scope on a
	// constructed element are copied to elements copied beneath it.
	//
	// XSLT: xsl:element/@inherit-namespaces and xsl:copy/@inherit-namespaces.
	// XQuery: the inherit / no-inherit half of copy-namespaces.
	InheritNamespaces() bool

	// PreserveNamespaces reports whether a copied element keeps every
	// namespace that was in scope on the original, or only those used in the
	// names of the element and its attributes.
	//
	// XSLT has no way to ask for anything but the first, so an XSLT policy
	// returns true. XQuery: the preserve / no-preserve half of
	// copy-namespaces.
	PreserveNamespaces() bool

	// PreserveTypes reports whether a copied node keeps its type annotation
	// and its is-id, is-idrefs and nilled properties.
	//
	// XSLT: validation="preserve" against any other validation mode.
	// XQuery: construction mode preserve against strip.
	PreserveTypes() bool

	// DropEmptyText reports whether a text node that a document constructor
	// would be left holding is dropped when it is zero length.
	//
	// The two languages part company here, and only over a document node's
	// children -- inside an element both drop zero-length text, which
	// AppendText does for either.
	//
	// XQuery returns true. "document {”, document{”}, ”}" has no children
	// at all: every value contributes a zero-length text node, §3.9.1.3
	// removes each of them, and Constr-docnode-nested-4 counts the result
	// and expects zero.
	//
	// XSLT returns false, because the same construction is not empty there.
	// A run of adjacent atomic values is joined by a single space *before*
	// the removal rule applies, so two xsl:sequence instructions selecting
	// ” leave a text node holding one space -- content, not nothing. The
	// suite is emphatic about it: on-empty-115a builds exactly that document
	// and its xsl:on-empty fallback reads "WRONG! the document node contains
	// a space, it is not empty!!!", and seqtor-039a wants " | | " where
	// dropping the empties would give "||".
	//
	// So this is a real difference in the languages rather than a gap, which
	// is why it is asked rather than decided in the builder.
	DropEmptyText() bool

	// CountNodes charges n newly constructed nodes against whatever budget
	// the host is measuring result-tree size against, returning an error to
	// refuse the construction.
	//
	// It is asked rather than decided here for the same reason the faults
	// are: the builder knows how many nodes it is about to make, and nothing
	// else does, but what a budget IS belongs to the host. This package
	// imports nothing but xdm precisely so that it names neither language,
	// and a counter reached through the Policy keeps it that way.
	//
	// Returning nil always is the unbounded behaviour, which is what a host
	// with no budget -- and every existing test policy -- wants. The charge
	// is made BEFORE the node is allocated, so a refusal costs nothing.
	CountNodes(n int) error
}

A Policy supplies what the builder cannot know by itself: how to name a structural fault, and the namespace and type rules a copy is made under.

The namespace questions are asked as two independent booleans because the specifications ask them that way. XQuery's copy-namespaces has two axes, preserve/no-preserve and inherit/no-inherit, which vary independently; XSLT's xsl:element and xsl:copy carry inherit-namespaces and always preserve.

Jump to

Keyboard shortcuts

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