xslt

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: 27 Imported by: 0

Documentation

Overview

Package xslt implements XSLT 2.0 transformation over the xdm data model, using the xpath package for expression evaluation.

Index

Constants

View Source
const DefaultMaxDepth = 1000

DefaultMaxDepth bounds template recursion when TransformOptions.MaxDepth is zero. It matches xdm.DefaultMaxDepth so that a document the parser accepts is one an identity transform can copy: the recursion counted here is the ordinary descent through the tree, not only a stylesheet calling itself.

View Source
const DefaultMaxResourceBytes int64 = 64 << 20

DefaultMaxResourceBytes bounds one file read through a FileResolver when MaxBytes is zero: 64 MB.

It is deliberately the same number as xdm.DefaultMaxBytes. A document read through fn:doc is handed straight to that parser, so a different figure here would mean one of the two limits never binds — a smaller one would make the parser's limit unreachable and a larger one would make this one decorative. Matching it means the file is refused at the read, before the bytes are spent, and the parser's identical limit remains the backstop for input that arrives some other way. 64 MB is far above any real stylesheet, code list or text resource while still bounding what a single resolved reference can allocate.

Variables

This section is empty.

Functions

func ApplyParameterDocument added in v1.2.0

func ApplyParameterDocument(root *xdm.Node, o *OutputSettings) error

ApplyParameterDocument reads the children of an output:serialization-parameters element into output settings.

root is that element. Each child names one parameter: its local name is the parameter, and a "value" attribute carries the value -- except use-character-maps, which spells its entries out as children because it has no xsl:character-map declaration to point at. The element form is the same one fn:serialize accepts as its second argument.

A parameter in another namespace is an implementation-defined extension the spec allows and this ignores; one in no namespace is a malformed document rather than an extension, since an extension must name its own namespace.

It is exported for the sake of an XQuery main module, whose prolog names this document with "declare option output:parameter-document" (XQuery 3.1 §2.2.4) and gets the same parameters by the same route. The fetching stays with the caller: XSLT resolves the URI against the element that wrote it and XQuery against the query's static base URI, and neither rule belongs to the reading of a document already in hand.

func Serialize added in v1.2.0

func Serialize(w io.Writer, seq xdm.Sequence, opts OutputSettings, charMap map[rune]string) error

Serialize writes a sequence with the serialization methods of the XSLT and XQuery Serialization 3.1 specification.

The rules it applies are the specification's, not XSLT's: sequence normalisation, the xml, xhtml, html, text, json and adaptive output methods, and the parameters that steer each. Nothing in it is peculiar to a stylesheet. It lives in this package because this is where the serialiser was written, and moving it would be a change to the packages of every existing caller for no gain; but a host other than XSLT — an XQuery main module stating its parameters with "declare option output:*", which is the same specification's other binding — serialises its result through this same function, so that the two agree by construction rather than by maintenance.

charMap is the character map applied to the finished text, or nil. An OutputSettings with an empty Method asks for the default method to be chosen from the sequence itself, which is what a host that stated no method wants; see defaultMethod.

func SerializeAsXML added in v1.0.0

func SerializeAsXML(r *Result) string

SerializeAsXML renders a result with the xml output method, ignoring the stylesheet's own xsl:output.

It exists for a caller making a *tree* assertion about a result — the W3C conformance harness is the one in this repository. The stylesheet's method is part of what serialisation means, not part of what the tree is: the html method injects a content-type meta into <head> and writes void elements unclosed, so a result asserted as a tree would be compared against markup the stylesheet never produced, and would not parse back as XML at all.

Indentation and the other settings are deliberately left at their defaults rather than inherited, for the same reason.

The version is the one exception, and it is not a rendering choice: XML 1.1 [2] Char admits the C0 controls and 1.0 does not, so the version decides whether a character can be written down at all rather than how it looks. A tree holding &#x1; is a legal 1.1 document and has no 1.0 spelling, so serialising it as 1.0 does not render it differently -- it fails, which is SERE0006 and correct. Forcing 1.0 here made the harness compare an assertion against the truncated prefix of an error it had discarded (xml-version-002 and -020 stopped at "<out>"), reporting a defect in the transform where the only defect was in how the comparison asked for the text.

func SetSerializationParam added in v1.2.0

func SetSerializationParam(o *OutputSettings, name, val string) error

SetSerializationParam applies one serialization parameter, named by its local name in the serialization namespace and carrying its lexical value, to a set of output settings.

It is the single place the parameter names of Serialization 3.1 §3 are turned into fields, so that every source of them agrees: an external parameter document read by applyParameterDocument, and an XQuery prolog's "declare option output:*", which states the same parameters in a different syntax and must not acquire a second, subtly different reading of them. xsl:output is not routed through here, because its values arrive already separated into attributes with their own AVT and QName-expansion rules.

An unsupported parameter is SEPM0017 rather than something to ignore: accepting one silently would let a caller believe it had asked for something it did not get.

Types

type BudgetedModuleResolver added in v1.3.0

type BudgetedModuleResolver interface {
	ModuleResolver
	// ResolveModuleWith is ResolveModule charging against b.
	ResolveModuleWith(
		b *xdm.EntityBudget, href, base string) (*xdm.Node, string, error)
}

BudgetedModuleResolver is a ModuleResolver that charges the entity expansion of the modules it parses against an allowance shared across the compilation, rather than minting a fresh one per module.

It is a separate optional interface rather than an extra parameter on ResolveModule so that existing implementations keep working: a resolver that does not implement it is called through ResolveModule exactly as before. This is the same arrangement xpath.ContextDocumentResolver uses, and for the same reason.

The allowance is scoped to ONE COMPILATION. xsl:import and xsl:include compose, so a single compilation resolves a whole graph of modules, and a per-module ceiling bounds none of it; a per-resolver allowance would be wrong in the other direction, because a FileResolver caches parsed trees and is documented as shareable between transforms, so its allowance would be spent by unrelated runs and would eventually refuse everything. The compilation is the operation that pulls the modules in, so it is the operation the ceiling belongs to.

type Compatibility added in v1.2.1

type Compatibility struct {
	// DropAttributesOnDocumentNode discards an attribute or namespace node
	// that reaches the content of a document node, instead of raising
	// XTDE0420.
	//
	// The specified behaviour is the error. 5.8.1 applies its rules in the
	// order listed, and the rule that unwraps a document node in the result
	// sequence unwraps document nodes only -- an attribute survives it and
	// reaches the check. The suite agrees: error-0420a exists to assert this,
	// and its own description reads "the xsl:copy is copying a document node
	// which can't have an attribute".
	//
	// Saxon nonetheless accepts it, and stylesheets are written to that.
	// DocBook xslTNG builds a temporary tree from a sequence containing an
	// attribute in its head.xsl, and every document carrying an xml:lang
	// fails against a processor that applies the rule.
	DropAttributesOnDocumentNode bool

	// PrivateFunctionsVisibleToEvaluate is retained for symmetry and is a
	// no-op: the private-visibility default is already confined to a real
	// xsl:package, which is where visibility is a meaningful property. It is
	// named here so that the behaviour is discoverable alongside the other
	// divergences rather than only in the changelog.
	PrivateFunctionsVisibleToEvaluate bool
}

CompileOptions configures compilation. Compatibility relaxes rules this engine is right to enforce, for callers who have to run stylesheets written against a processor that does not.

Every field here is off by default and every one makes this engine LESS correct. They exist because being right is not on its own useful: a rule that no released processor enforces is a rule real stylesheets were never written to satisfy, and a library that refuses them all is one that cannot be adopted. Turning a field on is the caller saying, explicitly and in one place, that they would rather match the incumbent than the specification.

Nothing here is inferred. A stylesheet cannot switch these on for itself, because the decision belongs to the host that knows where the stylesheet came from.

type CompileOptions

type CompileOptions struct {
	// Resolver loads xsl:include and xsl:import targets. Nil disables them,
	// which is the safe default for untrusted stylesheets.
	Resolver ModuleResolver

	// BaseURI of the stylesheet, for resolving relative include paths.
	BaseURI string
	// StaticParams supplies values for static stylesheet parameters — a
	// top-level xsl:param carrying static="yes" — keyed by the parameter's
	// {uri}local name. A static parameter is bound before static analysis
	// begins, so its value has to come from the caller rather than from
	// Transform's runtime Params.
	StaticParams map[string]xdm.Sequence

	// SchemaResolver loads the schemas named by xsl:import-schema. Nil
	// disables loading by location, for the same reason a nil Resolver
	// disables xsl:include: following a location means fetching whatever
	// the stylesheet names. An inline <xs:schema> child needs no resolver.
	SchemaResolver xsd.Resolver

	// SchemaParseOptions are passed to the XML parser for each schema
	// document xsl:import-schema loads, including the ones those documents
	// import in turn.
	//
	// The zero value refuses a DOCTYPE, which is the right default for the
	// same reason it is in xsd.Options: a schema has no use for one, and it
	// is the entry point for entity expansion attacks. A host that must
	// read a schema carrying one -- the W3C's own schema for schemas
	// declares its entities that way -- sets AllowDOCTYPE here, and thereby
	// says so deliberately rather than having it decided for it.
	SchemaParseOptions xdm.ParseOptions

	// PackageResolver loads the packages named by xsl:use-package. Nil
	// disables package composition, for the same reason a nil Resolver
	// disables xsl:include: resolving a package name means fetching whatever
	// the stylesheet asks for, and a host running an untrusted stylesheet
	// should decide what that can reach.
	PackageResolver PackageResolver

	// XPathVersion pins the version of XPath every expression in the
	// stylesheet is compiled in, overriding what the stylesheet declares.
	//
	// Nil, the default, derives it from the version attribute the way the
	// specification requires: 1.0 and 2.0 are XPath 2.0, 3.0 is XPath 3.1,
	// since XSLT 3.0 section 2.2 requires an XPath 3.1 processor.
	//
	// Setting it is for a host that must decide the language itself rather
	// than let the document decide — pinning XPath20 to run an untrusted
	// stylesheet under a smaller surface, or raising a stylesheet that
	// declares 2.0 but is known to want the later functions. It is a
	// deliberate departure from conformance in both directions, which is why
	// nothing sets it implicitly.
	XPathVersion *xpath.Version

	// Compat relaxes rules this engine is right to enforce, for stylesheets
	// written against a processor that does not. The zero value enforces
	// them all; see Compatibility.
	Compat Compatibility

	// MaxVersion caps the XSLT version whose constructs the compiler will
	// accept, whatever the stylesheet's own @version says.
	//
	// It exists for the constructs a later version *adds* to the language
	// rather than changes in it. xsl:package is the case that forces it: a
	// package written to XSLT 3.0 routinely carries version="2.0", because
	// the version attribute states the XSLT version of the *expressions*, not
	// of the packaging — so the attribute cannot tell an XSLT 2.0 processor,
	// for which xsl:package is XTSE0010, apart from a 3.0 one for which it is
	// the module element. Only the host knows which it is running as.
	//
	// Zero, the default, imposes no cap and accepts everything implemented.
	MaxVersion float64
	// contains filtered or unexported fields
}

type DecimalFormat

type DecimalFormat = xpath.DecimalFormat

DecimalFormat holds an xsl:decimal-format declaration.

The type and the formatting engine live in the xpath package, because XPath 3.0 has fn:format-number without a stylesheet to declare a format. This alias keeps the name a stylesheet-facing one: xsl:decimal-format compiles to an xslt.DecimalFormat exactly as it did.

type FileResolver

type FileResolver struct {
	// Roots are the directories a relative or absolute path may resolve
	// inside. A path escaping all of them is refused.
	Roots []string

	// AllowDOCTYPE permits a DOCTYPE declaration in the documents this
	// resolver parses. It is off by default, which is what keeps a
	// stylesheet from reaching a document that expands entities or names an
	// external one — the XXE entry point. A caller whose inputs are trusted,
	// a conformance suite among them, can turn it on.
	AllowDOCTYPE bool

	// ExternalEntities permits the documents this resolver parses to read
	// external entities and an external DTD subset, using this same resolver
	// — so they are confined to Roots on exactly the terms everything else
	// is, with the same scheme rejection and the same symlink handling.
	//
	// It is separate from AllowDOCTYPE and off by default. AllowDOCTYPE
	// admits declarations that cost nothing outside the document; this
	// admits reads of other files, which is the XXE surface proper. A caller
	// that wants DTD-declared entities does not thereby want file reads, and
	// making one imply the other would silently widen every existing caller.
	ExternalEntities bool

	// UnparsedText permits fn:unparsed-text to read files through this
	// resolver, confined to Roots on the same terms as everything else.
	//
	// It is separate from every other flag here and off by default, because
	// it is the widest of them. ResolveDocument hands the stylesheet a
	// parsed XML document, so a file that is not well-formed XML discloses
	// nothing; unparsed-text hands back the raw bytes of any file inside
	// Roots, so a root containing one XML data file and one private key
	// leaks the key. A caller who wants fn:doc does not thereby want that,
	// and folding the two together would silently widen every existing
	// caller of NewFileResolver.
	UnparsedText bool

	// MaxBytes bounds a single file this resolver reads, whichever way the
	// stylesheet asks for it: fn:doc and xsl:import, an external entity,
	// fn:unparsed-text, and XInclude parse="text" all go through one read.
	// Zero means DefaultMaxResourceBytes; a negative value means no limit,
	// for a caller reading files it produced itself.
	//
	// It is one field rather than one per call path on purpose. The confinement
	// above is a property of the *file*, not of the function that names it —
	// every root is readable by every path, so a stylesheet refused a 200 MB
	// file through unparsed-text would simply ask for it through doc(), and two
	// numbers would only mean the effective limit is the larger of them while
	// looking like it were the smaller. One number is the honest statement of
	// what a resolver will put in memory for one resource.
	//
	// The bound is needed here and not only downstream. xdm.ParseOptions.MaxBytes
	// bounds the *parse*, but readConfined has the whole file in memory before
	// the parser is handed anything, and fn:unparsed-text and XInclude
	// parse="text" never reach a parser at all. Once a caller enables a
	// FileResolver the stylesheet chooses which permitted file is read, so an
	// unbounded read makes any large readable file a memory-exhaustion
	// primitive.
	MaxBytes int64
	// contains filtered or unexported fields
}

FileResolver loads stylesheet modules and documents from the filesystem, confined to a set of allowed directories.

The confinement is the point. A stylesheet that can call document() on any path is a file-disclosure primitive, and one that can reach http:// is an SSRF primitive — but a blanket deny is not workable either, because real rule sets load code lists that ship beside the stylesheet. Naming the directories makes the trust boundary explicit and auditable.

func NewFileResolver

func NewFileResolver(roots ...string) (*FileResolver, error)

NewFileResolver returns a resolver confined to the given directories.

func (*FileResolver) Preload added in v1.0.0

func (r *FileResolver) Preload(uri string, tree *xdm.Tree)

Preload records a tree that has already been parsed as the answer for uri, so that a later fn:doc or fn:document naming the same resource hands back the very same nodes rather than a second parse of the same bytes.

Node identity is the point. XSLT 2.0 section 16.1 requires two retrievals of one absolute URI to return the same node, and the test the specification is written for is "fn:doc(fn:document-uri($arg)) is $arg". A caller that parses the principal source itself — every conformance harness does, because it has to annotate the tree before the transform sees it — supplies a document node the resolver has never heard of, and doc() of its own document-uri then parses the file again and answers a different node. Preloading closes that gap without weakening the containment check: the uri still has to resolve to a path inside a permitted root, and an unresolvable one is a no-op.

func (*FileResolver) ResolveDocument

func (r *FileResolver) ResolveDocument(uri, base string) (*xdm.Tree, error)

ResolveDocument implements xpath.DocumentResolver for fn:doc and fn:document.

func (*FileResolver) ResolveDocumentIn added in v1.3.0

func (r *FileResolver) ResolveDocumentIn(
	ctx *xpath.Context, uri, base string) (*xdm.Tree, error)

ResolveDocumentIn implements xpath.ContextDocumentResolver, charging the entity expansion of whatever it parses against the evaluation's allowance rather than minting a fresh one per document.

fn:doc and fn:document are ordinary functions, so an expression calls them once per node, and each call that missed the cache parsed with its own full ceiling -- the per-call mint that xpath.Context.entities exists to close, arriving here through a resolver instead of through fn:parse-xml.

The context is the right place to read the allowance from, and the resolver is not: a FileResolver caches parsed trees and is documented as shareable across transforms, so an allowance held on the resolver would be spent by unrelated runs and would eventually refuse everything. The context's allowance is minted by NewContext and inherited by AdoptBudget, so it is scoped to one evaluation exactly as items and bytes are.

func (*FileResolver) ResolveEntity added in v1.0.0

func (r *FileResolver) ResolveEntity(systemID, publicID, base string) (io.ReadCloser, string, error)

ResolveEntity implements xdm.EntityResolver, so that a document this resolver parses may read external entities — but only from inside Roots.

Every constraint the rest of this type enforces applies here unchanged, because the path goes through the same resolvePath: a non-file scheme is rejected before the filesystem is touched, symlinks are resolved before the containment check, and a path outside every root is refused. There is nothing entity-specific about the confinement, which is the point — an external entity is a file read like any other, and it gets the same gate rather than a second one written separately and drifting.

The base is the URI of the resource that made the reference, which for an entity declared in an external DTD subset is that subset rather than the document. Resolving against it is XML 1.0 section 4.4.3, and it is why a modular DTD in a subdirectory finds its siblings.

The returned URI is the file: URI of what was actually read, since that is what anything inside the fetched text resolves against.

func (*FileResolver) ResolveInclude added in v1.2.1

func (r *FileResolver) ResolveInclude(href, base, encoding string) ([]byte, string, error)

ResolveInclude implements xdm.IncludeResolver, so that a document this resolver loads may pull in other resources through XInclude.

Every constraint the rest of this type enforces applies here unchanged, because the path goes through the same resolvePath as fn:doc, xsl:include, fn:unparsed-text and external entities: a non-file scheme — http, https, ftp, anything — is rejected before the filesystem is touched, symlinks are resolved before the containment check, and a path outside every root is refused. There is deliberately nothing XInclude-specific about the confinement. A second gate written here would be a second thing to keep correct, and the first time the two drifted one of them would be the hole.

XInclude is therefore no wider a primitive than fn:doc already is: it reads the same files, from the same roots, with the same refusals. What it adds is only *who asks* — an element in the source document rather than an expression in the stylesheet — and the source document is exactly the party this library's threat model already treats as hostile, which is why reusing the confinement is the whole answer rather than merely part of it.

Unlike ResolveText there is no flag of its own guarding this. The gate sits one level up: nothing in this library performs XInclude processing unless the caller asks for it, and a caller that has asked has already named the roots. A second switch here would let a caller enable XInclude and then be quietly surprised that it did nothing.

The returned URI is the file: URI of what was actually read, since that is what a relative reference inside the included resource resolves against and what XInclude's base URI fixup records.

func (*FileResolver) ResolveModule

func (r *FileResolver) ResolveModule(href, base string) (*xdm.Node, string, error)

ResolveModule implements ModuleResolver for xsl:include and xsl:import.

func (*FileResolver) ResolveModuleWith added in v1.3.0

func (r *FileResolver) ResolveModuleWith(
	b *xdm.EntityBudget, href, base string) (*xdm.Node, string, error)

ResolveModuleWith is ResolveModule charging the entity expansion of whatever it parses against the shared allowance b.

It exists because a stylesheet reaches many modules -- xsl:import and xsl:include compose, so one compilation resolves a whole graph of them -- and parseUncached minted a fresh xdm allowance for every file it read. The 1 MB ceiling therefore bounded each MODULE separately rather than the compilation: sixty imported modules each expanding 700,000 bytes, every one of them under the ceiling, expanded 42 MB in total from 234 KB of source and allocated 173 MB. This is the same defect as XInclude's and fn:parse-xml's, one boundary further out; see docs/security.md.

A nil b leaves the per-document allowance in place, which is what a caller holding no budget of its own gets.

func (*FileResolver) ResolveText added in v1.0.0

func (r *FileResolver) ResolveText(uri, base, encoding string) (string, error)

ResolveText implements xpath.TextResolver for fn:unparsed-text.

The path goes through the same resolvePath as every other read this type performs, so the confinement is one implementation rather than two: a non-file scheme is rejected before the filesystem is touched, symlinks are resolved before the containment check, and a path outside every root is refused. UnparsedText only decides *whether* to ask; it does not relax where the answer may come from.

The encoding argument is honoured only for the encodings this package can decode without pulling in a converter. XSLT 2.0 section 16.2 requires an error for an encoding that is not supported, and reporting one is better than silently returning mojibake -- a stylesheet that reads a Shift-JIS file and gets bytes reinterpreted as UTF-8 produces wrong output with no indication anything went wrong.

type Instruction

type Instruction interface {
	// Execute runs the instruction, appending to out.
	Execute(rt *runtime, out *outputBuilder) error
}

Instruction is one compiled XSLT instruction.

Instructions write to an output builder rather than returning values, because an XSLT sequence constructor produces a *stream* of nodes and atomic values: xsl:element opens a node that subsequent instructions add children to. Returning trees from each instruction and concatenating them would mean building and copying the same subtree repeatedly.

type ModuleResolver

type ModuleResolver interface {
	ResolveModule(href, base string) (*xdm.Node, string, error)
}

ModuleResolver loads an included or imported stylesheet module.

type OutputSettings

type OutputSettings struct {
	Method        string // "xml", "html", "text"
	Indent        bool
	OmitXMLDecl   bool
	Encoding      string
	DocTypePublic string
	DocTypeSystem string
	CDataElements []xdm.QName
	// SuppressIndentation names the elements whose content is written with
	// no added whitespace even when indent is yes. It is separate from
	// CDataElements despite taking the same kind of list, because the two
	// name elements for opposite reasons: one because their text needs no
	// escaping, the other because their text must not be disturbed.
	SuppressIndentation []xdm.QName
	Standalone          string
	// Version is xsl:output/@version. For the html method it selects the
	// HTML version; for xml and xhtml it is the XML version.
	Version string
	// HTMLVersion is xsl:output/@html-version, new in XSLT 3.0.
	//
	// It exists because @version cannot answer the question for XHTML: there
	// it states the version of XML, so an XHTML5 document is still
	// version="1.0" and the HTML version had nowhere to go. Section 9.1: if
	// html-version is absent "the html output method uses the value of the
	// version parameter in its place", so it overrides for html and is the
	// only source for xhtml.
	HTMLVersion string
	// UseCharacterMaps names the xsl:character-map declarations applied at
	// serialisation.
	UseCharacterMaps []xdm.QName
	// ByteOrderMark writes a BOM at the start of the output. It is "no" by
	// default for every method, which is what makes UTF-8 output usable by
	// readers that do not expect one.
	ByteOrderMark bool
	// IncludeContentType controls whether the HTML and XHTML methods insert
	// the content-type meta element. It defaults to true, which is why it is
	// stored as a pointer: an explicit "no" has to be distinguishable from
	// the attribute being absent.
	IncludeContentType *bool
	// EscapeURIAttributes controls percent-escaping of URI-valued attributes
	// in the HTML and XHTML methods. It defaults to true, and is a pointer
	// for the same reason.
	EscapeURIAttributes *bool
	// UndeclarePrefixes emits namespace undeclarations, which only XML 1.1
	// permits. This parser implements XML 1.0, so it is recorded and ignored.
	UndeclarePrefixes bool
	// ItemSeparator is xsl:output/@item-separator: the string inserted
	// between adjacent items of the result sequence during sequence
	// normalisation (XSLT 3.0 section 5.7.1 step 3).
	//
	// It is a pointer because an explicit zero-length separator is not the
	// same as the attribute being absent: absent means the default rule
	// (adjacent atomic values separated by a single space, nodes not
	// separated at all), while item-separator="" means every adjacency gets
	// nothing, including between two atomic values.
	ItemSeparator *string

	// BuildTree says whether the raw result is normalised into a final
	// result tree before it is delivered or serialised (XSLT 3.0 section
	// 2.3.6). Nil is the default, which depends on the method: yes for xml,
	// html, xhtml and text, no for json and adaptive.
	//
	// With build-tree="no" the raw sequence is serialised as it stands, so
	// item-separator has something to separate -- normalisation would have
	// merged the whole sequence into one tree first, which is why the note
	// in 27.1 says the separator "has no effect ... if the effective value
	// of build-tree is yes".
	BuildTree *bool
	// ParameterDocument is the URI of an external
	// output:serialization-parameters document whose parameters override the
	// ones written here. It is kept as a URI rather than resolved at compile
	// time because 25.1 says the document "should be read during run-time
	// evaluation of the stylesheet", so that a stylesheet deployed away from
	// where it was written still finds its own parameters.
	ParameterDocument string
	// ParameterDocumentBase is the base URI of the element that wrote
	// ParameterDocument, which is what a relative URI there resolves
	// against. It has to travel with the URI rather than be taken from the
	// principal module: an <xsl:output name="f" parameter-document="p.xml"/>
	// in a module reached by xsl:include names a document beside *that*
	// module, and output-0722 puts the included module and its parameter
	// document in a subdirectory to make the difference visible.
	ParameterDocumentBase string
	// InlineCharMap is a character map given by value rather than by name:
	// the output:use-character-maps element of a parameter document spells
	// its entries out, having no xsl:character-map declaration to point at.
	// It overrides UseCharacterMaps entirely, being the higher-precedence
	// source of the same parameter.
	InlineCharMap map[rune]string
	// AllowDuplicateNames is the JSON output method's parameter of that name:
	// with it unset, two map keys that render to the same JSON string are
	// SERE0022 rather than two entries in one object.
	AllowDuplicateNames bool
	// JSONNodeOutputMethod is the method a node nested inside a JSON value is
	// serialised with, JSON having no node type of its own. Empty is the
	// default, "xml".
	JSONNodeOutputMethod string
	// MediaType is the media type of the output. It is metadata a caller
	// passes on, with one exception: the html and xhtml methods write it
	// into the content attribute of the meta element they inject, where it
	// is escaped like any other attribute value.
	MediaType string
	// NormalizationForm names a Unicode normalisation applied to the output.
	// NFC, NFD, NFKC and NFKD are implemented, as is "none"; see
	// normalizerFor in serialize.go. Any other value -- "fully-normalized"
	// among them -- is a serialization error rather than something to ignore,
	// because output that was silently left unnormalised would be accepted
	// by a consumer that then compares it against a normalised form and
	// finds a spurious difference.
	NormalizationForm string
	// Version10Implicit records that the principal module declares version
	// "1.0" and this is the implicitly-created final result tree.
	//
	// It changes only the *default* output method. Under backwards
	// compatibility an XSLT 1.0 stylesheet has no xhtml method to select —
	// the method did not exist — so a result whose document element is html
	// in the XHTML namespace serialises as xml, not xhtml: URI-valued
	// attributes are left unescaped and no content-type meta element is
	// added. An explicit xsl:output/@method overrides this like any other
	// default, and xsl:result-document clears the flag, because the tree it
	// creates is not the implicit one.
	Version10Implicit bool
}

OutputSettings holds the xsl:output declaration.

type PackageResolver added in v1.1.0

type PackageResolver interface {
	ResolvePackage(name, versionMatch string) (*xdm.Node, error)
}

PackageResolver locates the package a use-package declaration names.

A package is addressed by name and version, not by a URI to fetch, so this cannot be folded into ModuleResolver: there is no href to resolve. The resolver is handed the name and the version-matching expression exactly as written, and answers with the tree of the package that best matches, or an error if there is none.

type Pattern

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

Pattern is a compiled xsl:template match pattern.

Patterns look like path expressions but mean something different: a path says "navigate from here", a pattern says "does this node match". The natural implementation of matching — evaluate the path and check membership — is quadratic, because it would visit every node in the document for every node being matched.

Instead a pattern is matched right-to-left from the candidate node: check the last step's node test against the node itself, then walk *up* verifying each preceding step. That makes a match cost O(depth) rather than O(document size), which is the difference between a transform that finishes and one that does not on a large invoice.

func CompilePattern

func CompilePattern(src string, ns xpath.NamespaceResolver) (*Pattern, error)

CompilePattern compiles an XSLT match pattern.

func (*Pattern) Alternatives added in v1.0.0

func (p *Pattern) Alternatives() []*Pattern

Alternatives splits a union pattern into one Pattern per branch.

Section 6.4 says a template rule whose match pattern is a union behaves as if it were several template rules, one per branch, each with the default priority computed for that branch alone. Keeping them fused would give the whole rule the highest branch's priority, so a low-priority branch would outrank templates it should lose to; it would also make xsl:next-match skip the rule entirely after the first branch fired, when the spec has it reconsider the rule for each remaining branch.

func (*Pattern) Matches

func (p *Pattern) Matches(node *xdm.Node, ctx *xpath.Context) (bool, error)

func (*Pattern) MatchesReporting added in v1.1.0

func (p *Pattern) MatchesReporting(node *xdm.Node, ctx *xpath.Context) (bool, error, error)

Matches reports whether node matches the pattern.

ctx supplies the focus for predicate evaluation. Predicates in a pattern are evaluated with the candidate node as the context item, and with a context position derived from its position among its like-named siblings — which is why "para[1]" as a pattern means "a para that is the first para child of its parent" rather than "the first para in the document". MatchesReporting is Matches, additionally reporting the error of the last alternative that failed and was recovered from.

Recovery is right for a pattern that cannot be evaluated against a particular node -- see recoverPatternError -- but the caller sometimes has context that turns the same failure into a stylesheet-level error. The key index builder is the case: an XPST0008 naming a global that is itself being computed is the circularity XTDE0640 describes, and only the builder knows which globals those are.

The recovered error is returned alongside a false match rather than instead of it, so a caller that does not ask keeps the recovering behaviour.

func (*Pattern) Priority

func (p *Pattern) Priority() float64

Priority returns the pattern's default priority, per the XSLT rules: a specific name test scores 0, a namespace wildcard -0.25, a full wildcard or bare kind test -0.5, and anything more complex 0.5.

These numbers exist so that a more specific template wins over a general one without the author having to say so. Getting them wrong makes template selection silently pick the wrong rule, which is far harder to debug than a crash.

func (*Pattern) String

func (p *Pattern) String() string

String returns the pattern source.

type Result

type Result struct {

	// Nodes is the result sequence, which for a typical stylesheet is a
	// single element.
	Nodes xdm.Sequence
	// Messages holds xsl:message output, in the order produced.
	Messages []string
	// Warnings holds the warnings the transform raised, in the order
	// produced. They report conditions the spec asks a processor to notice —
	// currently the two xsl:mode warning attributes — and never affect the
	// result.
	Warnings []string
	// Secondary holds the documents produced by xsl:result-document, in the
	// order produced. It is empty for the great majority of stylesheets,
	// which produce a single result.
	Secondary []SecondaryResult
	// BaseURI is the URI this result tree is identified by, which Tree()
	// puts on the document node it manufactures. It is empty for the
	// principal result, whose document node has no URI of its own; a caller
	// assembling a Result from a SecondaryResult sets it from that
	// document's BaseURI so that base-uri(/) answers inside it.
	BaseURI string
	// contains filtered or unexported fields
}

Result is the outcome of a transform.

func (*Result) BuildsTree added in v1.3.0

func (r *Result) BuildsTree() bool

BuildsTree reports whether this result is normalised into a final result tree, which is XSLT 3.0 section 26.1's build-tree attribute applied to the principal result: "The build-tree attribute controls whether the raw principal result or secondary result is converted to a final result tree."

It is exported because Tree returns nil when the answer is no, and a caller needs a way to tell that apart from a transform that produced nothing.

func (*Result) Serialize

func (r *Result) Serialize(w io.Writer) error

Serialize writes the result using the stylesheet's xsl:output settings.

Deliberately not named WriteTo: that name implies io.WriterTo, whose contract returns a byte count this would have to fabricate.

func (*Result) String

func (r *Result) String() string

String renders the result using the stylesheet's output settings, DISCARDING any serialization error and returning "" in its place.

Serialization errors are not incidental: checkOutputSettings raises them for an encoding this serialiser cannot produce, for a sequence holding a map or a function, and -- the reason this warning is here -- for a doctype-system or media-type value that cannot be written safely. Those last two are reachable from a SOURCE DOCUMENT through an attribute value template, so a caller serialising untrusted input through this method gets "" where it expected a document and no indication that anything was refused.

String cannot report the error and stay a fmt.Stringer, and the error is not raised any earlier: XSLT 3.0 section 2.10 places a serialization error on the principal result "after the transformation has finished", so Transform returns nil for a stylesheet whose output settings are invalid. Use Serialize for anything whose failure you need to see.

func (*Result) Tree

func (r *Result) Tree() *xdm.Node

Tree returns the result as a document node, for callers that want to keep navigating it rather than serialise it — which is what a Schematron driver does with an SVRL report.

type SecondaryResult

type SecondaryResult struct {
	// Href is the resolved @href value, as written by the stylesheet. It is
	// the caller's choice what to do with it — this engine never writes to
	// the filesystem on a stylesheet's behalf, since a transform that can
	// create files anywhere the process can write is a hazard the caller
	// should be the one to opt into.
	Href string
	// BaseURI is Href resolved against the base output URI, which is the
	// base URI of every node in this document that does not override it with
	// xml:base.
	//
	// Section 19.1 makes the base output URI implementation-defined when the
	// caller does not supply one, and the stylesheet's own location is the
	// only URI this engine has: an @href of "out/second.xml" written in a
	// stylesheet read from .../foo.xsl means .../out/second.xml, which is
	// also where a caller honouring the href would write it. Leaving it
	// empty made base-uri() answer "" for every node in a secondary result,
	// and made a relative xml:base inside one resolve against nothing.
	BaseURI string
	// Nodes is the result sequence for this document.
	Nodes xdm.Sequence
	// Output holds the serialisation settings that apply to this document,
	// taken from @format and any serialisation attributes on the instruction.
	Output OutputSettings
	// contains filtered or unexported fields
}

SecondaryResult is one document produced by xsl:result-document.

It is kept separate from the principal result rather than merged into it: the whole point of the instruction is that the stylesheet author wants two distinct documents, and folding them together would give a caller expecting several outputs a single plausible-looking wrong one.

func (*SecondaryResult) Serialize

func (sr *SecondaryResult) Serialize(w io.Writer, charMap map[rune]string) error

Serialize writes the secondary document using its own output settings.

A caller holding a SecondaryResult would otherwise have no way to render it: the serialiser is unexported, and re-deriving these settings from the stylesheet is exactly the duplication @format exists to avoid. The charMap argument overrides the table resolved from the document's own @use-character-maps; passing nil uses that table, which is what a caller almost always wants.

func (*SecondaryResult) String

func (sr *SecondaryResult) String() string

String renders the secondary document using its own output settings, DISCARDING any serialization error and returning "" in its place.

The same trade as Result.String, and the more exposed of the two: the serialization attributes of xsl:result-document are attribute value templates, so doctype-system and media-type can be supplied by the SOURCE DOCUMENT rather than by the stylesheet author. Those are exactly the two values checkOutputSettings refuses -- SEPM0016 for a system identifier holding both quote kinds, and the escaping that keeps media-type inside the <meta> attribute it is written into -- so this is the method where untrusted input most directly reaches a refusal that String drops on the floor. A caller writing result documents through it gets "" for a refused document and no indication that anything was wrong.

String cannot report the error and stay a fmt.Stringer, and the error is not raised any earlier: XSLT 3.0 section 26 makes a serialization error on a secondary result a dynamic error in the evaluation of xsl:result-document, which is where it is raised, not at the end of the transformation. Use Serialize for anything whose failure you need to see; it takes a character map because a secondary document carries its own.

type Stylesheet

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

Stylesheet is a compiled XSLT 2.0 stylesheet.

Compilation is separated from execution so that a stylesheet compiles once and transforms many documents concurrently. Everything reachable from here is immutable after Compile returns; all per-transform state lives in the runtime context. That is what makes a compiled EN 16931 rule set — tens of megabytes — shareable rather than per-worker.

func Compile

func Compile(doc *xdm.Node, opts CompileOptions) (*Stylesheet, error)

Compile compiles a stylesheet from a parsed XSLT document.

The lock is taken here and only here. compileLocked holds the body, so that a compilation running *inside* another one -- which fn:transform is, when a static variable calls it during the static phase -- can reach the same code without asking for a mutex the outer call has not let go of. See compileNestedLocked.

func (*Stylesheet) Output

func (s *Stylesheet) Output() OutputSettings

Output returns the stylesheet's output settings.

func (*Stylesheet) Schema

func (s *Stylesheet) Schema() *xsd.Schema

Schema returns the schema assembled from the stylesheet's xsl:import-schema declarations, or nil when it has none.

It is exposed so that a caller can validate a source document against the same schema the stylesheet declares, rather than having to load it twice and risk the two disagreeing.

func (*Stylesheet) SetSchemaIfAbsent added in v1.3.0

func (s *Stylesheet) SetSchemaIfAbsent(sch *xsd.Schema) bool

SetSchemaIfAbsent installs sch as the stylesheet's schema when the stylesheet declared no xsl:import-schema of its own, and reports whether it did so.

A schema-aware processor holds one schema cache, and a stylesheet compiled against it can validate using any component in that cache -- XSLT 2.0 section 3.14 makes an xsl:import-schema satisfiable "using a schema that is already known to the processor". A caller that has loaded a schema externally, as the W3C test suite's own driver does for an environment's <schema>, needs to say so for a validation="strict" to find anything at all.

It refuses to displace a schema the stylesheet built for itself: a declaration named by xsl:import-schema is the one the stylesheet asked for, and a caller merging into it should use Schema() and merge, not this.

func (*Stylesheet) SourceDocumentResolver added in v1.1.0

func (s *Stylesheet) SourceDocumentResolver(inner xpath.DocumentResolver) xpath.DocumentResolver

SourceDocumentResolver wraps a document resolver so that the trees it hands back are whitespace-stripped by this stylesheet's xsl:strip-space and xsl:preserve-space declarations, exactly as Transform strips the ones fn:doc returns during the transformation.

It exists for the one caller that has to evaluate an expression over the source documents *before* the transform begins: the initial match selection is supplied to Transform as a sequence, so whoever computes that sequence computes it outside the transform and would otherwise see unstripped trees the transform itself never sees. Section 4.4 scopes stripping to "all source documents", and a node reaches the transform through the initial match selection as surely as through fn:doc. mode-1802 selects doc('mode-14.xml')//v[position() = 1 to 5] and then indexes the source by position, which counts the whitespace text nodes if they are still there.

Passing nil returns nil, so a caller with no resolver to wrap is unchanged.

func (*Stylesheet) Transform

func (s *Stylesheet) Transform(ctx context.Context, source *xdm.Node, opts TransformOptions) (*Result, error)

Transform applies the stylesheet to a source document.

The Stylesheet is not mutated, so one compiled stylesheet may be used from many goroutines concurrently.

type Template

type Template struct {
	Match    *Pattern
	Name     xdm.QName
	HasName  bool
	Mode     []string // empty means the default (unnamed) mode
	Priority float64
	Params   []*Variable
	Body     []Instruction
	// contains filtered or unexported fields
}

Template is a compiled xsl:template.

type TransformOptions

type TransformOptions struct {
	// Params supplies values for top-level xsl:param, keyed by Clark name
	// ("{uri}local", or just "local" for a no-namespace parameter).
	Params map[string]xdm.Sequence

	// Documents resolves fn:doc and fn:document. Nil disables them, which is
	// the default: a stylesheet that can open arbitrary URIs is an SSRF and
	// file-disclosure vector, and validation rule sets need at most the code
	// lists shipped beside them.
	Documents xpath.DocumentResolver

	// Collections resolves fn:collection. Nil disables it, which is the
	// default, and setting Documents does not set this: enabling fn:doc for
	// a known code list should not also let a stylesheet enumerate whatever
	// a collection URI happens to name.
	Collections xpath.CollectionResolver

	// Texts resolves fn:unparsed-text. Nil disables it, which is the
	// default, and setting Documents does not set this: fn:doc hands back a
	// parsed XML document, while fn:unparsed-text hands back the raw bytes
	// of whatever the resolver will open. See xpath.TextResolver.
	Texts xpath.TextResolver

	// Environment answers fn:environment-variable and
	// fn:available-environment-variables. Nil withholds the process
	// environment from both, which is the default: a stylesheet that can
	// read the environment can read whatever credentials the process was
	// started with, and no resolver root bounds that. Setting Documents or
	// Texts does not set this. See xpath.EnvironmentResolver, and
	// xpath.OSEnvironment for the widest grant.
	Environment xpath.EnvironmentResolver

	// MaxDepth bounds template recursion. Zero means DefaultMaxDepth; a
	// negative value means no limit.
	//
	// "No limit" is not the safe end of the range. Recursion runs on the Go
	// stack, and exhausting it is a fatal error the runtime does not deliver
	// as a panic, so recover cannot turn it back into a failed request: the
	// process dies and every other request in flight dies with it. The bound
	// is what converts that into an error value. Remove it only for input
	// you produced yourself.
	//
	// The bound catches a stylesheet that recurses without a base case,
	// which is the common authoring mistake. But it also counts the ordinary
	// descent of an identity transform, so a limit below the parser's left
	// this refusing documents it had just accepted: at the old fixed 300, a
	// legal 500-deep document could be parsed and not transformed.
	MaxDepth int

	// DisableAssertions turns off xsl:assert checking for the whole
	// transformation. The zero value leaves assertions enabled, which is what
	// XSLT 3.0 section 22.2 requires: "By default, assertions are enabled."
	//
	// The same section asks for this switch: "An implementation should provide
	// an external mechanism to disable assertion checking for the stylesheet
	// as a whole (either statically or dynamically). The detail of such
	// mechanisms is implementation-defined." It lives here rather than on
	// CompileOptions because dynamic is the more useful of the two readings —
	// one compiled stylesheet can then be run with assertions on in test and
	// off in production, without recompiling — and because a caller who wants
	// the static reading already has use-when, which 22.2 names first.
	//
	// A disabled assertion is skipped before its @test is evaluated, so it
	// cannot fail and costs nothing. Note that this is the only thing that may
	// skip one: the section closes by asking implementations to "avoid
	// optimizing xsl:assert instructions away".
	DisableAssertions bool

	// InitialMode names the mode for the initial apply-templates.
	InitialMode string

	// InitialMatchSelection is what the initial apply-templates selects from.
	//
	// Section 2.3.2 makes the initial match selection an arbitrary sequence,
	// not a node: a caller may start the transform at a set of nodes, or —
	// since XSLT 3.0 gave patterns the power to match one — at atomic values
	// with no source document behind them at all. Leaving it nil defaults the
	// selection to the source document, which is the ordinary invocation.
	//
	// Setting it is also what satisfies XTDE0044: naming an initial mode
	// obliges the caller to say what to apply it to, and the source document
	// is only the usual way of saying it.
	InitialMatchSelection xdm.Sequence

	// InitialTemplate names a template to invoke instead of matching the
	// document root, which is how a stylesheet with only named templates is
	// entered.
	InitialTemplate string
	// InitialTemplateURI is the namespace URI of InitialTemplate, for a
	// caller that has already resolved the prefix in its own namespace
	// context. Empty means resolve any prefix in InitialTemplate against the
	// stylesheet's own declarations instead.
	//
	// The two are not interchangeable. A caller naming the template from
	// outside the stylesheet — a test catalog, a command line with its own
	// bindings — binds the prefix itself, and resolving it a second time
	// against the stylesheet can silently select a DIFFERENT template that
	// happens to spell another namespace with the same prefix.
	InitialTemplateURI string

	// InitialFunction names a stylesheet function to invoke as the entry
	// point, which is the third way into a stylesheet beside a template and
	// an apply-templates. Section 2.3.5 calls it the initial function.
	//
	// The name is expanded: a caller naming a function from outside the
	// stylesheet has already bound its own prefixes, and resolving a second
	// time against the stylesheet's declarations could select a different
	// function that spells another namespace with the same prefix. This is
	// the same hazard InitialTemplateURI exists for, and the QName form
	// avoids it outright rather than needing a companion field.
	//
	// Leave it zero to use one of the other entry points. It is mutually
	// exclusive with InitialTemplate and with an initial mode.
	InitialFunction xdm.QName

	// InitialFunctionParams are the arguments of the initial function call,
	// in order. Section 2.3.5 makes the arity of the entry point the LENGTH
	// OF THIS LIST -- "in the design of a concrete API, the arity may be
	// inferred from the length of the parameter list" -- so this is not
	// merely the values but also the half of the function's identity that
	// InitialFunction does not carry.
	//
	// A nil list therefore selects arity zero rather than meaning "unset":
	// the spec gives no way to name an initial function without also fixing
	// its arity, so a caller naming a function and supplying no parameters is
	// asking for the nullary one, and gets XTDE0041 if none exists.
	InitialFunctionParams []xdm.Sequence

	// InitialTemplateParams are the values supplied for the initial
	// template's non-tunnel parameters, keyed by the parameter name in Clark
	// notation. InitialTemplateTunnelParams are its tunnel parameters, which
	// pass through to whatever the template calls in turn.
	//
	// They are separate from Params, which binds the stylesheet's global
	// parameters. Section 2.3.2 makes those two different acts of priming: a
	// global parameter belongs to the stylesheet and is set once, while these
	// are the arguments of one call, and a template parameter and a global
	// parameter may share a name without either standing for the other.
	InitialTemplateParams       map[string]xdm.Sequence
	InitialTemplateTunnelParams map[string]xdm.Sequence

	// InitialModeParams and InitialModeTunnelParams are the same thing for an
	// apply-templates invocation: section 2.3.3 gives that entry point "two
	// sets of (QName, value) pairs, one set for tunnel parameters and one for
	// non-tunnel parameters", with "the same [effect] as when a template is
	// invoked using xsl:apply-templates with an xsl:with-param child".
	//
	// They are kept apart from the initial-template pair rather than shared
	// with it because the two entry points are mutually exclusive (XTDE0047)
	// but the names would still mislead a caller reading the API.
	InitialModeParams       map[string]xdm.Sequence
	InitialModeTunnelParams map[string]xdm.Sequence

	// Now fixes the value fn:current-dateTime returns. Leave it zero to use
	// the wall clock; set it to make a transform reproducible, which is what
	// a golden-file test needs.
	Now time.Time

	// ImplicitTimezone is the offset in minutes for date values with no
	// timezone. Defaults to UTC so that results are reproducible across
	// machines.
	ImplicitTimezone int

	// BaseOutputURI is the URI the principal result tree is destined for.
	// Section 19.1 makes it implementation-defined when the caller supplies
	// none, and this engine never writes files itself, so the default is to
	// have none at all: fn:current-output-uri then answers the empty
	// sequence everywhere, and a relative @href on xsl:result-document
	// resolves against the stylesheet's own location instead.
	BaseOutputURI string
	// contains filtered or unexported fields
}

TransformOptions configures one transform.

type Variable

type Variable struct {
	Name xdm.QName
	// Select is the value expression, or nil when the value comes from the
	// element's content.
	Select *xpath.Compiled
	// Body is the sequence constructor used when Select is absent. A variable
	// with content builds a temporary tree, which is why the two forms are
	// not interchangeable: "select" yields whatever the expression yields,
	// while content always yields a document node.
	Body []Instruction
	// Required marks a parameter that must be supplied.
	Required bool
	// IsParam distinguishes an xsl:param from an xsl:variable. The two are
	// compiled to the same structure because they evaluate identically, but
	// only a parameter can be supplied from outside, and section 10.1.1
	// gives the two different error codes when a declared "as" type rejects
	// the value: a parameter left unsupplied whose required type excludes
	// the empty sequence is XTDE0610, while a variable is only ever the
	// plain type error.
	IsParam bool
	// Tunnel marks a tunnel parameter, which passes through templates that do
	// not declare it.
	Tunnel bool
	// contains filtered or unexported fields
}

Variable is a compiled xsl:variable, xsl:param or xsl:with-param.

Jump to

Keyboard shortcuts

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