Skip to content

Releases: spagu/XSLT-Processor

v1.2.1

Choose a tag to compare

@spagu spagu released this 30 Sep 19:30
c3b97b4

Same library as 1.2.0, which was released on GitHub without its macOS and
Windows executables and was never published to npm. Upgrade from 1.1.x
straight to 1.2.1; everything listed under 1.2.0 applies.

Fixed

  • Standalone executables for macOS and Windows were missing from the 1.2.0 release: the tests of the binary build scripts failed on those runners, so the release workflow skipped every upload. On macOS the tests compared against the unresolved temporary directory (/var/...) while the scripts return real paths (/private/var/...); on Windows the test archive was built with GNU tar from Git Bash, which reads C:\... as a remote host, instead of the system tar the scripts use.

Internal

  • CI runs the binary build script tests on macOS and Windows for every pull request (binary scripts job), so a platform-only failure shows up before a release.

Full list of changes since 1.1.3: see 1.2.0 and the CHANGELOG.

v1.2.0

Choose a tag to compare

@spagu spagu released this 30 Sep 16:08
331c999

Changed

  • libxslt parity (task 0021): the libxslt 1.1.45 conformance corpus passes completely (299 of 299 counted cases; 32 cases are skipped as DTD-dependent, implementation-defined or extension-only).
  • importStylesheet() rejects duplicate named templates or global variables at the same import precedence, text between top-level elements, invalid or undeclared names of templates and attribute sets, and an attribute set that uses itself. An undeclared prefix in an XPath name test is an error instead of matching names in no namespace.
  • xsl:namespace-alias keeps the literal prefix and supports #default without a default namespace; named templates, attribute sets and decimal formats are compared by expanded name, and attribute sets with the same name are merged across import precedence; xsl:strip-space/xsl:preserve-space resolve namespaces.
  • HTML output writes namespace declarations and derives the doctype from version (version="5" gives <!DOCTYPE html>); XHTML 1.0 doctypes get libxml2's Content-Type meta and xmlns; generated prefixes are ns_1, ns_2, ... like libxslt.
  • Browser parity: transformToFragment into an HTML document creates XHTML elements for xml output; transformToDocument returns an HTML document for html output and Blink's text page (XHTML doctype, head/title, pre) for text output. engine.outputSettings.indent defaults to undefined; engine.stripSpace/preserveSpace hold expanded names.
  • Unprefixed name tests are namespace-strict (task 0003): item, @a and names in patterns match only nodes in no namespace (XPath 1.0 section 2.3), like Chrome/libxslt. Elements of HTML documents are still matched by unprefixed, case-insensitive names. Migration: bind a prefix to the namespace, or use local-name(); the deprecated new XSLTProcessor({ legacyNameTests: true }) (also on XsltEngine/XPathEvaluator) restores the old matching for now.
  • transformToFragment(source, htmlDocument) with html output (declared or detected) returns real HTML elements parsed by the owner document, like Chrome: <a> is an HTMLAnchorElement and scripts run when inserted. The markup is parsed as body content, so html/head/body tags are dropped. XML output keeps the XML DOM nodes.
  • Extension elements (extension-element-prefixes) are no longer copied to the result: their xsl:fallback children run; without a fallback nothing is output and a warning is shown. New XsltEngine#registerExtensionElement(uri, localName, handler); element-available() reports registered elements.
  • HTML URI attributes are %-escaped like libxml2: only href, action, src and a/@name on elements in no namespace; spaces, control characters, DEL and non-ASCII characters are escaped (a b becomes a%20b).
  • id()/key() in patterns accept only string literals (XSLT 1.0 section 5.2); other arguments are an import error.
  • xsl:number numbers negative values as 0 with a warning; alphabetic and Roman formats fall back to decimals below 1, and Roman above 5000 (libxslt).

Fixed

  • Deeply nested result trees no longer overflow the call stack when serialized (task 0024): transformToString, transformToStream, serializeResult and serializeChunks walk the result tree on an explicit stack of open elements, so trees nested 50,000 elements deep serialize with the xml, html, xhtml and text methods. Output is byte-identical, and serialization is 10 to 30% faster on large documents.
  • Document order is computed once per transformation (DocumentOrderIndex) instead of calling compareDocumentPosition for every comparison, except where that method is native (browsers). With xmldom a union-heavy transformation dropped from about 150 s to 0.7 s.
  • Muenchian grouping was about 12% slower than in 1.1.3 under jsdom: sorting key(...)/@v walked the attribute list of every element (now only for two attributes of the same element), unprefixed name tests read the owner document's content type for every node (now only for HTML-specific outcomes), and key() read properties of the jsdom Document proxy on each call. In the benchmark Muenchian grouping is now 1.09 times faster than 1.1.3 (157 ms to 144 ms), and the other scenarios gained 2 to 6%.
  • Deep template recursion (task 0005): recursive xsl:call-template and xsl:apply-templates stopped at about 1,000 to 1,400 levels with Template recursion too deep. Templates now run from an explicit work stack, so the JavaScript stack no longer grows with template depth: libxslt's limit of 3,000 nested templates fits in Node.js and every browser, and more with a higher limit.
  • transformToDocument() returned null when the result had whitespace text around its root element (common with built-in templates), found by the browser tests; document-level whitespace is dropped and a DocumentType node is created from doctype-public/doctype-system.
  • A carriage return in XML text is written as &#13;; adjacent text nodes in cdata-section-elements form one CDATA section; xsl:copy-of declares the namespaces in scope; xml:id via xsl:attribute; element-available() with the default namespace; format-number() patterns ., #. and .#; html:div and other operator names as QName local parts; xsl:number on namespace nodes.
  • xsl:number hung on huge values (format="I" or "a" with 9007199254740992), a denial of service found by the conformance suite; conversion now takes logarithmic time. Values from 1e21 print as full digits, NaN and Infinity as NaN/Infinity.
  • xsl:number format tokens of any Unicode digit family (٠١, 1, ...) use that family's digits; count="@*" level="any" counts the attribute itself.
  • A global xsl:param with higher import precedence wins over an imported xsl:variable of the same name (XSLT 1.0 section 11.4).
  • self::* and other non-attribute axes no longer match attribute or namespace nodes.
  • Node-sets holding an element and its own attributes are sorted in document order under jsdom.
  • The release workflow ran twice per release (on the tag push and on the published release), including npm publish; it now runs once, on the published release.
  • The CLI entry point handles an unexpected rejection from main() instead of leaving the promise unhandled (SonarCloud S9383).

Security

  • Updated the development dependency brace-expansion to 5.0.12 (GHSA-q2hr-2g5m-vwhr, GHSA-qhr7-859c-m2p7, GHSA-6j4f-fj2g-mc7p).
  • Build, conformance and browser-test scripts confine paths from command line arguments and HTTP requests to the repository (or the temporary directory) after canonicalizing them, and run tar/codesign from fixed system directories instead of looking them up in PATH (SonarCloud S8707, S2083, S4036).
  • CI, release and Docker builds install dependencies with npm ci --ignore-scripts, so lifecycle scripts of dependencies never run during builds (SonarCloud S6505).
  • The GitHub Pages workflow grants pages: write and id-token: write only to the deploy job instead of the whole workflow (SonarCloud S8233).

Added

  • DOM implementations other than jsdom (task 0007): @xmldom/xmldom 0.9+ is supported in Node.js (optional peer dependency). The XML declaration and top-level whitespace xmldom keeps are ignored by every axis and by xsl:number, results are built without append/remove/createRange, and xmldom's appendChild(DocumentFragment) and Document.doctype bugs are worked around. linkedom (no namespace support) and xmldom 0.8 are not supported.
  • CLI DOM choice: the CLI uses jsdom, or @xmldom/xmldom when jsdom is not installed; XSLT_DOM=jsdom|xmldom picks one. With xmldom, start-up takes 72 ms instead of 457 ms and an issue-#9-sized transformation 1.9 s instead of 4.9 s. The standalone executables bundle both.
  • npm run test:dom runs the test suites, the CLI tests and the conformance suite with jsdom and with xmldom (298/298; one case needs internal-DTD entities that xmldom does not expand); CI job dom-matrix.
  • maxTemplateDepth option for XSLTProcessor and XsltEngine, default 3000 (XSLT_MAX_TEMPLATE_DEPTH, libxslt's xsltMaxDepth); deeper nesting throws Template recursion too deep, as libxslt reports a potential infinite recursion.
  • Asynchronous and streaming API (task 0010): XSLTProcessor#transformToStream(source, { signal, chunkSize }) returns a ReadableStream<string> serialized on demand; transformAsync(source, { signal, stylesheet, stylesheetUri, fetchStylesheet, fetchDocument }) returns a Promise<string>; importStylesheetAsync(style, uri, { loader, documentLoader, signal }) loads the xsl:import/xsl:include tree and literal document() URIs with fetch or a custom loader. Sources may be nodes, strings, bytes, ReadableStreams or async iterables. New exports serializeChunks(), DEFAULT_CHUNK_SIZE, transformToChunks() and transformToStream(), with TypeScript types. The CLI writes its output in chunks with backpressure (byte-identical, about 27% less peak memory on a 100 MB result).
  • Project website (task 0015) with the documentation, the changelog and an XSLT playground, built with spagu/ssg and deployed to GitHub Pages (make site, .github/workflows/site.yml); it replaces the Jekyll workflow. Pull requests build and check the site without deploying it.
  • Standalone xslt executables (task 0008) for linux-x64/arm64, darwin-x64/arm64 and windows-x64, attached to each GitHub release with checksums.sha256. They are built as Node.js Single Executable Applications with jsdom bundled (scripts/binaries/) and smoke-tested on each operating system in CI; scripts/install.sh installs them with checksum verification.
  • Browser tests (task 0009): npm run test:browser runs the built bundles in Chromium, Firefox and WebKit with Playwright, in CI on pull requests; an informational differential test compares the output with the browser's native `XSLTProcesso...
Read more

v1.1.3

Choose a tag to compare

@spagu spagu released this 29 Sep 21:44
7315db1

Fixed

  • Empty xsl:param / xsl:variable was true in boolean tests (#11) - a variable-binding element with neither select nor content became an empty result tree fragment, which converts to true. XSLT 1.0 section 11.2 gives it the empty string, so <xsl:param name="p"/> followed by <xsl:if test="$p"> is now false. This applies to global and template params, variables and xsl:with-param; a variable whose content produces no nodes is still a (true) result tree fragment, and setParameter() still overrides the default.

  • Empty XHTML elements were written as <script/> or <div/>, which breaks when XHTML is parsed as HTML. Empty elements in the XHTML namespace now follow the XHTML compatibility guidelines like libxml2 (Chrome) and Firefox: void elements as <br />, all others with an explicit end tag (<script src="a.js"></script>). Elements in other namespaces keep <x/>.

  • Output encoding was ignored: characters the declared encoding cannot represent are now written as character references (&#8364;, or HTML entity names such as &euro; with method="html"; CDATA sections are split around them). The CLI writes the file and stdout as bytes in the declared encoding (UTF-16 with a BOM; unknown multi-byte encodings fall back to UTF-8 with a warning). Previously an ISO-8859-1 declaration was followed by UTF-8 bytes.

  • name(), local-name(), namespace-uri() returned #document, #text and #comment for unnamed nodes; they return "" (XPath 4.1), and a processing instruction's name is its target.

  • id() with a node-set argument used only the first node and could return duplicates; every node's value is split and the result is a set in document order.

  • / and key() inside exsl:node-set() trees resolved against the source document instead of the fragment containing the context node.

  • lang() now works from text and attribute nodes and honours only xml:lang, not a plain lang attribute.

  • cdata-section-elements compares expanded names (a prefix bound to the same namespace matches; a bare name no longer matches the same local name in any namespace).

  • HTML output method now follows libxslt/Chrome: a Content-Type meta is added to head, &{ and < stay unescaped in attribute values, and non-ASCII characters in URI attributes (href, src, ...) are %-escaped as UTF-8.

  • Node-set functions (count(), sum(), name(), local-name(), namespace-uri()) given a number, string or boolean now raise a type error instead of returning a made-up value.

  • CLI: --method accepts only xml, html, xhtml or text; the root directory / (or a drive root) works as base directory.

  • Computed names are validated (XML 1.0 5th ed. QNames): xsl:element/xsl:attribute names such as 1a, x{ or a:b:c, undeclared prefixes and xmlns attribute names are reported with console.warn and skipped, as libxslt does, instead of producing malformed output. xsl:element name="p:e" namespace="" creates <e/> in no namespace.

  • xsl:fallback is instantiated for unknown XSLT instructions (XSLT 15), and forwards-compatible mode (version other than 1.0) ignores unknown top-level elements.

  • xsl:number default count compares expanded names and processing-instruction targets.

  • xsl:output cdata-section-elements names are expanded with the namespaces in scope on xsl:output, including the default namespace for unprefixed names (XSLT 16.1); several xsl:output lists are united and undeclared prefixes are reported.

  • isNativeXSLTSupported() no longer reports this polyfill as native after installGlobal().

  • Loaders with non-jsdom DOMs (e.g. xmldom): XML strings returned by loaders are parsed with the new domParser engine option, the global DOMParser or the stylesheet's window, and parse errors are detected without querySelector.

  • Warnings for duplicate variable bindings (XSLT 11.4/11.5) and undeclared exclude-result-prefixes / extension-element-prefixes; behaviour is unchanged (the later binding wins).

  • The broken npm run test:browser script (it pointed to a file that does not exist) was removed; npm test now expands src/**/*.test.js itself, so test files in nested directories are no longer skipped.

Performance

  • axis::x[n] steps (the common following-sibling::x[1] / preceding-sibling::x[1] idiom) stop walking the axis after the n-th match: 8,000 siblings in a loop dropped from 4.7 s to 0.09 s.

  • xsl:number is linear per transformation: 8,000 nodes with level="any" count="i[@k='1']" dropped from 105 s to 0.09 s, level="single" from 5.5 s to 0.1 s.

  • key()/id() patterns cache their anchor nodes per document.

Changed

  • count(5), sum('x') and similar calls now throw a type error; name(/) returns ""; lang() ignores a plain lang attribute; HTML output gains a Content-Type meta; the CLI's runTransformation() helper returns { output, encoding }.

  • Invalid patterns in xsl:template match, xsl:key match and xsl:number count/from now make importStylesheet() throw an error naming the pattern (XSLT 5.2), as Chrome's native processor rejects such stylesheets; previously the template silently never matched. A failed import leaves the processor's previous stylesheet in place.

  • engine.outputSettings.cdataSectionElements holds { namespaceUri, localName } objects instead of strings.

Added

  • XsltEngine option domParser; XsltEngine.setStylesheetLoader()/setDocumentLoader() return the engine for chaining; XPathLimits export.
  • Complete TypeScript declarations in scripts/xslt-processor.d.ts (DocumentLoader, XSLTProcessor.setDocumentLoader, XPathEvaluator options, XPathContext hostContext, ...); a test fails when a runtime export is missing from the declarations.

Documentation

  • README: corrected the Custom Security Limits example (parseXPath instead of the non-exported parse) and the Node.js loader example (jsdom with a global DOMParser; the xmldom version failed).
  • README: new table of contents and "Module exports", "TypeScript" and "Known Deviations" sections.
  • README: conformance tables corrected (namespace:: axis not supported, xsl:fallback and extensions partial, xsl:number ignores lang/letter-value, the DOM is consumed rather than implemented); invented per-spec test counts replaced by real figures.
  • README: browser minimums corrected for the ES2022 bundle (Chrome/Edge 93, Opera 79, Samsung Internet 17); the XsltProcessorLib CDN global and auto-install behaviour documented; development setup, Docker commands and publishing notes fixed; style-guide success and warning colours now meet WCAG 2.2 AA contrast.
  • SECURITY.md and CONTRIBUTORS.md rewritten for this project (they described a different project); security reports go through GitHub private vulnerability reporting.

v1.1.2

Choose a tag to compare

@spagu spagu released this 25 Sep 09:32
e0ec0ba

Fixed

  • (a) or (b) failed with Unexpected token FUNCTION (#9) - the tokenizer classified or, and, div and mod followed by ( as function calls before applying the XPath 1.0 operator disambiguation rule (section 3.7). The rules now run in the order the specification lists them.
  • xsl:sort ordering - text keys were upper-cased and compared with localeCompare, so §112 sorted before 100-00 and case-order folded the whole string. Text now compares by Unicode code point like libxslt (the engine of Chrome's native XSLTProcessor); lang or case-order switch to an Intl.Collator, where case-order only breaks ties.
  • xsl:sort data-type="number" - non-numeric keys were treated as 0 and parseFloat accepted 12abc. Keys now use XPath number() and NaN sorts before every number, as section 10 requires.
  • xsl:sort attribute value templates - order, data-type, case-order and lang were read literally, so order="{$sortOrder}" never took effect.
  • match="/" also matched the document element, so the classic catalog stylesheet (/ template wrapping apply-templates, plus match="catalog") rendered its wrapper twice. / now matches only the root node (XSLT 5.2).
  • Multi-step patterns never matched: c/d, */d, r//d, /r/c, c/d[2], e/@a, id('x')/d, key('k','v')//d. Patterns are now compiled once and matched right to left (new src/xslt/patterns.js, src/xslt/patternCompiler.js).
  • Template conflicts: with equal priority and import precedence the last template now wins, like libxslt (XSLT 5.5 recovery).
  • Diamond imports (two imported stylesheets importing the same third one) were rejected as circular; only real cycles are errors now. xsl:call-template now honours import precedence.
  • Default output method: a result whose root element is <html> is serialized as HTML when the stylesheet has no xsl:output method (XSLT 16).
  • Simplified stylesheets (<html xsl:version="1.0">) dropped their literal root element.
  • Keys: several xsl:key elements with the same name now all feed the index, key() with several values returns document order without duplicates, and the index is rebuilt for every transformation instead of going stale after DOM changes.
  • != on node-sets is existential (@n != 1 is true when some @n differs), and a node-set compared with a boolean uses boolean() (XPath 3.4).
  • XML whitespace is only space, tab, CR and LF: normalize-space() and number() no longer treat a non-breaking space as whitespace.
  • number() follows the XPath Number grammar: 1e3, 0x10, +5 and Infinity are NaN; 5. and .5 are numbers.
  • string() of numbers never uses exponent notation (1e21 gives 1000000000000000000000).
  • Prefixed function calls such as exsl:node-set($rtf) failed to parse. EXSLT exsl:node-set() and msxsl:node-set() are now available.
  • xml: prefix is predeclared, so @xml:lang works without a declaration.
  • @* no longer includes xmlns namespace declarations.
  • Adjacent text and CDATA form one text node in the XPath data model (<r>a<![CDATA[b]]>c</r>: one text() with value abc).
  • string-length(), substring(), translate() count characters, not UTF-16 code units.
  • Template parameters: a template's xsl:param default was overridden by any same-named parameter in scope (the caller's own params, global params, or undeclared xsl:with-params). A param now takes a same-named with-param from its direct caller, otherwise its own default (XSLT 11.6).
  • Variable scoping was dynamic: a called template saw its caller's local variables, and variables declared inside xsl:if, xsl:choose or xsl:for-each leaked out. Scoping is now lexical (XSLT 11.5).
  • Global variables and params may reference ones declared later (XSLT 11.4); circular definitions report a clear error.
  • xsl:copy-of select="/" copied nothing, and copying attribute nodes produced an empty text node instead of the attribute. Copied elements and attributes keep their namespaces.
  • Result namespaces: namespace prefixes declared on xsl:template or literal result elements were ignored in XPath expressions; xsl:element ignored the default namespace in scope (<p xmlns=""/> inside XHTML); xsl:attribute name="xl:href" and namespace= lost the namespace; literal result elements did not carry their in-scope namespace declarations.
  • &#160; in stylesheets was stripped as whitespace (<td>&#160;</td> became <td/>).
  • Attribute value templates containing { or } inside a string literal ({concat('{', 'x')}) were split incorrectly.
  • xsl:number: grouping-separator and grouping-size were ignored, and format was not an attribute value template.
  • transformToDocument() with method="text" returned null; it now returns <html><head/><body><pre>…</pre></body></html> like Chrome.
  • xsl:attribute after child nodes is ignored with a warning, as libxslt does, instead of being added.
  • Serialization: comments containing -- and processing instructions containing ?> are made well-formed instead of producing broken XML or throwing.
  • Deep recursion: a recursive named template overflowed the stack after about 700 levels; it now reaches about 1,200 levels (frames per level cut from 11 to 5) and reports Template recursion too deep instead of leaking a RangeError.
  • Long XPath expressions such as a 120-term sum failed with Maximum recursion depth exceeded (100) inside stylesheets; transformations now allow 1000 levels (XSLT_MAX_EXPRESSION_DEPTH).
  • Reverse axes in predicates - preceding-sibling::*[1] and preceding::*[1] selected the farthest node instead of the nearest one. Reverse axes now use proximity positions (XPath 1.0 section 2.4), while node-sets are still returned in document order.
  • Axes from attribute nodes - parent::, ancestor::, following:: and preceding:: returned nothing for an attribute context node.
  • Transformations of documents with more than 10,000 matching nodes returned null (Result set exceeds maximum size). The standalone XPath API keeps its 10,000 guard for untrusted expressions; XsltEngine now allows 5,000,000 nodes per step (XSLT_MAX_RESULT_SIZE, configurable with the maxResultSize engine option).
  • The xslt command crashed for every npm user with ERR_MODULE_NOT_FOUND: jsdom: the CLI imported jsdom, which was only a devDependency. jsdom is now an optional peer dependency, loaded on demand; when it is missing the CLI prints how to install it. The library itself keeps zero runtime dependencies.
  • The CLI used the private processor._engine field instead of the public engine getter.
  • CLI xsl:include, xsl:import and document() did not work: no loaders and no base URI were set. They now resolve relative to the referencing stylesheet, confined to the base directory; http:/https: URIs are refused, and a document() that cannot be loaded yields an empty node-set with a one-line warning.
  • CLI input decoding always assumed UTF-8, so an ISO-8859-1 document came out as caf�. Files are now decoded per XML 1.0 Appendix F: byte order mark, then the XML declaration's encoding, else UTF-8 (new bin/lib/decode.js).

Performance

  • Trimming XML whitespace in normalize-space() and number() used a regular expression that backtracks quadratically on long whitespace runs; it is now a linear scan (400,000 spaces: 4 ms).
  • Template matching is linear: apply-templates over 8,000 children with match="item[@id]" dropped from 90 s to 0.1 s, Muenchian grouping over 8,000 items from 8.5 s to 0.3 s, key lookups from 7 s to 0.2 s.
  • The transformation from issue #9 (3.4 MB of HTML output) dropped from 28 s to 8 s. Axes walk firstChild/nextSibling instead of indexing jsdom NodeLists (each index access crosses a Proxy), step results are merged without quadratic concat/unshift, sort keys are computed once per node instead of once per comparison, and name tests only read namespaceURI and the document content type when the result depends on them.

Added

  • src/xpath/axes.js (axis traversal) and src/xslt/sort.js (xsl:sort), each with a full test suite, plus src/regressions.test.js for reported issues.
  • XSLT_MAX_RESULT_SIZE and XSLT_MAX_EXPRESSION_DEPTH exports and the maxResultSize / maxRecursionDepth / documentLoader engine options in the TypeScript declarations.
  • New modules: src/xpath/strings.js, src/xslt/{patterns,patternCompiler,variables,stylesheetNamespaces,resultNamespaces,copying,avt}.js, bin/lib/{decode,loaders,output}.js.

Changed

  • CLI stdout no longer gets an extra trailing newline when piped or redirected, so it is byte-identical to -o; a newline is only added for an interactive terminal.
  • Stylesheets without xsl:output method whose result root is <html> are now serialized as HTML (no XML declaration, <br>); engine.outputSettings.method is null unless declared.
  • Behaviour that relied on the fixed bugs changes accordingly: / no longer matches the document element, equal-priority templates pick the last one, number('1e3') is NaN, @* skips xmlns declarations.
  • Templates no longer see their caller's local variables, and variables declared inside xsl:if/xsl:choose/xsl:for-each are not visible after them; stylesheets that relied on this now fail with Undefined variable, as they do in libxslt.
  • Namespace declarations in scope in the stylesheet now appear on result elements (list them in exclude-result-prefixes to suppress them), matching libxslt.
  • XsltContext gained globals and xpathVariables; the internal processElement/processXsltElement methods were replaced by an instruction dispatch table.
  • engine.keys[name] is now an array of { match, use } definitions.
  • `xsl:s...
Read more

v1.1.1

Choose a tag to compare

@spagu spagu released this 10 Sep 22:54
bc13fcb

Fixed

  • npm packaging - the bin entry used a ./ prefix, which npm 11 rejects ("script name bin/xslt.js was invalid and removed"), so the published package would have had no xslt executable. The entry is now bin/xslt.js.

  • npm packaging - test files (src/**/*.test.js) are excluded from the tarball via negated files patterns (68 -> 48 files).

  • Release workflow - the publish step now passes the NPM_TOKEN secret as NODE_AUTH_TOKEN when it is configured, falling back to Trusted Publishing (OIDC) otherwise.

Version 1.1.0 was tagged on GitHub but never reached npm (the publish job ran before npm Trusted Publishing was configured); 1.1.1 is the release to install.

v1.1.0

Choose a tag to compare

@spagu spagu released this 10 Sep 22:27
739769f

Minor release: new public API (setStylesheetLoader, setDocumentLoader, transformToString, engine), the xsl:output serializer and the XSLT 1.0 conformance fixes below. Versions 1.0.4-1.0.8 were tagged but never published to npm, so this is the first npm release after 1.0.3.

Added

  • XSLTProcessor.setStylesheetLoader(loader) - public API for configuring the loader used to resolve xsl:import and xsl:include. It can be called before importStylesheet() (required, since the engine is created during import) or after it (the live engine is updated). Passing anything other than a function or null throws a TypeError. Returns the processor for chaining.

  • XSLTProcessor.engine - read-only getter exposing the underlying XsltEngine for advanced usage. Returns null until a stylesheet has been imported.

  • importStylesheet(style, stylesheetUri) - the optional second argument is now forwarded to the engine and used as the base URI when resolving relative xsl:import/xsl:include hrefs.

  • TypeScript declarations for the new API, including an exported StylesheetLoader type (synchronous: (href, baseUri?) => Document | string).

  • dist/xslt-processor.d.cts - CommonJS-flavoured declarations, wired through nested types conditions in package.json exports, so require() consumers under TypeScript node16/nodenext resolution no longer get the ESM declarations for the CommonJS bundle ("Masquerading as ESM" reported by @arethetypeswrong/cli). Verified with TypeScript 7.0.2 in strict mode under nodenext and bundler resolution.

  • Release workflow - publish job using npm Trusted Publishing (OIDC) with provenance; runs on v* tags after tests and build, and refuses to publish when the tag does not match package.json. Requires a one-time Trusted Publisher configuration on npmjs.com (documented in README).

  • XSLT-defined XPath functions - document(), key(), format-number(), current(), generate-id(), system-property(), function-available(), element-available() and unparsed-entity-uri(). Calling any of them previously raised Unknown function: X, which made transformToFragment()/transformToDocument() return null.

  • XSLTProcessor.setDocumentLoader(loader) (and XsltEngine.setDocumentLoader(loader), plus a documentLoader engine option) - synchronous loader for the XSLT document() function, returning a Document, an XML string or null. Same validation and chaining contract as setStylesheetLoader(). Missing loader or a null result yields an empty node-set instead of failing the transformation; document('') returns the stylesheet, node-set arguments are unioned, fragment identifiers are ignored and relative URIs resolve against the stylesheet URI.

  • XPathEvaluator.registerFunctions(map) - extension hook used to register the XSLT function library, keeping src/xpath a pure XPath 1.0 implementation. XPathContext gained an optional hostContext that is carried through predicate evaluation so host functions such as current() can reach the XSLT context.

  • xsl:apply-imports - previously reported as Unknown XSLT element. Applies only templates of lower import precedence in the same mode, falling back to the built-in rules.

  • xsl:strip-space / xsl:preserve-space - parsed since 1.0.0 but never applied. Whitespace-only text nodes are now removed from a copy of the source tree (the caller's document is never modified), honouring "most specific name test wins", xsl:preserve-space winning ties, and xml:space="preserve" on ancestors.

  • New focused modules with full test suites: src/xslt/functions.js, keys.js, formatNumber.js, number.js, numberFormat.js, whitespace.js, literalResult.js, resultTree.js, elements.js and uri.js.

  • CommonJS consumer smoke test (tests/cjs-smoke.cjs) exercising the built bundle through require() with jsdom, plus tests for the package entry point.

  • Output serializer (xsl:output, XSLT 1.0 section 16) - new src/xslt/serializer.js exporting serializeResult(node, outputSettings) plus the focused modules in src/xslt/serializer/ (baseWriter, xmlSerializer, htmlSerializer, textSerializer, escape, indent, namespaces, settings, rawText, constants).

    • method="xml" - XML declaration honoring encoding, version and standalone, omit-xml-declaration, doctype-public/doctype-system, minimal text and attribute escaping, <x/> for empty elements, namespace declarations emitted where first used and never twice, comments and processing instructions.
    • method="html" - no XML declaration, HTML doctype, void elements written as <br>, minimized boolean attributes, unescaped script/style content, >-terminated processing instructions, original element and attribute name case, no namespace declarations.
    • method="xhtml" - XML rules with void elements written as <br />.
    • method="text" - concatenation of all descendant text nodes, unescaped.
    • Automatic default method detection: html when the result document element is html in no namespace, xml otherwise.
    • indent="yes" - newline plus two-space indentation for element-only content; mixed content, cdata-section-elements and the HTML pre/script/style/textarea elements are left untouched.
    • cdata-section-elements - text children wrapped in <![CDATA[...]]>, split around any ]]> terminator.
    • disable-output-escaping="yes" on xsl:text and xsl:value-of is now honored; text nodes can also be marked explicitly with the exported markRawText() helper.
  • XSLTProcessor.transformToString(source) and XsltEngine.transformToString(sourceNode) - non-W3C convenience methods returning the serialized result. transformToFragment() and transformToDocument() are unchanged.

  • Public exports - serializeResult, markRawText, isRawText and resolveOutputSettings are exported from the package entry point, and transformToString/OutputSettings are declared in the generated TypeScript declarations.

  • CLI - bin/xslt.js now serializes through transformToString() instead of re-indenting with a regular expression, and gained --indent, --method <m> and --no-declaration flags that override the stylesheet xsl:output settings. Helpers were extracted to bin/lib/options.js and bin/lib/transform.js.

  • Tests - src/xslt/serializer.test.js, src/XSLTProcessor.serialization.test.js and src/cli.test.js (119 new tests, 560 in total), including the <xsl:output method="xml" indent="yes"/> regression from DesignLiquido/xslt-processor#219.

Fixed

  • TypeError: Cannot read properties of undefined (reading 'setStylesheetLoader') (#6) - the README documented processor.engine.setStylesheetLoader(...), but processor.engine was undefined and the engine did not exist before importStylesheet(). The documented workflow now works through processor.setStylesheetLoader(...).
  • README - rewrote the "Using xsl:import and xsl:include" section: the previous example used await inside a non-async callback and contained two unreachable "options". It now shows a correct synchronous loader, a browser pre-fetch pattern, and a Node.js filesystem example using path.resolve(path.dirname(baseUri), href).
  • xsl:copy lost attributes - the identity transform turned <i k="a">1</i> into <i>a1</i>. Attribute nodes were never matched by patterns such as @*|node() because attributes have no parentNode; patterns are now evaluated from the ownerElement, so <xsl:copy> on an attribute copies the attribute instead of falling back to the built-in text rule. The identity transform now round-trips elements, attributes, text, comments and processing instructions exactly.
  • CDATA sections were invisible - the string-value of an element containing a CDATA section was empty. CDATA nodes now count as text everywhere: string-value, the text() node test, xsl:value-of, xsl:copy-of and the built-in text template.
  • xsl:number level="any" always produced the same number (I, I instead of I, II). Counting was rewritten for single, multiple and any, including count, from and the 1, 01, a, A, i, I format tokens with prefixes, separators and suffixes.
  • xsl:namespace-alias produced wrong output (<ax:stylesheet xmlns:ax="xsl"/>). Aliases are now resolved against the namespace declarations in scope, so literal result elements and their attributes are emitted in the result namespace with the result prefix (or the default namespace for result-prefix="#default"), which makes stylesheet-generating stylesheets work.
  • xsl:use-attribute-sets on literal result elements was ignored. It now applies the same attribute sets as on xsl:element/xsl:copy (literal attributes still win), and xsl:* attributes (xsl:version, xsl:exclude-result-prefixes, xsl:extension-element-prefixes, xsl:use-attribute-sets) never leak into the result.
  • transformToFragment(xmlDoc, htmlDocument) lower-cased names and injected the XHTML namespace (<bar xmlns="http://www.w3.org/1999/xhtml"> for <BAR>). The result tree is now built in a neutral XML document and imported into the output document at the end, preserving names, namespaces and disable-output-escaping markers while keeping the W3C behaviour that the fragment is owned by the output document.
  • XPath function lookup no longer resolves inherited Object.prototype members, so expressions such as constructor() report Unknown function instead of invoking an object built-in.
  • setParameter() broke every transformation - the processor stored { value } in globalParameters, but the engine only understood { select } / { node } definitions and called processChildren(undefined), throwing Cannot read properties of undefined (reading 'childNodes') (so `tran...
Read more

v1.0.8

Choose a tag to compare

@spagu spagu released this 15 Jul 16:34
93cfec6

Fixed

  • Vite/Angular module resolution - Changed browser targets in package.json to point to the ESM build (dist/xslt-processor.js) instead of the IIFE build (dist/xslt-processor.browser.js). This resolves a SyntaxError: The requested module ... does not provide an export named 'default' issue under bundlers like Vite (e.g. in Angular 19+).

Changed

  • Dependencies: Bumped esbuild to ^0.28.0 (resolving security vulnerability CVE-2024-52317 / GHSA-67mh-4wv8-2f99 on esbuild < 0.25.0) while keeping eslint on v9 to retain Node.js 18 compatibility.
  • Updated VERSION in src/index.js to 1.0.8.