From f3d9de2c8617ee995d7e97bbb7073fc5ae765821 Mon Sep 17 00:00:00 2001 From: Mihaela Mihaljevic Date: Wed, 24 Jun 2026 02:16:20 +0200 Subject: [PATCH] fix(html): do not escape < in an attribute value in the html output method (#258) XSLT 1.0 16.2: the html output method escapes & and " in attribute values but, unlike the xml method, leaves < (and >) literal. The serializer was escaping < to < in attribute values. Removing that case lets it pass through; the xml method is unchanged and still escapes both. Closes Apache Xalan conformance cases output49 and output74. --- CHANGELOG.md | 2 ++ Sources/HTML/HTMLSerializer.swift | 3 ++- Tests/Fixtures/xalan-baseline.txt | 2 -- Tests/XSLTHTMLOutputTests.swift | 20 ++++++++++++++++++++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e606343..6ad277b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- The `html` output method no longer escapes a `<` character in an attribute value (XSLT 1.0 16.2). The HTML output method escapes `&` and `"` in attribute values but leaves `<` and `>` literal, unlike the `xml` method; the serializer was escaping `<` to `<` there. The `xml` output method is unchanged (it still escapes both). Closes Apache Xalan conformance cases `output49` and `output74`. + - A string-form `document()` reference now resolves against the base URI of the stylesheet element holding it (XSLT 1.0 12.1), which for a declaration in an `xsl:include`d or `xsl:import`ed file is that file's URI rather than the top stylesheet's. The parser already tracked each top-level declaration's source-file base; that base is now carried onto the compiled templates and global variables and threaded into expression evaluation, so a relative `document('data.xml')` (or `document('')`, the self-reference) in an included file resolves against the included file, not the importing one. Closes Apache Xalan conformance cases `mdocs12` and `mdocs13`. - The `document()` function now resolves a relative URI reference against the correct base URI (XSLT 1.0 12.1). The two-argument form resolves the first argument against the base URI of the second argument's first node, and the node-set form resolves each reference against the base URI of the node that supplied it; previously every reference resolved against the single global base, so a reference relative to a document that `document()` itself had loaded from a subdirectory could not be found. Each loaded document now records the URI it came from as its base URI, and a relative reference is resolved against that base before the loader applies its own source-document base. Closes Apache Xalan conformance cases `reluri09`, `reluri10`, and `reluri11`. diff --git a/Sources/HTML/HTMLSerializer.swift b/Sources/HTML/HTMLSerializer.swift index ef372a8..4d2849d 100644 --- a/Sources/HTML/HTMLSerializer.swift +++ b/Sources/HTML/HTMLSerializer.swift @@ -85,7 +85,8 @@ extension PureXML.HTML { case "&": previousWasAmpersand = true case "\"": result += """ - case "<": result += "<" + // The html output method does not escape `<` (or `>`) in an + // attribute value, unlike the xml method (XSLT 1.0 16.2). default: result += latin1Entity(scalar) ?? String(Character(scalar)) } } diff --git a/Tests/Fixtures/xalan-baseline.txt b/Tests/Fixtures/xalan-baseline.txt index b236689..f76b0a3 100644 --- a/Tests/Fixtures/xalan-baseline.txt +++ b/Tests/Fixtures/xalan-baseline.txt @@ -31,13 +31,11 @@ output/output26 output/output32 output/output40 output/output48 -output/output49 output/output60 output/output63 output/output70 output/output72 output/output73 -output/output74 output/output75 output/output80 output/output86 diff --git a/Tests/XSLTHTMLOutputTests.swift b/Tests/XSLTHTMLOutputTests.swift index 167dccb..dd27b8b 100644 --- a/Tests/XSLTHTMLOutputTests.swift +++ b/Tests/XSLTHTMLOutputTests.swift @@ -37,6 +37,26 @@ struct XSLTHTMLOutputTests { #expect(!out.contains("<")) } + @Test("The html method leaves < and > literal in an attribute value") + func test_attributeAngleBrackets() throws { + // XSLT 1.0 16.2: the html output method does not escape `<` in an + // attribute value (nor `>`), unlike the xml method. `&` and `"` still + // escape (Apache Xalan output49, output74). + let out = try transform(htmlStyle("&"\">t"), "") + #expect(out == "&"\">t") + } + + @Test("The xml method still escapes < in an attribute value") + func test_xmlAttributeStillEscapes() throws { + let style = """ + + + t + + """ + #expect(try transform(style, "") == "t") + } + @Test("The XML output method still self-closes empty elements") func test_xmlStillSelfCloses() throws { let style = """