Working draft for incremental chapter checkpoints
Table of Contents
Table of Contents
SaxonForms implements XForms behavior using Interactive XSLT 3.0 and SaxonJS. This guide maps the XForms 1.1 specification to concrete runtime structures in the SaxonForms so developers can understand behavior, locate implementation points, and validate conformance incrementally.
SaxonForms is an implementation of XForms built with Interactive XSLT 3.0 and SaxonJS. It runs in the browser and is authored primarily in XSLT, using SaxonJS extension mechanisms for event handling and DOM updates.
The project started from a practical need: rewriting Saxonica's internal license-management application so more processing could move into XSLT and into the browser, with less Java-specific application code.
A key design goal is integration with application logic. For example, the form pipeline can be combined with custom XSLT logic to parse incoming order text, populate instance data, apply business rules during edits, and handle submission responses in-application.
Historical context and implementation details are discussed in the presentation at https://www.youtube.com/watch?v=GWvl7EhsocI.
The guide follows the XForms 1.1 specification structure, while focusing on how SaxonForms currently implements those features:
SaxonForms and SaxonJS — Runtime boundaries and Interactive XSLT integration points.
Processing Model — Initialization, event flow, and update cycle behavior.
Data Types and MIPs — XSD support and model item property handling.
XPath and Controls — Expression handling and control-to-HTML rendering.
Actions and Submission — Action execution and HTTP submission mechanisms.
Conformance — Test coverage and implementation-defined behavior.
Additional Topics — Reusable components, accessibility, BPMN workflows, XSLT API reference.
The use-case diagram enumerates the implementation-facing scenarios that the rest of the guide traces back to source templates, functions, and Playwright assertions. Four actor roles interact with SaxonForms:
Form Author — Writes XForms markup using standard controls and XPath bindings. Relies on correct rendering and function evaluation.
Application Developer — Integrates SaxonForms into web applications, using actions (insert, delete, toggle, script, load), submission, and file upload.
End User — Interacts with rendered forms, triggering lifecycle events and control updates through the DOM event bridge.
QA Engineer — Validates conformance using the W3C Playwright test suite
(e.g. tests/xforms/w3c/ch02–ch11, appendix) and
regression gate.
The following terms are used consistently throughout this guide:
The Saxonica Interactive XSLT 3.0 engine for the browser. Provides the ixsl:* extension functions for DOM access, event handling, and JavaScript interop.
Stylesheet Export File — a compiled XSLT representation that SaxonJS loads and executes. Built from saxon-xforms.xsl via xslt3 compiler.
A SaxonJS execution mode where XSLT templates respond to DOM events (e.g., ixsl:onclick, ixsl:onchange) and modify the page in place.
An XML document bound to an xforms:model that stores form data. Identified by @id and accessed via xforms:instance().
An xforms:bind element associating an XPath nodeset with model item properties (MIPs).
Model Item Property — attributes on xforms:bind that control node behavior: readonly, relevant, required, constraint, calculate, type.
The sequence recalculate → revalidate → refresh that runs after user interactions or action execution.
An in-memory map(*) built by the set-action mode template in src/saxon-xforms.xsl that stores parsed action metadata for runtime dispatch.
Implementation hook references in this guide use symbol names plus source files (for example, xformsjs-main in src/saxon-xforms.xsl) unless a different file is specified.
DocBook source validity and XInclude closure for this chapter.
Consistency between use-case language and component/package terms used in later chapters.
Verify terminology definitions match actual variable/template names in source.
User review required before proceeding to processing-model chapters.
Table of Contents
This chapter defines subsystem boundaries between browser APIs, the SaxonJS runtime, and SaxonForms XSLT packages. These boundaries are the anchor model for all subsequent diagrams and chapter discussions.
SaxonForms operates within three major subsystems: the browser environment (DOM, event loop, JavaScript runtime), the SaxonJS 3 Interactive XSLT engine, and the SaxonForms XSLT runtime itself. The component diagram below is the anchor model for all other diagrams — package, sequence, and class representations stay consistent with these component boundaries.
Provides the HTML/DOM tree that SaxonForms renders into, the DOM event loop that delivers native events (onclick, onchange, onkeyup), and the JavaScript runtime used for operations that require JS interop: HTTP fetch via ixsl:promise, file reading via readFileAsXML(), focus management via setFocus(), and script evaluation.
The Interactive XSLT engine that loads the compiled SEF transform and executes it. SaxonJS provides the ixsl:* extension namespace: ixsl:page() for DOM access, ixsl:event() for the current event object, ixsl:call()/ixsl:set-property() for JavaScript interop, and ixsl:promise for async operations. It also provides the mode-based event dispatch mechanism (e.g., mode="ixsl:onclick").
The XSLT packages that implement XForms behavior. This subsystem contains the model state (instances, binds, MIPs), action dispatcher, submission adapter, function library, and XSD helpers. All XForms-specific logic resides here.
The XSLT source is compiled offline into a SEF file (builds/saxon-xforms.sef.xml or sef/saxon-xforms.sef.xml) using the xslt3 command-line compiler from Saxonica. At runtime, a host HTML page loads SaxonJS and invokes SaxonJS.transform2() with the SEF location and the XForms document as input. This triggers the xformsjs-main named template in src/saxon-xforms.xsl, which is the entry point for all SaxonForms processing.
The xformsjs-main template reads the page DOM via ixsl:page(), locates the XForms source document (either embedded or linked), resolves namespace declarations via addNamespaceDeclarationsToDocument() in src/saxon-xforms.xsl, extracts models and instances, builds the submission map, and initiates the model construction lifecycle.
The package map shows the organization of XSLT source files and their relationships. All packages are consumed by saxon-xforms.xsl via xsl:use-package.
The main transform. Contains the entry point (xformsjs-main), model lifecycle templates (xforms-model-construct, xforms-rebuild, xforms-recalculate, xforms-revalidate, xforms-refresh), control rendering templates (get-html mode), action dispatch (applyActions, outermost-action-handler, all action-* named templates), submission logic (HTTPsubmit, xforms-submit), and DOM event handler templates (ixsl:on* modes).
Core runtime functions: xforms:impose() (XPath rewriting), xforms:instance(), xforms:index(), xforms:id(), xforms:min()/max(), xforms:random(), xforms:context(), xforms:if().
XForms specification functions: property(), boolean-from-string(), avg(), event(), date/time functions, cryptographic stubs (digest(), hmac()).
XSD type validation: xsdh:canonical-type() normalizes type QNames, xsdh:is-type-valid() validates values against XSD types.
Optional logging package with configurable log levels (sfp:logInfo, sfp:logDebug). Controlled by the $LOGLEVEL parameter.
Table of Contents
This chapter maps lifecycle events and deferred processing behavior to SaxonForms runtime dispatch, recalculate/revalidate/refresh orchestration, and DOM updates.
The XForms processing model defines a lifecycle that begins with model construction and culminates in a ready state where the form is interactive. SaxonForms implements this lifecycle through named templates in saxon-xforms.xsl that execute in sequence during the xformsjs-main entry point template.
The xformsjs-main template in src/saxon-xforms.xsl orchestrates model construction:
Document parsing — The XForms source is read from the page DOM via ixsl:page(). The source may be an embedded xforms:xform element or an XHTML document with XForms in the <head>. Namespace declarations are propagated via addNamespaceDeclarationsToDocument() in src/saxon-xforms.xsl.
Model and instance resolution — Models ($models) and the default instance ($first-instance) are extracted in xformsjs-main. Default IDs are assigned if not present using $global-default-model-id and $global-default-instance-id.
Submission map construction — The $submissions variable in xformsjs-main builds a map(xs:string, map(*)) of all xforms:submission elements, keyed by ID, containing method, resource, serialization, replace mode, and other attributes.
xforms-model-construct — The xforms-model-construct template in src/saxon-xforms.xsl initializes each model: loads instance data (inline or from @src), saves initial instance snapshots for xf:reset, and stores instances in JavaScript global state.
xforms-rebuild — The xforms-rebuild template processes xforms:bind elements via the add-context mode in src/saxon-xforms.xsl, resolving @nodeset paths to absolute references and annotating binds with @instance-context.
xforms-recalculate — The xforms-recalculate template evaluates all @calculate, @relevant, @readonly, @required, and @constraint expressions on binds via xforms-recalculate-binding.
Validation stage boundary — In the default construction path, SaxonForms performs xforms-rebuild and xforms-recalculate before dispatching xforms-model-construct-done. xforms-revalidate runs during deferred update cycles and submission validation rather than as a mandatory construction step.
xforms-model-construct-done — Dispatched after construction completes. Action handlers bound to this event (e.g., setvalue on xforms-model-construct-done) execute here. Known issue: event handler execution during model construction does not always update instance values (4.2.2.b).
Control rendering — XForms control elements are matched by the control-entry templates in src/saxon-xforms.xsl and transformed to HTML. The get-properties mode resolves data bindings for each control. The set-action mode builds action maps. The registerOutput template registers output controls for refresh tracking.
xforms-ready — Dispatched when the form is fully initialized and rendered. The form enters the interactive state.
After each user interaction or action execution, SaxonForms runs the deferred update cycle: recalculate → revalidate → refresh. This cycle is triggered by:
xforms-value-changed in src/saxon-xforms.xsl — Fired when a form control value changes via the ixsl:onchange event handlers.
Action execution — Insert, delete, and setvalue actions trigger recalculation after modifying instance data.
Explicit dispatch — The action-recalculate, action-revalidate, and action-refresh templates in src/saxon-xforms.xsl handle explicit xf:recalculate/xf:revalidate/xf:refresh actions.
The recalculate phase (xforms-recalculate) iterates binds and evaluates MIP expressions. The xforms-recalculate-binding template evaluates each bind's @calculate expression and updates the instance node value if changed. For non-incremental <select>/<select1> controls, the deferred cycle runs inline during onchange via dedicated ixsl:onchange handlers to avoid duplicate outer dispatch (resolved: 4.6.3.a/b/c).
The revalidate phase (xforms-revalidate) checks required fields, constraint expressions, and XSD type validity. During each cycle, validation MIP state (valid/required) is rebuilt into a runtime registry keyed by instance ID and binding reference, and validation state changes dispatch xforms-valid/xforms-invalid events (resolved: 6.1.6.a).
The refresh phase (xforms-refresh) updates the DOM to reflect current model state: refreshOutputs-JS updates output/control values and projects validation CSS classes from the revalidate registry, refreshRepeats-JS regenerates repeat content, refreshRelevantFields-JS shows/hides controls based on relevant MIP, and refreshElementsUsingIndexFunction-JS updates elements whose values depend on index().
Table of Contents
This chapter documents data typing support (including XSD-derived constraints) and the mapping between XForms model item properties and runtime state propagation into rendered controls.
SaxonForms provides partial XSD type validation through the xsd-helpers.xsl package (~148 lines). Type validation occurs during the revalidate phase via check-instance-xsi-types() in src/saxon-xforms.xsl.
The xsdh:canonical-type() function in src/xsd-helpers.xsl normalizes type QNames to a canonical form. It handles the xs: and xsd: prefixes and maps type names to their XSD 1.1 canonical equivalents. This normalization ensures consistent type comparison regardless of prefix usage in the XForms source.
The xsdh:is-type-valid() function in src/xsd-helpers.xsl validates a string value against an XSD type name. It supports the common XSD simple types: xs:string, xs:integer, xs:decimal, xs:double, xs:float, xs:boolean, xs:date, xs:dateTime, xs:time, xs:anyURI, and others. Validation uses castable as expressions where possible.
Limitation: The type MIP on xforms:bind is parsed but not fully enforced — the bind @type attribute is recognized but instance data is not rejected based on type mismatch during the revalidate phase. This affects 6 W3C tests related to schema type validation.
XPath 3.1 is stricter about type coercion than XForms expects. SaxonForms wraps calculate and relevant expressions in try/catch blocks to handle empty-string arithmetic crashes (e.g., '' + 1 throws in XPath 3.1 but should produce NaN in XForms). This was resolved for tests 6.1.5.a and 6.1.4.b.
Similarly, the xforms:min() and xforms:max() wrappers in src/xforms-function-library.xsl return NaN instead of throwing XPath 3.1 type errors when applied to non-numeric sequences, matching XForms semantics (resolved: 7.7.2.b, 7.7.3.b).
MIPs are defined on xforms:bind elements within xforms:model. During the rebuild phase, binds are processed by add-context mode in src/saxon-xforms.xsl to resolve their @nodeset to absolute instance paths. During recalculate, xforms-recalculate-binding evaluates each MIP expression.
The @readonly MIP expression is evaluated as a boolean. When true(), SaxonForms sets the HTML disabled/readonly attribute on the corresponding control. Readonly propagates by inheritance: if a parent bind is readonly, child binds inherit readonly status. Resolved: 6.1.2.a, 6.1.2.b (readonly MIP enforcement now propagates to HTML inputs, including inheritance from ancestor bindings).
The @relevant MIP expression is evaluated as a boolean. When false(), the bound control is hidden from the DOM. The refreshRelevantFields-JS and getRelevantStatus templates in src/saxon-xforms.xsl handle the show/hide logic during the refresh phase. Controls within an irrelevant group or case are also hidden.
The @required MIP expression is evaluated as a boolean. When true(), the control is marked with a CSS class (via getHtmlClass in src/saxon-xforms.xsl) and empty values cause the node to be invalid. Validation is performed by check-required-fields() and check-required-bindings(). During revalidate, required state is also persisted in the validation MIP registry and then projected in refresh as xforms-required/xforms-optional class tokens.
The @constraint MIP is an XPath boolean expression evaluated against the bound node. When the constraint evaluates to false(), the node is invalid and xforms-invalid is dispatched. Validation is performed by check-constraints-on-fields() and check-constraints-on-bindings() in src/saxon-xforms.xsl. Constraint validation events (xforms-valid/xforms-invalid) now dispatch after setvalue changes constraint state (resolved: 6.1.6.a). Revalidate persists resulting validity in the validation MIP registry, and refresh projects this as xforms-valid/xforms-invalid class tokens.
At the start of each xforms-revalidate cycle, the JavaScript-side validation MIP registry is cleared and rebuilt for controls carrying @data-ref and @instance-context. Entries are keyed by instance ID plus reference path and store both validity and required flags.
In refreshOutputs-JS, these registry values are read and merged with existing additional class values before class computation. This ensures validation/required class state survives explicit xf:revalidate followed by xf:refresh, and class updates are applied both to the control element and to its wrapper parent for input/select/textarea.
The @calculate MIP is an XPath expression whose result replaces the bound node's value on each recalculate cycle. Evaluation is handled by xforms-recalculate-binding using evaluate-xpath-with-context-node() in src/saxon-xforms.xsl. Calculate expressions containing position() or last() have actual nodeset position/size pre-substituted before evaluation (resolved: 7.2.e).
When a control references a bind (via @bind) or a nodeset (via @ref/@nodeset), the get-properties mode template in src/saxon-xforms.xsl resolves the binding to a concrete instance node and matching bind element. The resolution process:
If @bind is present, look up the bind element by ID using $binding-referenced-by-id.
Resolve the model context via $context-model — either from the bind's parent model or the default model.
Resolve the instance context via $instance-context — extracted from the nodeset expression by xforms:getInstanceId().
If no explicit bind, match by nodeset: evaluate both the control's @ref and each bind's @nodeset against the instance, and compare resulting nodes via $binding-matching-nodeset.
During required/constraint/type validation checks, control-bound references are resolved with instance awareness: when a control carries @instance-context, SaxonForms evaluates @data-ref through xforms:evaluate-xpath-with-instance-id(); otherwise it falls back to context-node evaluation against the active instance root.
Table of Contents
This chapter maps XForms XPath semantics to XPath 3.1 execution in SaxonForms and explains control rendering and interaction patterns for standard and custom controls.
SaxonForms translates XForms XPath expressions into XPath 3.1 at evaluation time through a function-rewriting pipeline. The central rewriting function is xforms:impose() in src/xforms-function-library.xsl, which rewrites XForms function calls to their xforms:-prefixed XSLT equivalents so SaxonJS can evaluate them natively.
When a binding expression or calculate attribute is evaluated, xforms:impose() performs string-level substitution of XForms function names to their xforms: namespace counterparts. For example, instance('foo') is rewritten to xforms:instance('foo'), which resolves to the XSLT function xforms:instance() in src/xforms-function-library.xsl.
The xforms:resolve-index() function in src/xforms-function-library.xsl handles repeat index substitution: expressions containing index('repeatId') are replaced with the current integer index value before evaluation. This avoids runtime resolution issues where index() would need repeat context that is unavailable in a static XPath 3.1 call.
XPath evaluation is dispatched through xforms:evaluate-xpath-with-context-node() in src/saxon-xforms.xsl, which accepts the expression string, a context node from the bound instance, and the namespace context for prefix resolution. The related xforms:evaluate-xpath-with-instance-id() function resolves the instance element first.
Within repeats, context resolution relies on positional information. The xforms:resolveXPathStrings() function in src/saxon-xforms.xsl resolves relative paths against the current repeat context node. The position()/last() substitution (resolved status: 7.2.e) pre-computes positional values in bind calculate expressions before evaluation.
Functions are split across two XSLT packages:
xforms-function-library.xsl (~300 lines): Core runtime functions — instance(), index(), id() (three overloads: fn:id, @xml:id, and xsi:type ID), min()/max() (NaN-safe wrappers), random(), context(), if().
xforms-xpath-functions.xsl (~420 lines): Specification-defined functions — property(), boolean-from-string(), count-non-empty(), power(), choose(), avg(), is-card-number() (Luhn algorithm), event(), date/time functions (now(), local-date(), local-dateTime(), days-from-date(), days-to-date(), seconds-from-dateTime(), seconds-to-dateTime(), seconds(), months(), adjust-dateTime-to-timezone()), and cryptographic stubs (digest(), hmac() — delegated to JS crypto.subtle).
position()/last() in bind calculate — Pre-substituted with actual nodeset position and size before evaluation (resolved: 7.2.e).
current() in repeat — Output @value starting with instance(...) no longer short-circuits $instanceField inside repeats (resolved: 7.10.2.b).
id() routing — Added to function rewriting; supports fn:id, @xml:id, and both W3C xsi namespace URIs (resolved: 7.10.3.a/b/c).
Type coercion in calculate/relevant — Wrapped in try/catch to handle empty-string arithmetic crashes under XPath 3.1 strict typing (resolved: 6.1.5.a, 6.1.4.b).
min()/max() negative tests — Return NaN instead of throwing XPath 3.1 type errors (resolved: 7.7.2.b, 7.7.3.b).
SaxonForms maps each XForms control element to an HTML element via get-html mode templates. The control rendering entry point is the control-match template in src/saxon-xforms.xsl, which processes all XForms control elements and delegates to get-html mode for HTML output generation.
The diagram below maps each XForms control to its HTML output element and the DOM event handlers that wire interactive behavior back into the action dispatch pipeline.
Each core control template resolves its data binding via the get-properties mode template in src/saxon-xforms.xsl, which returns a map containing the bound nodeset, instance context, and matching bind element. Key control templates:
xforms:input template in src/saxon-xforms.xsl — Renders <input> with type derived from XSD type (xs:date → type="date", xs:boolean → type="checkbox"). Supports incremental mode via ixsl:onkeyup handlers.
xforms:output template in src/saxon-xforms.xsl — Renders <span> with computed value. Supports @mediatype for image rendering and @value attribute for XPath expression evaluation.
xforms:select/xforms:select1 templates in src/saxon-xforms.xsl — Render <select> (multi/single). Handle xforms:item, xforms:choices, and xforms:itemset child elements. Non-incremental select uses blur/focusout handlers for deferred update.
xforms:textarea template in src/saxon-xforms.xsl — Renders <textarea>.
xforms:range template in src/saxon-xforms.xsl — Renders <input type="range">. Incremental mode uses ixsl:oninput handlers.
xforms:upload template in src/saxon-xforms.xsl — Renders <input type="file">. File content is parsed via readFileAsXML() in xforms-javascript-library.xsl and inserted into the bound instance node.
xforms:trigger template in src/saxon-xforms.xsl — Renders <button> or <a> with data-action attribute. Onclick dispatches DOMActivate.
xforms:submit template in src/saxon-xforms.xsl — Renders <button data-submit>. Onclick triggers submission flow.
xforms:group template in src/saxon-xforms.xsl — Renders <div class="xforms-group">. Supports @ref for scoped context and relevant hiding.
xforms:repeat template in src/saxon-xforms.xsl — Renders iterable <div data-repeatable-context> with data-repeat-item children. Repeat index is tracked and updated on click events. Refresh is handled by refreshRepeats-JS.
xforms:switch/xforms:case templates in src/saxon-xforms.xsl — Render <div class="xforms-switch"> with cases shown/hidden. The action-toggle template controls case visibility. Known issue: toggle inside repeat affects all iterations (9.3.1.f).
The fork adds several controls and behaviors not present in upstream Saxon-Forms:
xf:script action (action-script template in src/saxon-xforms.xsl) — XForms 2.0 script execution via ixsl:call(window, 'eval', ...).
xf:load action (action-load template in src/saxon-xforms.xsl) — Supports show="new", show="replace", and javascript: URI scheme.
Namespace prefix propagation — XPaths can use prefixes declared on <xf:xform> (e.g., o:catalog instead of *:catalog). Implemented via addNamespaceDeclarationsToDocument() in src/saxon-xforms.xsl.
Table of Contents
This chapter documents action dispatch semantics, context resolution, and submission processing behavior including request serialization, headers, and response handling.
When a DOM event fires (e.g., button click), the corresponding ixsl:onclick mode template in src/saxon-xforms.xsl routes to applyActions. This template retrieves the action map for the control and invokes outermost-action-handler, which processes the outermost action container. For nested actions, xforms-event-handler handles recursive dispatch.
Event-action matching uses get-matching-event-actions(), which filters action maps by event name and observer ID. Event default cancellation is checked via is-event-default-cancelled().
During control rendering, the set-action mode template in src/saxon-xforms.xsl builds an action map (map(*)) for each action element. The map stores:
name — The action type (setvalue, insert, delete, toggle, dispatch, send, etc.).
ref — The effective nodeset reference, resolved via get-properties mode.
instanceContext — The instance ID the action operates on.
if/while/iterate — Guard conditions from @if, @while, @iterate attributes.
bindRef — Optional @bind reference for bind-based action resolution.
Action-map persistence now keeps bind metadata, and insert/delete resolve local context first with absolute-ref fallback (resolved: 10.4.b, 10.18.b).
Evaluates the @value expression or uses element text content to set the bound instance node. Delegates to action-setvalue-inner for XPath evaluation and to action-setvalue-form-control for DOM control update.
Clones and inserts nodes into the instance. Supports @origin, @at, @position (before/after), and @context attributes. Uses insert-node mode for DOM-level insertion. Known issues: multi-instance @context resolution (10.3.a/c), @bind/@model crash (10.3.b), @at position evaluation (10.3.d, 10.4.d).
Removes nodes from instances. Uses delete-node mode. Known issues: post-delete repeat index tracking (10.4.e/f).
Toggles visibility of xforms:case elements within a xforms:switch. The $source-control scoping mechanism exists in src/saxon-xforms.xsl but is not always passed through the event dispatch chain. Known issues: toggle inside repeat affects all iterations (9.3.1.f), case child @value precedence (10.6.1.b).
Dispatches a named event to a target, supporting @targetid, @bubbles, @cancelable, and @delay. Known issue: cancelable="true" + ev:preventDefault does not prevent default action (10.8.f).
Displays a modal message via alert(). Known issue: model-level event messages (xforms-rebuild, xforms-reset, etc.) do not invoke the modal (10.13.a, 10.8.1.a/b, 8.1.8.a, 8.2.2.a–c, 8.2.3.a–c).
Triggers submission by looking up the submission map and invoking the submission flow.
Sets the repeat index for a given repeat ID.
Sets focus to a control. Enhanced with suffixed-ID fallback and group→first-child focus (resolved: 9.1.1.c, 10.7.a).
Restores instance data from initial snapshots saved during model construction (resolved: 10.a).
Explicitly trigger the corresponding lifecycle phase. action-revalidate invokes xforms-revalidate (including validation MIP registry rebuild used by refresh class projection) and clears the deferred revalidate flag.
Fork enhancement: XForms 2.0 script execution via ixsl:call(window, 'eval', ...).
Fork enhancement: Supports show="new" (window.open), show="replace" (location.href), and javascript: URI scheme.
The following sequence shows the event-to-action resolution pipeline and execution flow for each action type.
Submission is initiated by action-send, which looks up the submission configuration from the $submissions map built during xformsjs-main. The xforms-submit template in src/saxon-xforms.xsl orchestrates the full submission lifecycle:
Pre-submit validation — Runs xforms-revalidate to check required fields and constraints. If validation fails, dispatch-submit-error fires.
Serialization — Instance data is serialized based on @serialization (default: application/xml). The setSubmission template configures the submission parameters.
Transport — xforms-submit builds an HTTP request map and schedules transport via ixsl:schedule-action. The response callback is handled by HTTPsubmit, which normalizes status/body/headers before dispatching submit outcome events.
Response handling — Response headers are parsed by response-headers-to-nodes(). Based on @replace: "instance" replaces instance data, "all" replaces page content, "none" dispatches xforms-submit-done.
Post-submit update — After instance replacement, xforms-rebuild/xforms-recalculate/xforms-refresh are triggered.
11.1.b — submission/@bind is parsed but does not filter submitted instance data to the bound node; the entire instance is sent.
11.3.b — xforms-submit-serialize event's submission-body property is not honored; original instance data is submitted.
11.8.1.a, 11.8.2.a — Custom HTTP headers defined via xforms:header/xforms:name/xforms:value child elements are not added to the outgoing request.
Table of Contents
This chapter records conformance claims and known implementation-defined behavior. It is synchronized with the latest W3C test status (tests/xforms/w3c/STATUS.md) and regression gate outputs.
SaxonForms is validated against the W3C XForms 1.1 Test Suite via Playwright end-to-end tests. Current coverage as of the latest regression gate run:
| Chapter | Description | Tests | Pass | Rate |
|---|---|---|---|---|
| 5 | Datatypes | 15 | 11 | 73% |
| 6 | Model Item Properties | 11 | 9 | 82% |
| 7 | XPath Expressions | 62 | 18 | 29% |
| 8 | Core Form Controls | 59 | 53 | 90% |
| 9 | Container Form Controls | 21 | 15 | 71% |
| 10 | XForms Actions | 15 | 11 | 73% |
| B | Insert/Delete Patterns | 15 | 15 | 100% |
| Total | 198 | 132 | 67% |
Additionally, 24 fork-specific tests (14 feature tests + 10 issue regression tests) exist outside the W3C suite, with 22 passing.
Appendix B — Data Mutation Patterns (15/15, 100%): insert, delete, duplicate, replace, move for elements and attributes.
Core Form Controls (53/59, 90%): input, secret, textarea, output, upload, range, submit, trigger, select, select1.
Evaluation Context (7.2) (6/6, 100%): outermost/inner bindings, namespace scope.
Repeat (9.3.1) (6/6, 100%): basic repeat, startindex, unrolling, nested switch.
Key XPath functions: index(), compare(), if(), context(), event(), id().
XPath 3.1 type coercion — Calculate/relevant expressions are wrapped in try/catch to handle empty-string arithmetic. XForms expects NaN; XPath 3.1 throws a type error. SaxonForms catches and returns NaN.
Non-incremental select deferred update — Non-incremental <select>/<select1> controls perform inline recalculate/revalidate/refresh during onchange to avoid duplicate outer deferred-cycle dispatch. This deviates from the specification's strict deferred update model but produces correct observable behavior.
Namespace prefix propagation — Fork enhancement: XPaths can use prefixes declared on <xf:xform>. This is outside the XForms 1.1 specification.
ixsl:promise submission — Submission uses the browser fetch API via ixsl:promise rather than XMLHttpRequest. Behavior is equivalent for standard HTTP methods but may differ for edge-case transport scenarios.
SaxonJS engine version — Runtime behavior depends on the SaxonJS version. The fork targets SaxonJS 3.
Browser environment — DOM event ordering and JavaScript runtime behavior vary across browsers. Tests are run in Chromium via Playwright.
Crypto functions — digest() and hmac() require crypto.subtle, which is only available in secure contexts (HTTPS or localhost).
The following gap categories are tracked in tests/xforms/w3c/STATUS.md with root cause analysis:
Unimplemented XForms functions (35 test failures) — Functions referenced in the spec but previously unimplemented. Many have been resolved in the fork (property, boolean-from-string, avg, etc.); remaining gaps include current() in repeat context (issue #20).
Insert/delete action semantics (13 tests) — Root causes include model-level xforms-ready handler unreliability, multi-instance @context resolution, @bind/@model crash, @at position evaluation, and post-delete index tracking.
Model-event message dispatch (10 tests) — xforms:message elements listening for model-level events do not produce modal dialogs.
Multi-model support (7 tests) — Multiple <xf:model> elements crash during rendering due to default instance resolution and cross-model context switching bugs.
Schema type validation (6 tests) — xf:bind/@type is parsed but not enforced.
Submission features (4 tests) — @bind filtering, xforms-submit-serialize event, and custom HTTP headers.
Processing model behavioral (3 tests) — Model construction event handler timing and lazy instance resolution.
The following gaps have been resolved in the fork and are documented here for traceability. Each resolution references the affected test IDs:
Readonly MIP enforcement (6.1.2.a/b, 7.2.f)
index() returns NaN for non-existent repeats (7.7.5.b)
XPath 3.1 type coercion try/catch (6.1.5.a, 6.1.4.b)
min()/max() NaN handling (7.7.2.b, 7.7.3.b)
Constraint validation events (6.1.6.a)
Switch/case selector scoping (9.1.1.a2, 9.1.1.b, 9.2.2.b/c)
Reset action with initial snapshots (10.a)
setfocus with suffixed-ID fallback (9.1.1.c, 10.7.a)
current() in repeat (7.10.2.b)
position()/last() in bind calculate (7.2.e)
adjust-dateTime-to-timezone() test fix (7.9.8.a)
Model-construction crash without instance (4.2.1.a/d, 4.2.2.a, 4.2.3.a, 4.5.2.a)
Non-incremental select sequencing (4.6.3.a/b/c)
Action context resolution for bind-based actions (10.4.b, 10.18.b)
id() function routing and implementation (7.10.3.a/b/c)
Chapter 2 submit selector alignment (2.1.a, 2.2.a, 2.3.a)
Use tests/xforms/w3c/STATUS.md as the source of truth for known gaps/resolutions.
Verify pass-rate table matches latest npm run test:gate output.
Verify no new W3C regressions in regression-gate evidence before chapter approval.
Review and justify any non-W3C waivers explicitly.
This chapter covers reusable components, accessibility, BPMN workflow diagrams, and the XSLT API reference.
SaxonForms contains several reusable template and function patterns that are used across controls, actions, and submissions. The class diagram below shows the logical structure of these runtime components.
The get-properties mode template in src/saxon-xforms.xsl is the central binding resolution mechanism. It accepts any XForms element and returns a map(*) containing the resolved nodeset, instance context, matching bind, and context model. This template is called by every control rendering template and every action template that needs to resolve a data reference.
The get-context-instance-id mode template in src/saxon-xforms.xsl determines which instance an XForms element refers to. It checks: (1) whether the element has a @bind that belongs to a specific model, (2) whether the element's nodeset expression references a specific instance via instance('id'), (3) whether the element's ancestor model has a default instance.
Two primary functions handle XPath evaluation against instance data:
xforms:evaluate-xpath-with-context-node() in src/saxon-xforms.xsl — Evaluates an XPath expression with a given context node and namespace context. Used for binding resolution, calculate evaluation, and control value computation.
xforms:evaluate-xpath-with-instance-id() in src/saxon-xforms.xsl — Resolves the instance root element first, then delegates to the context-node variant.
The get-field and set-field mode templates provide a uniform interface for reading from and writing to HTML form controls:
get-field — Specializations for *:input, *:select, *:textarea, and *[@data-xf-component] (web components) in src/saxon-xforms.xsl.
set-field — Specializations for *:input, *:select, *:textarea, and *[@data-xf-component] (web components) in src/saxon-xforms.xsl.
All accessors use the standard .value property, making them compatible with both native HTML controls and form-associated custom elements.
The refresh phase uses four named templates that update different categories of DOM elements:
refreshOutputs-JS in src/saxon-xforms.xsl — Re-evaluates and updates all registered xforms:output elements, and projects validation MIP classes (xforms-valid/xforms-invalid, xforms-required/xforms-optional) from the revalidate registry onto controls (including parent wrappers for input/select/textarea).
refreshRepeats-JS in src/saxon-xforms.xsl — Regenerates repeat item content when instance data changes.
refreshRelevantFields-JS in src/saxon-xforms.xsl — Shows/hides controls based on the relevant MIP.
refreshElementsUsingIndexFunction-JS in src/saxon-xforms.xsl — Updates elements whose values depend on index() function results.
SaxonForms provides a generic mechanism for binding arbitrary HTML custom elements (web components) to XForms instance data. Any non-XForms element that carries an @xforms:ref attribute is treated as a bound web component control and participates in the XForms processing model identically to native controls.
To bind a web component to instance data, add the xf:ref attribute (in the XForms namespace) pointing to the target node. All non-XForms attributes are passed through to the rendered HTML element unchanged:
<tinymce-editor
xf:ref="instance('data')/htmlContent"
on-Change="xfComponentChanged"
height="300"
menubar="false"
toolbar="bold italic | bullist numlist"
plugins="lists"
license-key="gpl"
/>The mechanism is not specific to any particular web component. Any custom element that exposes a .value property can be bound.
The following attributes in the XForms namespace (http://www.w3.org/2002/xforms) are recognized on web component elements:
xf:refXPath binding expression pointing to the instance node whose value the component manages. Resolved using the same algorithm as native XForms controls (context model, parent nodeset, instance resolution).
xf:event (optional)Name of the DOM event that signals a value change. Defaults to "change". If the web component dispatches a non-standard event (e.g. "input" or a custom event name), specify it here so the Saxon-Forms JavaScript bridge can translate it into a native change event for the XSLT event handler.
The template at priority 10 matching *[not(self::xforms:*)][exists(@xforms:ref)] processes bound web components. During rendering:
All non-XForms attributes are copied to the output element unchanged.
Saxon-Forms binding attributes are injected: id, data-ref, instance-context, data-xf-component="true", data-xf-change-event.
Constraint, relevant, and required MIP attributes are added when a matching xf:bind exists.
The initial value from the bound instance node is emitted as the element's text content (web components typically read textContent for initial value).
The element is registered via registerOutput so that refreshOutputs-JS can update its value during the refresh phase.
Two mode templates provide the DOM ↔ model interface for web components:
get-field matching *[@data-xf-component] — reads the element's .value property via ixsl:get(., 'value').
set-field matching *[@data-xf-component] — writes to the element's .value property via ixsl:set-property.
These templates rely on the web component implementing the standard value property getter/setter contract (the same contract used by form-associated custom elements).
When the web component dispatches a native change event on the host element, SaxonJS matches it with the ixsl:onchange mode template for *[@data-xf-component]. This template invokes action-setvalue-form-control followed by outermost-action-handler, triggering the standard XForms processing cycle (recalculate → revalidate → refresh).
The JavaScript library provides runtime support for web components that do not natively dispatch DOM change events:
xfComponentObserverA MutationObserver that watches for elements with data-xf-component being added to the DOM. For each new element, if data-xf-change-event names an event other than "change", the observer registers a listener that translates that event into a native change event dispatch on the host element.
xfComponentChanged(evt)A global bridge function intended for use with web components that expose attribute-based event callbacks (e.g. TinyMCE's on-Change attribute). When called, it locates the host custom element via the shadow DOM (evt.target.getContainer().getRootNode().host) and dispatches a native change event on it.
The following demonstrates binding the TinyMCE web component to an OSCAL part element in an XForm:
<xf:xform xmlns:xf="http://www.w3.org/2002/xforms"
xmlns:o="http://csrc.nist.gov/ns/oscal/1.0">
<xf:model>
<xf:instance id="data">
<catalog xmlns="http://csrc.nist.gov/ns/oscal/1.0">
<control id="ac-1">
<part name="statement"><p>Account lockout...</p></part>
</control>
</catalog>
</xf:instance>
</xf:model>
<!-- Rich text editing of the part content -->
<tinymce-editor
xf:ref="o:p"
on-Change="xfComponentChanged"
height="200"
menubar="false"
toolbar="undo redo | bold italic underline | bullist numlist"
license-key="gpl"
skin="oxide"
/>
</xf:xform>Host page requirements:
Import tinymce and expose it globally (window.tinymce) so the web component can find the editor core without loading from CDN.
Import @tinymce/tinymce-webcomponent to register the <tinymce-editor> custom element.
Set tinymce.baseURL to point to the directory containing TinyMCE skins, themes, and icons.
For a web component to work with this binding mechanism, it must satisfy:
Expose a .value property (getter returns current content, setter updates content).
Read initial content from the element's textContent when connected to the DOM.
Either dispatch a native change event on the host element when content changes, OR provide an attribute-based callback mechanism (like on-Change) that can invoke the xfComponentChanged global bridge function.
SaxonForms generates standard HTML form controls, which inherit the browser's native accessibility support. The following aspects are relevant:
Since SaxonForms renders standard HTML <input>, <select>, <textarea>, and <button> elements, keyboard tab navigation works natively. The action-setfocus template in src/saxon-xforms.xsl supports programmatic focus management, including suffixed-ID fallback and group→first-child focus delegation.
The xforms:label template in src/saxon-xforms.xsl renders labels associated with controls. xforms:hint is matched but currently suppressed (empty template). The action-message template handles xforms:message display via alert(), though model-level message dispatch has known issues (10.13.a, 8.2.2.a–c, 8.2.3.a–c).
The getHtmlClass template in src/saxon-xforms.xsl computes CSS classes for controls based on MIP state: xforms-readonly, xforms-required, xforms-valid/xforms-invalid, xforms-enabled/xforms-disabled. These classes enable CSS-based visual feedback without additional JavaScript.
This section provides a summary of the public API surface of SaxonForms — the named templates and functions that form the implementation contract.
xformsjs-main (template in src/saxon-xforms.xsl) — Entry point. Parses XForms source, builds submission map, initiates model construction.
xforms-model-construct (template in src/saxon-xforms.xsl) — Initializes model instances and binds.
xforms-rebuild (template in src/saxon-xforms.xsl) — Processes bind elements, resolves nodesets.
xforms-recalculate (template in src/saxon-xforms.xsl) — Evaluates MIP expressions on all binds.
xforms-revalidate (template in src/saxon-xforms.xsl) — Validates required fields, constraints, XSD types.
xforms-refresh (template in src/saxon-xforms.xsl) — Updates DOM to reflect model state.
applyActions (template in src/saxon-xforms.xsl) — Main action dispatch entry point.
action-setvalue, action-insert, action-delete, action-toggle, action-dispatch, action-message, action-send, action-setindex, action-setfocus, action-reset, action-recalculate, action-revalidate, action-refresh, action-script, and action-load templates in src/saxon-xforms.xsl.
xforms:impose($xpath) in src/xforms-function-library.xsl — Rewrites XForms function calls to xforms: namespace.
xforms:resolve-index($xpath) in src/xforms-function-library.xsl — Substitutes index() calls with current values.
xforms:instance($id?) in src/xforms-function-library.xsl — Returns instance root element.
xforms:index($repeatId) in src/xforms-function-library.xsl — Returns current repeat index.
xforms:id($value, $node?) in src/xforms-function-library.xsl — XForms-compliant id() with xml:id and xsi:type support.
xforms:min($seq) and xforms:max($seq) in src/xforms-function-library.xsl — NaN-safe min/max.
xforms:property($name) in src/xforms-xpath-functions.xsl — Returns XForms property values (version, conformance-level).
xforms:boolean-from-string($s), xforms:count-non-empty($seq), xforms:power($base, $exp), and xforms:choose($cond, $t, $f) in src/xforms-xpath-functions.xsl.
xforms:avg($seq), xforms:is-card-number($s), and xforms:event($name) in src/xforms-xpath-functions.xsl.
Date/time functions in src/xforms-xpath-functions.xsl: xforms:now(), xforms:local-date(), xforms:local-dateTime(), xforms:days-from-date(), xforms:days-to-date(), xforms:seconds-from-dateTime(), xforms:seconds-to-dateTime(), xforms:seconds(), xforms:months(), and xforms:adjust-dateTime-to-timezone().
Crypto functions in src/xforms-xpath-functions.xsl: xforms:digest() and xforms:hmac() — JS crypto.subtle bridge.