Documentation
¶
Overview ¶
Package xdm implements the XQuery/XPath Data Model (XDM) that XPath 2.0 and XSLT 2.0 are defined over.
The central difference from XPath 1.0 is that every value is a *sequence* of items, and an item is either a node or a typed atomic value. XPath 1.0 had four types (node-set, string, number, boolean) with implicit coercion everywhere; 2.0 has the full XML Schema datatype hierarchy with explicit promotion rules. Modelling that faithfully here is what lets the rest of the engine avoid the 1.0-style "just call ToString" shortcuts that make 2.0 stylesheets silently produce wrong answers.
Index ¶
- Constants
- func AnnotationLocal(annotation string) string
- func AnnotationName(uri, local string) string
- func CompareDT(a, b *DateTime, implicitTZ int) int
- func DerivedBase(name string) string
- func ErrCast(format string, args ...any) error
- func ErrType(format string, args ...any) error
- func ErrorCode(err error) string
- func Errorf(code, format string, args ...any) error
- func HasSimpleTypeAnnotation(annotation string) bool
- func IsGregorian(t TypeCode) bool
- func IsNCName(s string) bool
- func IsQualifiedAnnotation(annotation string) bool
- func IsXMLWhitespace(s string) bool
- func LexicalGregorian(dt *DateTime, t TypeCode) string
- func ListItemOf(name string) string
- func RegisterDerivedType(name, primitive string)
- func RegisterListType(name, itemType string)
- func RegisterUnionType(name string, members []string)
- func SplitAnnotationName(annotation string) (uri, local string)
- func SplitQName(s string) (prefix, local string)
- func UnionMembersOf(name string) []string
- type Atomic
- func NewAnyURI(s string) *Atomic
- func NewBinary(s string, t TypeCode) *Atomic
- func NewBoolean(v bool) *Atomic
- func NewDateTime(dt *DateTime, t TypeCode) *Atomic
- func NewDecimal(r *big.Rat) *Atomic
- func NewDouble(v float64) *Atomic
- func NewDuration(d *Duration, t TypeCode) *Atomic
- func NewFloat(v float64) *Atomic
- func NewGregorian(dt *DateTime, t TypeCode) *Atomic
- func NewInteger(v int64) *Atomic
- func NewIntegerFromRat(r *big.Rat) *Atomic
- func NewQNameValue(q QName) *Atomic
- func NewString(s string) *Atomic
- func NewUntypedAtomic(s string) *Atomic
- func (a *Atomic) Bool() bool
- func (a *Atomic) DateTimeVal() *DateTime
- func (a *Atomic) Derived() string
- func (a *Atomic) DerivedMember() string
- func (a *Atomic) DurationVal() *Duration
- func (a *Atomic) FitsInt64() bool
- func (a *Atomic) Float64() float64
- func (a *Atomic) Int64() int64
- func (a *Atomic) IsNaN() bool
- func (a *Atomic) QName() *QName
- func (a *Atomic) Rat() *big.Rat
- func (a *Atomic) Str() string
- func (a *Atomic) String() string
- func (a *Atomic) TypeName() string
- func (a *Atomic) WithDerived(name string) *Atomic
- func (a *Atomic) WithDerivedUnion(name, member string) *Atomic
- type DateTime
- type Duration
- type EntityResolver
- type Error
- type Item
- type Node
- func (n *Node) AddAttr(a *Node)
- func (n *Node) AddNamespace(prefix, uri string)
- func (n *Node) AppendChild(c *Node)
- func (n *Node) Atomize() *Atomic
- func (n *Node) AtomizeList() (Sequence, bool)
- func (n *Node) Attr(uri, local string) *Node
- func (n *Node) AttrValue(local string) string
- func (n *Node) ChildElements() []*Node
- func (n *Node) Compare(o *Node) int
- func (n *Node) InScopeNamespaces() map[string]string
- func (n *Node) IsElement(uri, local string) bool
- func (n *Node) LookupPrefix(prefix string) (string, bool)
- func (n *Node) Order() int
- func (n *Node) Position() (line, col int, ok bool)
- func (n *Node) Root() *Node
- func (n *Node) SetSynthesizedOrder(owner *Node, offset int)
- func (n *Node) SetTypeAnnotation(annotation string)
- func (n *Node) StringValue() string
- func (n *Node) Tree() *Tree
- func (n *Node) TypeName() string
- type NodeKind
- type Opaque
- type ParseOptions
- type QName
- type Sequence
- type Tree
- type TypeCode
Constants ¶
const ( // DefaultMaxDepth is the nesting limit. DefaultMaxDepth = 1000 // DefaultMaxBytes is the source-size limit: 64 MB, far above any // schema or stylesheet and above most real instance documents, while // still bounding what a single parse can be asked to read. DefaultMaxBytes int64 = 64 << 20 // DefaultMaxNodes is the node-count limit. At roughly 200 bytes a node // this bounds a tree to about 2 GB, which is the point of it: the // number is chosen to bound *memory*, and it is the limit that actually // binds on the documents designed to be expensive. DefaultMaxNodes = 10_000_000 )
Limits applied when the corresponding ParseOptions field is zero.
const ( NSXSL = "http://www.w3.org/1999/XSL/Transform" NSXML = "http://www.w3.org/XML/1998/namespace" NSXMLNS = "http://www.w3.org/2000/xmlns/" NSXS = "http://www.w3.org/2001/XMLSchema" NSXSI = "http://www.w3.org/2001/XMLSchema-instance" NSFN = "http://www.w3.org/2005/xpath-functions" NSErr = "http://www.w3.org/2005/xqt-errors" NSSVRL = "http://purl.oclc.org/dsdl/svrl" NSSchema = "http://purl.oclc.org/dsdl/schematron" // NSGoxslt is this engine's extension namespace. Extensions live outside // the fn: namespace so that a stylesheet written for another processor // cannot silently pick one up in place of a standard function, and so // that a stylesheet using them is visibly engine-specific. NSGoxslt = "https://github.com/knroy/go-xml" )
Well-known namespace URIs used throughout the engine.
Variables ¶
This section is empty.
Functions ¶
func AnnotationLocal ¶ added in v1.0.0
AnnotationLocal returns just the local part of an annotation key.
func AnnotationName ¶ added in v1.0.0
AnnotationName builds the key a type annotation is recorded and compared under.
The data model keys annotations by a single string, and that string used to be the type's bare local name. That conflated every type sharing a local part across namespaces, and the conflation was not theoretical: the W3C's own schema-for-xslt20.xsd deliberately declares an xsl:QName of its own, as a restriction of xs:Name, and says in its text why ("This schema does not use the built-in type xs:QName... a schema processor would expand unprefixed QNames incorrectly"). Keyed by "QName", loading that schema overwrote the built-in's entry in a package-level, process-global map, so every later schema in the same process saw xs:QName deriving from xs:Name.
Both meanings have to coexist rather than one displacing the other: import-schema-029 asserts that the SHADOWING xsl:QName does erase to a string, while type-functions-0501 asserts that the built-in xs:QName still atomises to a QName value. A registration that refuses to shadow breaks the first; a global "something shadowed a built-in" flag breaks the second, because one flag cannot hold two answers at once. Only qualifying the key separates them.
The encoding is Clark notation, {uri}local, which the codebase already spells through QName.Clark, with one deliberate exception: a type in the XML Schema namespace keys under its BARE local name. That exception is what keeps the change tractable. Built-in annotations are compared against bare literals — "QName", "NOTATION", "ID", "string" — at roughly a hundred sites across four packages, in switch statements, map lookups and equality tests. Qualifying them would have required rewriting every one of those, whereas leaving them bare means only names that were previously AMBIGUOUS change spelling, and a built-in's key is the same string it has always been.
The empty URI also keys bare, which is the no-namespace case and is already unambiguous.
func DerivedBase ¶ added in v1.0.0
DerivedBase returns the type a schema type derives from, or "" if the name is not a registered schema type. The name is an annotation name, and so is the result, so a chain can be walked by feeding one back in.
It is what makes the subtype relation work for schema types: a value annotated as a restriction of xs:NOTATION is an instance of xs:NOTATION as well as of its own type, and answering that means walking the chain the schema recorded.
func ErrType ¶
ErrType is the XPath type error, XPTY0004. It is returned rather than panicked so that a stylesheet error degrades one transform.
func ErrorCode ¶
ErrorCode returns the spec error code carried by err, or "" if it has none.
It unwraps, so a code survives being wrapped with fmt.Errorf("%w"). Errors produced before this type existed still carry their code as a message prefix, so those are recognised too rather than silently reporting "".
func HasSimpleTypeAnnotation ¶ added in v1.0.0
HasSimpleTypeAnnotation reports whether an annotation names a simple type, or a complex type with simple content.
XSLT 2.0 section 4.4 preserves whitespace-only text in such an element *regardless* of xsl:strip-space: that text is the element's entire typed value, which the schema validated, and stripping it would leave a node whose annotation describes a value it no longer holds. An element with element-only or mixed content has no such value and is stripped normally.
The registration table is the oracle rather than a list of names, because the annotation on such an element is the built-in its content type erases to — "string" for both an element of type xs:string and one whose anonymous complex type extends xs:string, which is exactly the pair section 4.4 groups together. A complex type with element-only content registers no derivation to a built-in, so it answers false, which is the distinction being drawn.
func IsGregorian ¶
IsGregorian reports whether t is one of the five Gregorian types.
func IsNCName ¶
IsNCName reports whether s is an XML non-colonised name: a name with no prefix, which is what an element, attribute or processing-instruction name must be once the prefix has been split off.
It lives here rather than in a consumer because more than one caller needs it, and because the cost of *not* checking is that a computed name reaches the serialiser unvalidated. A name is written to output as-is, so a name holding "><script>" produces markup rather than a name — output that is either malformed or, in HTML, an injected element.
func IsQualifiedAnnotation ¶ added in v1.0.0
IsQualifiedAnnotation reports whether an annotation key carries a namespace, which is to say it names a type that is neither a built-in nor in no namespace.
func IsXMLWhitespace ¶
IsXMLWhitespace reports whether s consists entirely of XML whitespace.
XML defines whitespace as exactly four characters: space, tab, carriage return and line feed. Go's strings.TrimSpace uses unicode.IsSpace, which additionally matches U+00A0 (no-break space) and other Unicode separators — so using it to decide whether a text node is "just whitespace" silently deletes a that the author put there deliberately.
func LexicalGregorian ¶
LexicalGregorian returns the canonical lexical form.
func ListItemOf ¶ added in v1.0.0
ListItemOf returns the item type registered for a list type, or "" when the name is not a registered list.
func RegisterDerivedType ¶ added in v1.0.0
func RegisterDerivedType(name, primitive string)
RegisterDerivedType records that a schema type erases to a built-in one.
Both arguments are annotation names, which AnnotationName builds; passing a bare local name for a type that has a namespace re-creates the conflation this keying exists to prevent.
The xsd package calls this as it loads a schema, so that a node annotated with a user-defined type still atomises to a typed value rather than to untypedAtomic. Without it, "instance of my:partNumberType" could never be true for a value read out of a validated document, because the value would have discarded the annotation on the way out of the tree.
func RegisterListType ¶ added in v1.0.0
func RegisterListType(name, itemType string)
RegisterListType records that a schema type is a list, and what its items are.
The xsd package calls this as it loads a schema, for the same reason it calls RegisterDerivedType: the typed value of a list-typed node is a SEQUENCE of one atomic per token, and nothing in the data model can work out from a bare type name that "numbers" is a list of xs:decimal. Without it a list-typed node atomises to one untypedAtomic holding the whole literal, so count(data(@list)) answers 1 and "data(@list) instance of xs:untypedAtomic" answers true for a node the schema plainly gave a typed value.
Both arguments are annotation names, as RegisterDerivedType's are.
itemType is the item type's own name, which may itself be a registered schema type; atomicForAnnotation and the derivation walk resolve it.
func RegisterUnionType ¶ added in v1.0.0
RegisterUnionType records that a schema type is a union, and what its member types are.
The xsd package calls this as it loads a schema, for the reason it calls RegisterListType: a union's base is always xs:anySimpleType, so the derivation chain RegisterDerivedType records dead-ends immediately and carries no information about what the value actually is. Without the member list a union-typed node atomises to xs:untypedAtomic — the walk finds anySimpleType, cannot build a value for it, and gives up — which makes "data(u) instance of xs:untypedAtomic" true for a node the schema plainly gave a typed value, and makes every question about the member it validated as answer false.
The name and every member are annotation names, as RegisterDerivedType's arguments are. This registry is keyed by the same strings as derivedPrimitives and listItems, so qualifying one of the three and not the others would leave unions silently unresolvable.
The members are the *declared* members, in declaration order; which of them a given value belongs to is a per-value fact recorded on the node, because XSD 1.0 §3.14.4 chooses the member by trying each one's lexical space against the value in turn.
func SplitAnnotationName ¶ added in v1.0.0
SplitAnnotationName is the inverse of AnnotationName: it returns the namespace URI and local part of an annotation key.
A bare key is a built-in or a no-namespace type, and the two are told apart by nothing here — the URI comes back empty for both, because the callers that care (the built-in switches) match on the local part they already expect. What this function exists for is the comparison path, which needs the local part of a qualified key without mistaking "{uri}local" for a prefixed lexical QName.
It must be used in place of SplitQName wherever the input is an annotation. SplitQName cuts at the first colon, so handed "{http://x}foo" it returns the prefix "{http" and the local part "//x}foo" — nonsense rather than an error, and silently wrong.
func SplitQName ¶
SplitQName splits a lexical QName into prefix and local part. It does not resolve the prefix; resolution needs a namespace context and is done by the caller that has one.
func UnionMembersOf ¶ added in v1.0.0
UnionMembersOf returns the member types of a registered union type, or nil when the name does not denote one.
The result must not be modified: it is the stored slice, shared with every other caller.
Types ¶
type Atomic ¶
type Atomic struct {
Type TypeCode
// contains filtered or unexported fields
}
Atomic is a typed atomic value.
The representation is a tagged union rather than an interface per type. The evaluator switches on Type constantly — every arithmetic op, comparison and function call — and a type switch across seventeen concrete types in those hot paths costs more than a single integer compare. It also keeps the numeric tower in one place, where the promotion rules are easy to audit.
func NewBinary ¶
NewBinary returns an xs:hexBinary or xs:base64Binary holding the given lexical form.
The value keeps its own type rather than collapsing to xs:string, because the two binary types are inter-convertible: casting hexBinary to base64Binary has to re-encode the underlying octets, and a value that has forgotten which encoding its lexical form uses cannot be decoded.
func NewDateTime ¶
NewDateTime returns a date, time or dateTime atomic value.
func NewDecimal ¶
NewDecimal returns an xs:decimal holding an exact value.
func NewDuration ¶
NewDuration returns a duration atomic value of the given duration type.
func NewFloat ¶
NewFloat returns an xs:float. The value is rounded to float32 precision on construction, because xs:float operations must produce float32 results.
func NewGregorian ¶
NewGregorian returns one of the five Gregorian atomic values.
func NewInteger ¶
NewInteger returns an xs:integer. Integers are held as exact rationals so that they participate in decimal arithmetic without precision loss.
func NewIntegerFromRat ¶
NewIntegerFromRat returns an xs:integer from an exact rational, which must have denominator 1. Used by arithmetic that has already established integrality (idiv, string-length, count).
func NewUntypedAtomic ¶
NewUntypedAtomic returns an xs:untypedAtomic, the type produced by atomising a node in a document that has not been schema-validated.
func (*Atomic) DateTimeVal ¶
DateTimeVal returns the date/time value, or nil.
func (*Atomic) Derived ¶
Derived returns the narrower XML Schema type this value was constructed as, or "" if it was not built by a derived-type constructor.
func (*Atomic) DerivedMember ¶ added in v1.0.0
DerivedMember returns the union member type this value was validated as, or "" when the value's type is not a union.
It is a second answer alongside Derived, not a replacement for it: a value of a union type is an instance of both the union and the selected member.
func (*Atomic) DurationVal ¶
Duration returns the duration value, or nil.
func (*Atomic) FitsInt64 ¶
FitsInt64 reports whether the value can be represented as an int64 without wrapping.
xs:integer is arbitrary-precision, so this is a real question: Int64() truncates the big.Int and silently returns a different number, which is worse than refusing.
func (*Atomic) Float64 ¶
Float64 returns the value as a float64 for any numeric type. Decimal and integer values are converted, which may lose precision; callers doing exact arithmetic must use Rat instead.
func (*Atomic) IsNaN ¶
IsNaN reports whether a is a double or float NaN. NaN needs its own check throughout comparison, because it is the one value where the general "compare and negate" shortcut produces wrong answers.
func (*Atomic) String ¶
String returns the XPath 2.0 canonical lexical representation, which is what fn:string and every implicit string conversion must produce. It is not a debug format: the exact spelling of doubles and decimals here is observable in stylesheet output.
func (*Atomic) WithDerived ¶
WithDerived returns a copy of a annotated as the named derived type.
The union member is cleared: re-annotating the value as a different type makes any member recorded for the previous one meaningless, and carrying it forward would let a value claim membership in a union it no longer has.
func (*Atomic) WithDerivedUnion ¶ added in v1.0.0
WithDerivedUnion returns a copy of a annotated as the named union type with the named member recorded as the one that accepted it.
type DateTime ¶
type DateTime struct {
Year int // proleptic Gregorian; negative for BCE. No year zero.
Month int // 1-12
Day int // 1-31
Hour int // 0-24 (24 only as the lexical form 24:00:00)
Minute int // 0-59
Second *big.Rat // seconds including fraction, [0,60)
// TZOffset is the timezone offset in minutes east of UTC.
// HasTZ distinguishes "no timezone" from "+00:00", which are different
// values under XML Schema equality.
TZOffset int
HasTZ bool
}
DateTime represents xs:date, xs:time and xs:dateTime.
It is not time.Time. XML Schema dates carry three properties that time.Time cannot express: an optional timezone (distinct from UTC — an unzoned date is a different value from a UTC one), a year range that exceeds int64 nanoseconds, and second values with arbitrary fractional precision. Comparison of unzoned values against zoned ones is defined against an implicit timezone supplied by the dynamic context, which only works if "absent" is representable.
func ParseDateTime ¶
ParseDateTime parses the lexical form of xs:date, xs:time or xs:dateTime according to the requested type.
func ParseGregorian ¶
ParseGregorian parses the lexical form of one of the five Gregorian types.
Each has its own leading-hyphen convention — "--01" is a month, "---15" a day — which exists so that the forms cannot be confused with a truncated date. Getting the hyphen count wrong silently reinterprets the value, so each form is matched exactly rather than by a permissive scan.
func (*DateTime) ToSeconds ¶
ToSeconds returns the value as seconds since 1972-12-31T00:00:00Z, adjusted to UTC using implicitTZ (in minutes) when the value carries no timezone.
Comparison and subtraction are defined on this normalised timeline, so having one conversion point means the timezone rules are applied uniformly rather than re-derived at each comparison site.
type Duration ¶
type Duration struct {
Negative bool
Months int // years*12 + months
Seconds *big.Rat // days*86400 + hours*3600 + minutes*60 + seconds
}
Duration represents xs:duration and its two subtypes.
XML Schema durations have two independent components — months and seconds — that cannot be converted into one another, because the number of days in a month is not fixed. That is why xs:duration is only partially ordered and why the two totally-ordered subtypes (xs:yearMonthDuration and xs:dayTimeDuration) exist. Keeping the components separate rather than normalising to a single scalar is what makes the ordering rules implementable at all.
func ParseDuration ¶
ParseDuration parses the lexical form of xs:duration, xs:yearMonthDuration or xs:dayTimeDuration, rejecting components the requested subtype does not permit.
func (*Duration) SignedMonths ¶
SignedMonths returns the month component with the sign applied.
func (*Duration) SignedSeconds ¶
SignedSeconds returns the second component with the sign applied.
type EntityResolver ¶ added in v1.0.0
type EntityResolver interface {
ResolveEntity(systemID, publicID, base string) (io.ReadCloser, string, error)
}
EntityResolver fetches the resource an external entity or an external DTD subset names.
It is the caller's, deliberately: xdm has no filesystem and no network, so every decision about what may be read — which schemes, which directories, how symlinks resolve — is made in code the caller owns and can audit. A resolver MUST refuse anything it is not certain of; returning an error makes the reference fail, which is the safe outcome.
systemID is the system identifier exactly as the document wrote it, which is usually relative. base is the absolute URI of the entity that contains the reference, against which systemID is to be resolved — note that for an entity declared inside an external DTD subset this is the SUBSET's URI, not the document's, as XML requires.
It returns the resource's content and the absolute URI it resolved to. That URI becomes the base for anything the fetched text itself references, so a resolver must return the URI it actually read, not the one it was asked for.
type Error ¶
type Error struct {
// Code is the spec error code, such as "XPTY0004". Codes live in the
// http://www.w3.org/2005/xqt-errors namespace; the local name alone is
// carried here because it is unique across the specs and is how the
// documents themselves refer to them.
Code string
// Message is the human-readable detail, without the code prefix.
Message string
// Err is an underlying cause, if any.
Err error
}
Error is an XPath, XQuery or XSLT error carrying its specification error code.
The specs define a code for every error condition — XPTY0004 for a type error, FORG0001 for a failed cast, FODC0002 for an unretrievable document — and those codes are the stable, translatable part of an error. A message is prose that may be reworded; a code is what a caller can branch on and what a conformance suite compares.
The codes were already present as string prefixes on every error this engine produces, which reads correctly but cannot be inspected: a caller wanting to distinguish "the document was malformed" from "the stylesheet is wrong" had to match on substrings. This type makes the code a field while keeping the rendered message byte-identical, so nothing that reads error text changes.
type Item ¶
type Item interface {
// TypeName returns the QName of the item's type, for error messages and
// instance-of tests.
TypeName() string
// contains filtered or unexported methods
}
Item is a single member of a sequence: either a Node or an atomic value.
The interface is closed to outside implementations (unexported marker method). XDM defines exactly these two kinds of item in XPath 2.0; function items arrive in 3.0 and would be added here.
type Node ¶
type Node struct {
Kind NodeKind
Name QName
// Value is the text content for text, comment, PI and attribute nodes,
// and the namespace URI for namespace nodes. Element and document nodes
// derive their string value from descendants; see StringValue.
Value string
Parent *Node
Children []*Node
// Attrs and Namespaces hold attribute and namespace nodes for elements.
// They are kept out of Children because the child axis must not return
// them — a fact that a single mixed slice makes easy to get wrong.
Attrs []*Node
Namespaces []*Node
// BaseURI is the resolved base URI, used by fn:document and fn:doc.
BaseURI string
// DocumentURI is the data model's dm:document-uri property, which
// fn:document-uri returns. It is meaningful only on a document node.
//
// It is deliberately NOT the same field as BaseURI, and not derived from
// it. dm:base-uri and dm:document-uri are separate accessors in the XDM,
// and the difference is observable: dm:document-uri is the absolute URI a
// document was RETRIEVED BY, so it is empty for any document that was not
// retrieved by URI at all — a temporary tree built by xsl:variable, a
// document node constructed by xsl:document, a tree parsed from a string.
// Those trees still need a base URI, for fn:base-uri and for resolving a
// relative reference written inside them, so BaseURI on a temporary tree
// is set on purpose (xslt/runtime.go does this) and cannot double as the
// document URI. XPath F&O fn:document-uri: "returns the empty sequence if
// $arg is not a document node, or if the document node was not retrieved
// via a URI".
//
// The invariant a caller must maintain: set this ONLY when the document
// was fetched by that URI and registered in the document pool, so that
// fn:doc of this value returns this same node. Setting it on a tree that
// fn:doc cannot retrieve would make "doc(document-uri($d)) is $d" false
// while claiming it should be true. Parse sets it from
// ParseOptions.DocumentURI, which defaults to empty.
DocumentURI string
// TypeAnnotation records a schema type when the document has been
// validated. Untyped documents leave this empty, and atomisation then
// yields xs:untypedAtomic, which is the schemaless default.
//
// It holds an ANNOTATION NAME, which AnnotationName builds and
// SplitAnnotationName takes apart: a type in the XML Schema namespace
// keys under its bare local name ("string", "QName"), and any other type
// under Clark notation, {uri}local. Producers must go through
// AnnotationName; consumers comparing against a qualified name must use
// SplitAnnotationName rather than SplitQName, which would cut a Clark key
// at the colon inside its URI and yield nonsense without an error.
//
// The namespace is load-bearing rather than decorative. This string is
// the key into a process-global derivation table, so a bare local name
// let one schema's type displace a built-in of the same name for every
// later schema in the process — see the commentary on derivedPrimitives.
TypeAnnotation string
// UnionMember records which member type of a union simple type actually
// accepted this node's value, when TypeAnnotation names (or has simple
// content of) a union.
//
// It is separate state from TypeAnnotation because the two facts are
// different and both are needed. XSD 1.0 §3.14.4 makes member selection a
// property of the *value*, not of the type: "100" validated against
// union(my:partNumberType, xs:integer) is an xs:integer while "123-AB" is
// a my:partNumberType, and the same annotation covers both. Folding the
// winner into TypeAnnotation would answer "instance of xs:integer" at the
// cost of "instance of my:partIntegerUnion", which the union's own
// identity requires; keeping only the union answers the second and loses
// the first. A node must answer both, so both are recorded.
//
// It is an annotation name, like TypeAnnotation, and for the same reason:
// it is compared against, and walked through, the same registries.
//
// Empty for every node whose type is not a union, which is almost all of
// them, so the common path pays only the field.
UnionMember string
// IsID and IsIDREFS are the data model's is-id and is-idrefs properties
// (XDM §5.2, §6.2). They are deliberately *separate* state from
// TypeAnnotation rather than being derived from it, because XSLT 2.0
// §3.5 requires them to survive input-type-annotations="strip": that
// setting turns every annotation into xs:untyped/xs:untypedAtomic while
// leaving is-id and is-idrefs exactly as they were. Deriving them from
// the annotation would lose them at precisely the point the
// specification says they must be kept, and fn:id/fn:idref — which are
// defined over these properties, not over the annotation — would then
// find nothing in a stripped document whose ID attributes happen not to
// be spelled "id".
//
// Two bools rather than one enum: an attribute of a union type can in
// principle be neither, and nothing in the model makes them exclusive.
// They are set wherever an annotation is assigned (schema assessment,
// DTD attribute types) by whoever knows the declared type; a node whose
// type was never determined leaves both false, which is the correct
// answer for an unvalidated document.
IsID bool
IsIDREFS bool
// contains filtered or unexported fields
}
Node is a node in an XDM tree.
This is a concrete struct rather than an interface. Every node kind shares most of its fields, the evaluator switches on Kind rather than dispatching, and the axes need to walk parent/sibling links tens of thousands of times per document — an interface would add a pointer chase and an indirect call to each step for no expressiveness gained.
Trees are built by the parser in this package and are immutable afterwards. That immutability is what makes it safe to share one compiled stylesheet tree across concurrent transforms.
func (*Node) AddNamespace ¶
AddNamespace links a namespace node to n.
func (*Node) AppendChild ¶
AppendChild links c as the last child of n, setting the parent link. It does not assign document order; call Finalize once the tree is complete.
func (*Node) Atomize ¶
Atomize returns the typed value of a node. Without schema validation every node atomises to xs:untypedAtomic, which is what makes untyped comparison rules apply throughout a schemaless transform.
func (*Node) AtomizeList ¶ added in v1.0.0
AtomizeList returns the typed value of a node whose annotation is a list type, as one atomic value per whitespace-separated token.
The second result reports whether the annotation is in fact a list type; a caller that gets false must fall back to Atomize, which yields the single value that every non-list node has.
Only the three built-in list types are recognised here. A user-defined list type is registered by the schema layer with its item type, and that derivation chain is what DerivedBase walks; a list type derived by restriction from one of these three therefore resolves to it and is expanded with its item type.
The empty string atomizes to the empty sequence rather than to one zero-length token, which is what "a list of no items" means and what strings.Fields already produces.
func (*Node) AttrValue ¶
AttrValue returns the value of a no-namespace attribute, or "". Most attributes the stylesheet compiler reads (match, select, name, test) are unprefixed, so this is the common case worth a helper.
func (*Node) ChildElements ¶
ChildElements returns the element children, which is what almost every stylesheet-compilation walk wants.
func (*Node) Compare ¶
Compare orders two nodes in document order, returning -1, 0 or 1. Nodes in different trees are ordered by tree id, which is stable within a transform.
func (*Node) InScopeNamespaces ¶
InScopeNamespaces returns every prefix-to-URI binding visible at n, with inner declarations shadowing outer ones. Used when copying elements and when resolving QNames in stylesheet attribute values.
func (*Node) LookupPrefix ¶
LookupPrefix resolves a namespace prefix against the in-scope namespaces of n, walking up the tree. Returns the URI and whether the prefix was bound.
func (*Node) Order ¶
Order returns a number that identifies the node uniquely within the process.
It is the document-order index within the node's own tree, combined with the tree's identity so that nodes from two documents cannot collide. Callers wanting relative position must use Compare: this value orders nodes within one tree but says nothing across trees.
The combination is what fn:generate-id() needs. Returning the bare per-tree index gave the same answer to the first node of every document, so a stylesheet comparing generated identities across documents — the case key-042 in the XSLT suite exists to check — saw distinct nodes as identical.
A tree built by a sequence constructor is never finalized and has no tree of its own; those nodes take the identity assigned on demand to their root, the same one cross-tree comparison uses, so two parentless elements are also distinguished.
func (*Node) Position ¶
Position returns the 1-based line and column where the node starts, and false if the position is unknown — the node was built by a transform rather than parsed, or the source text was not retained.
func (*Node) Root ¶
Root returns the root of the containing tree, walking parent links. For a well-formed parsed document this is the document node.
func (*Node) SetSynthesizedOrder ¶ added in v1.0.0
SetSynthesizedOrder places a node the parser did not build into the document order of an existing tree, immediately after owner.
The namespace axis is the case this exists for: its nodes are synthesized on demand from the in-scope bindings, so they have no order of their own. Left at zero they sort before every real node, and — because generate-id() is derived from the order — every one of them answers "N0", colliding with each other and with the document node.
The offset separates the bindings of one element from each other while keeping them all adjacent to their owner. It is deliberately not an attempt at a spec-defined position: XPath leaves the relative order of namespace nodes implementation-dependent, and what a caller needs is that the order is stable and the identities distinct.
func (*Node) SetTypeAnnotation ¶ added in v1.0.0
SetTypeAnnotation records a type annotation and the is-id / is-idrefs properties that go with it.
It exists so that every producer of annotations — schema assessment, DTD attribute types, the XSLT validation instructions — sets the two properties the same way. Assigning TypeAnnotation directly is still legal but leaves is-id and is-idrefs at whatever they were, which is what a caller deliberately preserving them across a strip wants and what a caller annotating a fresh node does not.
The properties are only ever turned *on* here. A node that was already marked keeps its marking when re-annotated with a non-ID type, because the data model's properties describe how the node was validated originally and XSLT's stripping rules are the only thing entitled to change them — and those rules say the properties do not change at all.
func (*Node) StringValue ¶
StringValue returns the node's string value per XDM: the concatenation of all descendant text for document and element nodes, and the value itself for the leaf kinds.
type Opaque ¶
type Opaque struct {
// Label names the kind of value, for error messages.
Label string
// Value is the wrapped payload.
Value any
}
Opaque wraps an arbitrary Go value as an Item.
It exists so that layers above this package can thread their own state through an evaluation context, which binds sequences rather than typed fields. The XSLT engine uses it for the transform runtime and grouping state, which the xpath package cannot name without an import cycle.
An Opaque is not a legal XDM value: it has no string value, does not atomise, and must never reach a stylesheet. Every producer binds it under a reserved namespace that no stylesheet can spell.
type ParseOptions ¶
type ParseOptions struct {
// BaseURI is recorded on the document node and used to resolve relative
// references in fn:document and xsl:include.
BaseURI string
// DocumentURI is recorded on the document node as its dm:document-uri
// property, which is what fn:document-uri returns. It is separate from
// BaseURI because the two accessors are separate in the data model: see
// Node.DocumentURI for why they cannot be the same field.
//
// It defaults to empty, which is the right answer for every caller that
// is parsing something it did not retrieve by URI — a stylesheet string,
// a re-parsed entity expansion, a test fixture. A caller that DID fetch
// the document from a URI, and that registers it in a document pool so
// that fn:doc of the same URI returns this same tree, sets it to that URI.
DocumentURI string
// StripSpace removes whitespace-only text nodes. XSLT applies this per
// element name via xsl:strip-space, so the transform layer passes a
// predicate; a plain bool here would not express "strip in these elements
// only".
StripSpace func(elem QName) bool
// AllowDOCTYPE permits a DOCTYPE declaration. It defaults to false: a
// DOCTYPE is the entry point for both XXE (parser-executed file:// and
// http:// reads) and entity-expansion blowup, and a validator that
// happily expands entities from untrusted input is a liability. Callers
// that genuinely need DTD-declared entities opt in explicitly.
AllowDOCTYPE bool
// ExternalEntities permits external entities — those declared SYSTEM or
// PUBLIC, and an external DTD subset — to be read, by supplying the
// resolver that reads them.
//
// It is nil by default, and nil means every external entity is refused
// exactly as before. It is deliberately SEPARATE from AllowDOCTYPE and
// is not implied by it: AllowDOCTYPE admits a DOCTYPE and its internal
// declarations, which cost nothing outside the document, while this
// admits reads of other resources — the XXE surface proper. A caller
// that wants entity declarations does not thereby want file reads.
//
// xdm has no filesystem and no network, so it can only read what a
// resolver hands it. Confinement — permitted schemes, permitted
// directories, symlink resolution — is entirely the resolver's, and
// xslt.FileResolver implements it. Expansion remains bounded by this
// package: fetched bytes are charged to the document's shared budget
// before they are expanded, and the number and nesting of fetches are
// capped. See xdm/dtd_external.go.
ExternalEntities EntityResolver
// TrackPositions records where each element starts, so that a validator
// can report the line a failure occurred on. It retains the source text
// for the life of the tree, which measures at about 10% more memory on a
// typical invoice and no extra parse time. It is opt-in because that cost
// buys nothing for a caller that never asks for a position.
TrackPositions bool
// MaxDepth bounds nesting. Deeply nested input is the cheapest way to
// drive a recursive descent into stack exhaustion, so the limit is
// enforced during construction rather than left to the runtime.
MaxDepth int
// MaxBytes bounds the source document. Zero means DefaultMaxBytes;
// a negative value means no limit, for a caller reading input it
// produced itself.
MaxBytes int64
// MaxNodes bounds the tree. Zero means DefaultMaxNodes; a negative
// value means no limit.
//
// Both limits exist because neither alone is a memory bound. A node
// costs a fixed ~200 bytes whatever it contains, so the heap a document
// needs depends on how many nodes it has rather than how long it is:
// a megabyte of "<a/>" is fifty times the memory of a megabyte of text.
// MaxBytes bounds the read; MaxNodes bounds what the read can allocate.
MaxNodes int
// contains filtered or unexported fields
}
ParseOptions controls document construction.
type QName ¶
QName is an expanded name: namespace URI plus local part, with the prefix retained only for serialisation.
Equality in XPath is defined on (URI, Local) alone — the prefix is not part of the value — so Equal deliberately ignores Prefix. Keeping the prefix around anyway matters because a literal result element must be serialised with the prefix the stylesheet author wrote, not one we invent.
func (QName) Clark ¶
Clark returns the {uri}local form, which is unambiguous without a namespace context and is therefore what error messages and map keys use.
type Sequence ¶
type Sequence []Item
Sequence is an ordered list of items. The empty sequence is a nil or zero-length slice; both are treated identically by every operation, so callers never have to normalise before comparing.
A sequence is flat: XDM has no nested sequences, and every constructor in this package maintains that invariant.
func Atomize ¶
Atomize converts a sequence to atomic values, replacing each node with its typed value. This is the fn:data() operation, applied implicitly wherever XPath 2.0 requires atomic operands.
Every item in the result is an *Atomic. Callers rely on that: two dozen of them assert the type without checking, because within the data model there is nothing else atomisation can produce.
Opaque items are the exception, and they are dropped here rather than passed through. They carry engine-internal state — the transform runtime, grouping bookkeeping — through the closed Item interface, and a stylesheet that names the internal namespace could reach one:
xmlns:gi="urn:goxslt:internal" ... distinct-values($gi:runtime)
Passing it through made that expression panic with an interface-conversion error, which in a server embedding this engine is a denial of service triggered by stylesheet text. An Opaque has no typed value, so dropping it is also what the data model implies: it is not a node and not an atomic value, so fn:data has nothing to return for it.
func Empty ¶
func Empty() Sequence
Empty is the canonical empty sequence.
A function rather than a variable. An exported package-level var of slice type is writable by anyone who imports the package, and a single stray assignment would corrupt the value every other caller reads -- a process-wide fault with no owner and no way to detect it. The value is nil, so this compiles to nothing.
func One ¶
One wraps a single item as a sequence. Named for how often it is needed: most XPath operations produce exactly one item and must still return a sequence.
func SortDocumentOrder ¶
SortDocumentOrder sorts a sequence of nodes into document order and removes duplicates.
Every path expression in XPath 2.0 returns nodes in document order with duplicates removed, and so do the union, intersect and except operators. Doing it in one place means the axis implementations can emit nodes in whatever order is natural for them (reverse axes emit backwards) without each having to re-sort.
Items that are not nodes are an error at the call sites that use this, so they are passed through unsorted rather than silently dropped; the caller type-checks first.
func (Sequence) First ¶
First returns the first item, or nil if the sequence is empty. Callers that require exactly one item should use Single instead so that a length > 1 is reported rather than silently truncated.
func (Sequence) Single ¶
Single returns the sole item of a one-item sequence. It reports an error for any other length, because the places that call it (operands of arithmetic, the argument of a function declared to take exactly one item) are precisely the places where XPath 2.0 raises XPTY0004 rather than coercing.
type Tree ¶
type Tree struct {
Root *Node
// DocType is the DOCTYPE declaration's text, when the document had one
// and AllowDOCTYPE permitted it. Empty otherwise.
//
// It is retained because the internal subset is the only place a
// document's own DTD lives, and validating against it needs the text —
// encoding/xml hands the declaration over as one opaque token and keeps
// nothing. The dtd package parses it; this package applies only the two
// declarations whose absence is visible in the data model.
DocType string
// contains filtered or unexported fields
}
Tree owns a document and the counter used to assign document order.
func Parse ¶
func Parse(r io.Reader, opts ParseOptions) (*Tree, error)
Parse builds an XDM tree from an XML document.
It uses encoding/xml as a tokeniser only. The Go decoder's own namespace handling is not usable here: it resolves prefixes into Name.Space but discards the prefix and the declarations themselves, and XSLT needs both — namespace nodes are addressable on the namespace axis, and a literal result element must be serialised with the prefix the author wrote.
func ParseString ¶
func ParseString(s string, opts ParseOptions) (*Tree, error)
ParseString is Parse over a string, which is what most tests and the stylesheet compiler want.
func (*Tree) Finalize ¶
func (t *Tree) Finalize()
Finalize assigns document-order indices across the whole tree in a single pre-order walk. It must be called after the tree is fully built and before any node comparison; every parser entry point in this package does so.
func (*Tree) UnparsedEntity ¶ added in v1.0.0
UnparsedEntity returns the system identifier and notation of an unparsed entity declared in a document's internal subset.
An unparsed entity is the one kind a processor never reads: it is declared SYSTEM or PUBLIC with an NDATA notation, referenced from an attribute of type ENTITY, and its identifier is data for the application rather than something to fetch. fn:unparsed-entity-uri and fn:unparsed-entity-public-id return exactly these.
The declarations are re-read from the retained DOCTYPE text rather than carried on every tree, since a document with unparsed entities is rare and the lookup happens at most once per call.
type TypeCode ¶
type TypeCode int
TypeCode identifies an atomic type from the XML Schema built-in hierarchy.
Only the types XPath 2.0 gives special treatment are enumerated. The rest of the schema hierarchy (xs:token, xs:NMTOKEN, and the other string subtypes) behaves identically to its base type for every operation this engine performs, so carrying them as distinct codes would add branches with no behavioural difference.
const ( // TypeUntypedAtomic is the type of atomised nodes in a schemaless // document. It is the reason XPath 2.0 needs so few explicit casts: an // untypedAtomic operand is converted to the required type at the point of // use, but *only* in the specific contexts the spec lists. TypeUntypedAtomic TypeCode = iota TypeString TypeBoolean TypeDecimal TypeInteger TypeDouble TypeFloat TypeQName TypeAnyURI TypeDate TypeTime TypeDateTime TypeDuration TypeYearMonthDuration TypeDayTimeDuration TypeHexBinary TypeBase64Binary // The five Gregorian types denote a recurring or partial calendar point: // a year, a year and month, a month, a month and day, or a day. TypeGYear TypeGYearMonth TypeGMonth TypeGMonthDay TypeGDay )
func NumericPromote ¶
NumericPromote returns the common type for a binary numeric operation, per the XPath 2.0 promotion lattice: integer -> decimal -> float -> double. Both operands are converted to that type before the operation runs.