7.3 KiB
XSLT tools
Browser-based MVP for testing XML/XSLT transformations. The application itself does not intentionally upload opened files, and it uses LocalStorage plus local file dialogs where available. XSLT is executable code, however: a trusted stylesheet may access browser or network resources, so processing is not described as guaranteed local-only.
The application is independently deployable and also implements Toolbox Contract v1. When launched with a same-origin ?toolbox=<catalog URL> context, the shared application shell adds toolbox navigation and an application switcher; without that context, the same build runs standalone.
Features in this MVP
- Three-column workbench:
- XML input
- XSL transformation code
- XML output
- CodeMirror editors with:
- line numbers
- XML syntax highlighting
- editor undo/redo
- cut/copy/paste helpers
- find support via CodeMirror shortcuts
- Transformation engines:
- SaxonJS 2.7 as the default engine for XSLT 1.0/2.0/3.0-style workflows
- Browser-native
XSLTProcessoras a lightweight XSLT 1.0 fallback
- A mandatory SHA-256 trust confirmation for the exact executable stylesheet; every edit, replacement, format, or reset revokes trust for both engines
- Defense-in-depth input/output limits and a best-effort asynchronous deadline
- XML and XSLT well-formedness checks
- Basic XSLT root/version checks
- Snippet toolbox for common XSLT constructs
- Open/save local files
- File System Access API when supported
- file input/download fallback otherwise
- Output → Input action for chained transformations
- Confirmation dialog before destructive overwrites
- LocalStorage persistence
- Approximate “explain transformation” table after a run
- Shared toolbox shell with standalone fallback, application switching, and the XSLT-specific Help action
- Relocatable static output for deployment at root or nested paths
SaxonJS note
SaxonJS 2.7 is vendored as the unmodified browser distribution file at public/vendor/saxon/SaxonJS2.js and loaded lazily when the SaxonJS engine runs. It is not bundled from the npm package during the application build.
SaxonJS is available free of charge but is not open source. Its separate redistribution terms are preserved at public/vendor/saxon/LICENSE.txt. Release archives include that notice as LICENSES/SaxonJS-LICENSE.txt alongside LICENSES/xslt-tools-LICENSE.txt for the AGPL-3.0-only application; do not describe the complete archive as AGPL-only.
Executable stylesheet security
Treat every stylesheet like executable code. Transformation is disabled until
the user reviews a warning and trusts the SHA-256 fingerprint of the exact
current stylesheet. Trust is session-only and tied to both the content hash and
an edit revision; changing and then reverting text still requires another
confirmation. The same gate covers SaxonJS and native XSLTProcessor.
The application enforces conservative input/output limits and a best-effort asynchronous deadline, but transformations are not isolated from the browser or network and synchronous CPU exhaustion cannot be forcibly stopped. A trusted SaxonJS stylesheet may read data stored by other Toolbox apps on the same site, including PDF workspaces and portal preferences, then send it over the network or navigate the page away.
When served with the recommended portal CSP, cross-origin fetches and dynamic
JavaScript evaluation are restricted without granting unsafe-eval. CSP is
defense in depth, not complete containment. Only execute stylesheets from trusted
sources.
Run locally
npm install
npm run dev
Then open the local Vite URL shown in your terminal.
Build
npm run build
npm run preview
Vite uses base: './', so a single build can be hosted at an unknown nested path such as /apps/xslt/. The SaxonJS runtime is resolved from that application base as well.
Toolbox manifest
src/toolboxApp.ts is the typed source for both the shared shell and the published public/toolbox-app.json manifest. Keep the generated file current with:
npm run manifest:generate
npm run manifest:check
Toolbox mode is contextual rather than a separate build:
/apps/xslt/ standalone
/apps/xslt/?toolbox=/toolbox.catalog.json toolbox mode
An unavailable or invalid catalogue falls back to standalone operation.
Release archive
After a successful build, create the deterministic application ZIP and SHA-256 sidecar with:
npm run release
The command refuses to overwrite a same-version archive. Use
npm run release -- --force only when intentionally replacing it.
The archive contains the static app and toolbox-app.json, plus CHANGELOG.md,
SOURCE.md with the corresponding tagged source link,
THIRD_PARTY_NOTICES.md, the application licence at
LICENSES/xslt-tools-LICENSE.txt, the separate vendored SaxonJS licence, and
licence/notice files for every installed production npm dependency below
LICENSES/npm/. Generated artifacts are written below release/ and are not
committed.
Suggested next steps
- Add parameter handling for
xsl:param. - Add a real diagnostics/lint bridge into CodeMirror gutter markers.
- Add transformation test cases with expected output assertions.
- Add project import/export as a single
.xslt-tools.jsonfile. - Improve explain mode through stylesheet instrumentation.
- Add optional precompiled SEF import/export support for larger SaxonJS projects.
Structure
src/
├── components/ # Toolbar, editor panels, dialogs, and app-specific actions
├── editor/ # CodeMirror integration and XSLT snippets
├── file/ # Local file open/save helpers
├── transform/ # Transform engine abstraction, SaxonJS, native engine
├── validation/ # XML/XSLT validation helpers
├── workspace/ # LocalStorage-backed workbench state
└── toolboxApp.ts # Toolbox manifest and shared-shell application definition
Development checks
Use Node.js 22 or newer. The main local quality gate is:
npm run check
Useful individual commands:
npm run typecheck # TypeScript only
npm run lint # ESLint
npm run lint:fix # ESLint autofix where possible
npm run format:check # Prettier check
npm run format # Prettier write
npm run test # Vitest/jsdom test suite
npm run build # Production build
npm run smoke:static # Nested-path static build smoke test
npm run smoke:toolbox # Contract, asset, standalone, and contextual checks
npm run release # Deterministic ZIP and SHA-256 release artifacts
npm run clean # Remove generated build/cache files
The vendored SaxonJS browser file is intentionally excluded from ESLint and Prettier because it is a third-party distribution artifact.
Transformation engines
The app currently exposes two engines:
- SaxonJS 2 dynamic XSLT 3.0: default engine. It loads the vendored
public/vendor/saxon/SaxonJS2.js, compiles raw XSLT text to SEF in the browser, then executes the generated SEF after explicit trust confirmation. - Native browser XSLTProcessor: XSLT 1.0 fallback using the browser implementation.
The SaxonJS runtime is loaded only for a trusted Saxon transformation, so the initial app shell does not block on the large compiler/runtime file.