XML toolkit for Go covering XML parsing, SAX2-style streaming, XPath 3.1, XInclude, XSD, Relax NG, and Schematron. Started as a libxml2-style port to Go and grew broader native Go APIs and features along the way.
The xpath3 package targets XSD 1.1 only. This means +INF is a valid lexical form for xs:double and xs:float, and
xs:dateTimeStamp is a recognized type. QT3 tests with dependency type="xsd-version" value="1.0" are skipped.
The xsd package defaults to XSD 1.0 and treats 1.1 as opt-in via Compiler.Version(xsd.Version11) (or a
vc:minVersion="1.1" hint on the root <xs:schema> when no explicit version is set). The resolved version is frozen
onto the compiled Schema so the Validator applies the same semantics. 1.0 stays the default so existing behavior and
goldens are unchanged.
resolveVersion (compile.go) resolves in order: a forced Compiler.Version() (always wins) → a
vc:minVersion="1.1"-or-higher hint on the root → a configured Compiler.DefaultVersion(v) (the opt-in fallback for a
schema silent on version) → Version10. DefaultVersion never overrides a forced version or a vc hint — it only
chooses the fallback. The STANDALONE default stays Version10; DefaultVersion lets an embedding layer opt its schemas
into 1.1 by default while still honoring an explicit version.
Validator.SkipDatatypeIntegrityChecks(true) suppresses the document-wide datatype-integrity walks in
validateDocument (cfg.skipDatatypeIntegrity): the xs:ID/xs:IDREF/xs:IDREFS uniqueness+referential-integrity walk
(version-INDEPENDENT — runs in both 1.0 and 1.1) and the XSD 1.1-only xs:ENTITY/xs:ENTITIES walk; content-model, type,
and xs:key/unique/keyref identity-constraint validation are unaffected. It is for callers that validate an
element/subtree as a fragment and enforce document-scope ID/IDREF integrity themselves (xslt3). In 1.0 it suppresses the
ID/IDREF walk (the ENTITY walk never runs there).
XSD 1.1 is fully implemented behind the Version11 opt-in. The committed W3C conformance snapshot reports 1,049 pass
/ 0 skip / 0 fail (xsd/summary-xsd11.md). The complete feature-by-feature implementation state — every 1.1
construct, its file/function, spec clause, version gating, W3C test evidence, and remaining gaps — lives in
.claude/docs/xsd11.md. Read that doc before any work in xsd/. Feature areas covered there:
- Type system: xs:assert (complex + simpleContent), xs:assertion facet, conditional type assignment (xs:alternative), simpleContent content-type narrowing, attribute inheritance, 1.1 built-in datatypes, simple-type 1.1 edges.
- Content models: UPA weakening, open content (xs:openContent / xs:defaultOpenContent), xs:all relaxations, wildcard notNamespace/notQName, particle-restriction relaxations, content-model backtracking, Wildcard EDC.
- Identity constraints: field-node classification/canonicalization, @ref, @xpathDefaultNamespace, structural rules, skip-wildcard scoping.
- Document-wide walks: xs:ID/IDREF/IDREFS and xs:ENTITY/ENTITIES integrity.
- Schema composition & representation: xs:override, xsi: attribute references, conditional inclusion (vc:), NCName/QName whitespace collapse, xs:notation, and the many version-INDEPENDENT XML-representation/structural checks.
Do NOT enforce 1.1-only clauses in the 1.0/default path — 1.0 must stay byte-identical to origin.
The schematron package accepts ONLY the queryBinding values ISO/IEC 19757-3 defines normatively and this package
implements, and refuses every other value. An absent attribute or xslt resolves to QueryBindingXPath1 (Annex C's
default binding, the xpath1 engine, byte-identical to libxml2); xslt3 (2020 Annex J, XSLT 3.0, whose query language
is XPath 3.1) and xpath3 (2020 Annex K, XPath 3.0, which XPath 3.1 is a superset of) resolve to QueryBindingXPath3
(the xpath3 engine). Everything else fails compilation with ErrUnsupportedQueryBinding: the 2.0 bindings (Annexes H
and I) are defined but unimplemented, and xpath/xquery/exslt/stx are reserved by clause 6.4's NOTE without any
definition, so a schema naming one says nothing about what its expressions mean. Do NOT invent names the standard does
not define (xslt1, xpath1, xpath31). Matching is case-insensitive because each annex says "in any mix of upper and
lower case letters", and the value is trimmed because the attribute is declared xsd:token. resolveQueryBinding
(querybinding.go) resolves in order: a forced Compiler.QueryBinding() (always wins, and skips the attribute) → the
schema attribute → Compiler.DefaultQueryBinding(b) → QueryBindingXPath1. The resolved binding is frozen onto the
compiled Schema.
engine/runner/value (engine.go) is the seam the two engines implement, so parse.go and validate.go never
name an XPath package. The value methods carry the per-binding semantics: effectiveBoolean cannot fail under 1.0 but
raises FORG0006 for a multi-item atomic sequence under 3.1 (reported, test treated as false); stringValue
(<value-of>) takes the first node's string-value under 1.0 and space-joins every atomized item under 3.1; <let>
binds a sequence under 3.1. Rule contexts go through contextToXPath in both bindings, so an XSLT match pattern that is
not an expression (key('k','v')) is unsupported. The 3.1 engine passes no URI resolver and no HTTP client, so
fn:doc/fn:collection/fn:unparsed-text retrieve nothing.
Both engines build ONE document-order cache per validation run and share it across every expression
(xpath1Runner.docOrder threaded through ixpath.WithDocOrderCache, xpath3.Evaluator.DocOrderCache). Without it each
evaluation indexes the whole instance again, which makes validation quadratic in document size. Keep the cache per run:
a cache outliving the run would go stale against a mutated document.
Do NOT change the XPath 1.0 path — it must stay byte-identical to libxml2.
The xslt3 package targets Basic XSLT 3.0 conformance (W3C spec Section 27). The spec defines 8 conformance levels; only "Basic XSLT Processor" is required. The remaining 7 are optional features:
| Feature | Status | Notes |
|---|---|---|
| Basic XSLT Processor | Target | Core requirement |
| Schema-Awareness | In progress | xsl:import-schema, type annotations |
| Serialization | Implemented | xml/html/text output methods |
| Streaming | Implemented | DOM-materialization; XTSE3430 analysis |
| Higher-Order Functions | Implemented | Via xpath3 |
| XPath 3.1 | Implemented | Via xpath3 |
| Dynamic Evaluation | Implemented | xsl:evaluate |
| Backwards-Compatible Processing | Implemented | XSLT 1.0 behavior + XPath 1.0 compatibility mode |
Schemas imported via xsl:import-schema (and a source-document schema) default to XSD 1.1
(compile_schema.go/source_schema.go build the xsd.Compiler with .DefaultVersion(xsd.Version11)), so a schema
silent on version compiles with 1.1 semantics (CTA/xs:alternative, unions, etc.) while an explicit Compiler.Version()
or a schema vc:minVersion hint still wins. xslt3 validates a constructed element/subtree through
schemaRegistry.ValidateDoc with xsd.Validator.SkipDatatypeIntegrityChecks(true): content/type/CTA validation runs at
1.1 but the XSD 1.1 document-wide xs:ID/IDREF/ENTITY integrity walks are suppressed, because element-level validation
(xsl:validation="strict" on an LRE) must not enforce whole-document ID uniqueness — xslt3 applies document-scope
ID/IDREF integrity itself via validateDocIDConstraints at the true document/result-document scope (XTTE1555), matching
the W3C validation-16xx semantics.
Backwards-compatible processing (XSLT 3.0 §3.10) is enabled per element when its effective version < 2.0 — the
nearest-in-scope [xsl:]version (on the element, an ancestor, an included/imported module root, a global
variable/param, or a literal result element's xsl:version; a _version shadow attribute takes precedence over the
literal). Effective version in [2.0, 3.0) is identical to 3.0, so there is no separate "XSLT 2.0 compatibility"
behavior. The one exception is the xsl:output boolean serialization-parameter value space (compile_formats.go
compileOutput/serializationYesNoOnly): Serialization 3.0 widened the boolean parameters (indent,
omit-xml-declaration, byte-order-mark, escape-uri-attributes, include-content-type, undeclare-prefixes,
allow-duplicate-names, build-tree — and standalone's boolean synonyms) to the full xs:boolean lexical space {yes, no, true, false, 1, 0}, but an effective XSLT version below 3.0 (the module's in-scope c.effectiveVersion, NOT the
xsl:output element's own @version, which is the serialization output-version parameter) restricts them to {yes, no}
(standalone: {yes, no, omit}) — any other lexical form, including true/false/1/0, is an SEPM0016 static error
(W3C output-0197/0198/0199/0280/0281/0282/0283, all version="2.0"). Uppercase forms (TRUE/YES) are invalid in
every version (xs:boolean is lowercase-only). An absent/unparseable version defaults to 3.0 (permissive).
The core is XPath 1.0 compatibility mode, a runtime flag on the xpath3 evaluator (Evaluator.XPath10Compat(),
default off, so xsd/relaxng/schematron and ordinary xpath3/xslt3 are unchanged). xslt3 records every expression compiled
under an effective version < 2.0 by pointer identity (Stylesheet.compatExprs, set in compiler.compileXPath) and
evaluates it in compat mode (execContext.evalXPath/withCompat). XPath 1.0 mode (internal xpath10_compat.go): a
single-item function parameter given >1 items keeps only the first; an xs:string(?) parameter coerces via fn:string
and an xs:double(?)/xs:numeric parameter via fn:number (invalid/empty → NaN); arithmetic operands convert to
xs:double (÷0 → ±INF); general comparisons apply the 1.0 boolean/numeric/string rules, and the relational operators
(<,<=,>,>=) always convert both operands to number. Functions/operators that bypass signature coercion
(format-number value+picture, subsequence position/length, string-join separator, fn:number-family node args,
the to range operator) consult the flag directly. Runtime-compiled expressions not in compatExprs are covered by a
context flag instead: match-pattern predicates (execContext.patternCompat, set from pattern.compat at the match
entry, honored in evalXPath and the predicate evaluators) and xsl:evaluate's dynamic expression (compat when its
static xpath attribute is compat-marked). Backwards-compatible processing is NOT applied to the compile-time static
context — use-when, static="yes" variables/params, and shadow attributes are evaluated version-independently.
XSLT-level 1.0 behaviors (keyed off the compat-marked expression or an instruction Compat flag): xsl:value-of and
AVTs discard all but the first item; xsl:number/@value uses the first atom and outputs "NaN" for an empty or
non-integer value (no XTDE0980); xsl:sort uses the first sort-key item; xsl:call-template silently ignores a surplus
with-param (no XTSE0680); xsl:key/key() compare values as xs:string;
system-property('xsl:supports-backwards-compatibility') is "yes". Known gaps (skipped in expectations/xslt30.json
with specific reasons): the 1.0-only default output method (xhtml→xml for an implicit 1.0 result tree); base-uri()
fixture dependence; and XPath 1.0 grammar differences (div/mod as a name after an operator, unprefixed
function as a name test, empty function arguments) — compat mode changes semantics, not the grammar, so these stay out
of scope.
The spec="XSLT20"/spec="XSLT10" version-specific test bucket (~1120 cases) is in scope and un-gated:
specSupported in the helium-w3c-tests generator treats XSLT10/XSLT20 like their + forms, so a conformant 3.0
processor runs them. ~1015 pass as-is, plus 7 more from version-gating the xsl:output boolean serialization parameters
(below). The ~80 remaining are documented per-case in the w3cImplicitSkips map (helium-w3c-tests
xslt3/w3c_helpers_test.go), overwhelmingly as legitimate 2.0-vs-3.0 divergences where our 3.0 output is correct
and the case asserts a 2.0-only error a 3.0 processor no longer raises: 3.0-only regex constructs the 2.0 test expects
to reject with FORX0002; match/error pattern-syntax relaxations that dropped XTSE0340; xsl:sequence with a contained
sequence constructor, no longer XTSE0010; functions/arities added in 3.0/XPath 3.1, no longer XPST0017;
apply-templates/for-each select required-type errors XTTE0520/XTTE1120, both removed in 3.0 (a non-node population
is handled by the atomic built-in template rule / never matches a pattern); initial-entry conflict errors
XTDE0047/XTDE0060, removed in 3.0 (W3C bug 28418); current-group()/current-grouping-key() outside a grouping
context, which 3.0 makes a dynamic error XTDE1061/XTDE1071 where 2.0 gave the empty sequence; and a conflicting
xsl:strip-space/xsl:preserve-space at equal precedence/priority, a RECOVERABLE error in 1.0/2.0 but a STATIC error
XTSE0270 in 3.0 (helium correctly raises XTSE0270 via compile_formats.go checkSpaceConflicts; the 3.0
counterpart strip-space-019a passes); and format-date/format-time fractional-second handling, where XPath 3.1
truncates but the 2.0 case asserts rounding (the 3.0+ variant passes). The genuine-gaps list is now essentially
empty: every residual skip in the XSLT20/XSLT10 bucket is a legitimate 2.0-vs-3.0 divergence (a correct-skip where our
3.0 output is right), not a missing mandatory Basic 3.0 facility. xsl:output boolean serialization parameters are
version-gated (compile_formats.go serializationYesNoOnly): under an effective XSLT version < 3.0 they accept only
yes/no (raising SEPM0016 otherwise, per Serialization 1.0), while 3.0 keeps the full xs:boolean value space. Do
NOT implement XSLT 1.0/2.0 syntax support — compat mode changes semantics, not the grammar.
- NEVER modify generated files by hand. Regenerate through the owning generator (e.g.
go generate).
Read the linked doc BEFORE working in that area. No exceptions.
| Trigger | Doc |
|---|---|
| Package purpose, API, files | .claude/docs/packages.md |
| Cross-package imports | .claude/docs/dependencies.md |
Working with context.Context, package Context payloads, carrier/accessor patterns |
.claude/docs/context.md |
| Feature status, test counts, known gaps, ParseOption support | .claude/docs/libxml2-parity.md |
| Writing/running tests, golden files, test data, helpers | .claude/docs/testing.md |
Writing/editing examples/, example scope, example comments |
.claude/docs/testing.md |
| Error types, format strings, ErrorHandler, ValidateError | .claude/docs/error-formatting.md |
| Parse pipeline, encoding, entities, SAX→DOM, push parser | .claude/docs/parser-internals.md |
| DOM node hierarchy, struct fields, namespace/attr storage | .claude/docs/node-types.md |
| XSD/RELAX NG/Schematron compile→validate flow | .claude/docs/validation-pipeline.md |
XSD 1.1 feature state (Version11), any work in xsd/ — index → area sub-docs |
.claude/docs/xsd11.md |
| XSD 1.1 types: assert, alternative/CTA, simpleContent, attr inheritance | .claude/docs/xsd11-types.md |
| XSD 1.1 content models: UPA, open content, xs:all, wildcards, restriction, backtracking, EDC | .claude/docs/xsd11-content-models.md |
| XSD 1.1 identity constraints (xs:key/unique/keyref) | .claude/docs/xsd11-identity-constraints.md |
| XSD 1.1 document-wide ID/IDREF/ENTITY walks | .claude/docs/xsd11-doc-walks.md |
| XSD 1.1 representation/structural/composition checks, xs:override, vc:, notation | .claude/docs/xsd11-representation.md |
| helium CLI commands, flags, pipeline, exit codes | .claude/docs/helium-command.md |
| Cutting a release, editing release/conformance workflows, bumping the harness pin | RELEASING.md |
| XPath 3.1 design overview, constraints, sub-doc index | .claude/docs/xpath3-design.md |
| XPath 3.1 architecture, file layout, internal/xpath | .claude/docs/xpath3-architecture.md |
| XPath 3.1 public API, Context, Result, errors | .claude/docs/xpath3-api.md |
| XPath 3.1 Item/Sequence/Map/Array type system | .claude/docs/xpath3-types.md |
| XPath 3.1 lexer, parser, AST nodes | .claude/docs/xpath3-parser.md |
| XPath 3.1 evaluator, comparison, casting | .claude/docs/xpath3-eval.md |
| XPath 3.1 function system, built-in categories | .claude/docs/xpath3-functions.md |
| Saxon-HE source layout (reference) | .claude/docs/saxon-layout.md |
These docs cache repository state. Still read source before modifying code.
- When your changes affect a doc below, update it in the same commit.
- If you notice any doc is wrong or stale — even on an unrelated task — fix it immediately.
- Write current state, not history. These are caches of what the code is, not a changelog of how it got there. State what the code does now; never append "was X, now Y", "no longer / previously / used to", "the regression where…", "fixes #NNN", "round-N", or resolved "KNOWN RESIDUAL / deferred" notes — that framing rots into staleness the moment the state changes. When you fix a gap, delete its gap note (don't rewrite it to "now fixed"); when you change behavior, describe the new behavior in the present tense. Keep design rationale (spec citations, version-gating / byte-identical constraints, genuine current gaps) — that is current state; the journey belongs in git history.
| Doc | Update trigger |
|---|---|
packages.md |
Public API, package, or key file changes |
dependencies.md |
Inter-package import changes |
context.md |
context.Context conventions, package Context payload pattern, NewContext/GetContext guidance changes |
libxml2-parity.md |
Test count, parser limitation, feature, or ParseOption changes |
testing.md |
Test data layout, helper, env var, or test pattern changes |
error-formatting.md |
Error format, error type, or ErrorHandler changes |
parser-internals.md |
Parse pipeline, encoding, entity, SAX, or parserCtx changes |
node-types.md |
Node type, struct field, or tree operation changes |
validation-pipeline.md |
Compile/validate phase, data model, or backtracking changes |
xsd11.md (+ xsd11-*.md sub-docs) |
XSD 1.1 feature, gating, gap, or version-resolution changes — update the sub-doc for the affected area; keep the index table in sync |
helium-command.md |
CLI command, flag, pipeline, or exit code changes |
xpath3-design.md |
Design constraints, sub-doc structure changes |
xpath3-architecture.md |
Package layout, file additions/removals, import graph changes |
xpath3-api.md |
Public API, Context, Result, error type changes |
xpath3-types.md |
Item/Sequence/AtomicValue/Map/Array type changes |
xpath3-parser.md |
Lexer, parser, AST node, token type changes |
xpath3-eval.md |
Evaluator, comparison, casting logic changes |
xpath3-functions.md |
Function registry, built-in function additions/changes |
saxon-layout.md |
Reference layout updates |