diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6ad277b..3d46e71 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 now names the document type declaration `HTML` rather than the document element name (XSLT 1.0 16.2: "The name following the `` regardless of the result's root element; the `xml` method still uses the document element name (16.1). Closes Apache Xalan conformance cases `output40`, `output48`, and `output60`.
+
- 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`.
diff --git a/Sources/XSLT/XSLTTransform.swift b/Sources/XSLT/XSLTTransform.swift
index 4add3ca..45f443d 100644
--- a/Sources/XSLT/XSLTTransform.swift
+++ b/Sources/XSLT/XSLTTransform.swift
@@ -186,7 +186,12 @@ public extension PureXML.XSLT {
/// The `` prologue for the result's root element when the output
/// declares `doctype-system` (optionally with `doctype-public`), else empty.
private static func doctype(for result: PureXML.Model.Node, _ output: Output, method: String) -> String {
- guard let name = rootName(of: result) else { return "" }
+ // A doctype precedes the first element, so emit none without one. The
+ // html output method then names it HTML (16.2: "The name following
+ // \n"
diff --git a/Tests/Fixtures/xalan-baseline.txt b/Tests/Fixtures/xalan-baseline.txt
index f76b0a3..478ce94 100644
--- a/Tests/Fixtures/xalan-baseline.txt
+++ b/Tests/Fixtures/xalan-baseline.txt
@@ -29,9 +29,6 @@ numbering/numbering63
output/output22
output/output26
output/output32
-output/output40
-output/output48
-output/output60
output/output63
output/output70
output/output72
diff --git a/Tests/XSLTDoctypeOutputTests.swift b/Tests/XSLTDoctypeOutputTests.swift
index 6a5fd49..10b7099 100644
--- a/Tests/XSLTDoctypeOutputTests.swift
+++ b/Tests/XSLTDoctypeOutputTests.swift
@@ -20,17 +20,44 @@ struct XSLTDoctypeOutputTests {
#expect(try transform(style, "") == "\n")
}
- @Test("doctype-public and doctype-system emit a PUBLIC doctype")
+ @Test("the html method names the doctype HTML regardless of the document element")
func test_publicAndSystem() throws {
+ // XSLT 1.0 16.2: the html output method's doctype name is HTML, not the
+ // document element name (here `root`), unlike the xml method (Apache
+ // Xalan output40, output48, output60).
let style = """
-
+
"""
let out = try transform(style, "")
- #expect(out.hasPrefix("\n"))
- #expect(out.contains(""))
+ #expect(out.hasPrefix("\n"))
+ #expect(out.contains(""))
+ }
+
+ @Test("the xml method still uses the document element name for the doctype")
+ func test_xmlMethodUsesElementName() throws {
+ let style = """
+
+
+
+
+ """
+ #expect(try transform(style, "") == "\n")
+ }
+
+ @Test("the html method emits no doctype when the result has no element")
+ func test_htmlNoElementNoDoctype() throws {
+ // A doctype precedes the first element (16.2); a text-only result has
+ // none, so even the html method with doctype-public emits nothing.
+ let style = """
+
+
+ just text
+
+ """
+ #expect(try transform(style, "") == "just text")
}
@Test("Without doctype-system no doctype is emitted")