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 = """