fix(xslt): ignore disable-output-escaping for attribute, comment, and PI values (#264)
XSLT 1.0 16.4: disabling output escaping for a value used as something other than a text node (an attribute, comment, or processing-instruction string value) is an error; the recovery is to ignore it. The attribute value built from such content kept its raw-output markers, so the serializer emitted < and > unescaped in the attribute. Strip the markers for these three node kinds, via a shared helper, gated on the stylesheet actually using the feature so a private-use character in ordinary data is never removed. Closes Apache Xalan conformance case output75.
This commit is contained in:
parent
74bf622275
commit
3ab8637c14
@ -52,6 +52,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- `disable-output-escaping` is now ignored when an `xsl:text` or `xsl:value-of` produces the string value of an attribute, comment, or processing-instruction node (XSLT 1.0 16.4: disabling escaping for a value used as something other than a text node is an error, with the recovery being to ignore it). The attribute value built from such content kept its raw-output markers, so the serializer left `<` and `>` unescaped in the attribute; the markers are now stripped for these three node kinds (only when the stylesheet uses the feature, so a private-use character in ordinary data is untouched). Closes Apache Xalan conformance case `output75`.
|
||||
|
||||
- The `html` output method now percent-escapes non-ASCII and control characters in URI-valued attributes (XSLT 1.0 16.2, HTML 4.01 appendix B.2.1). For the HTML 4 attributes of type %URI (`href`, `src`, `cite`, `action`, and the rest), each non-ASCII or control/space character in the value is written as the uppercase hex of its UTF-8 bytes (so `cite="bë.xml"` becomes `cite="b%C3%AB.xml"`), while `"` and `&` keep their entity form; other attributes are unaffected. Closes Apache Xalan conformance case `output32`.
|
||||
|
||||
- Serializing a processing instruction whose data contains `?>` now inserts a space (`? >`) so the instruction is not terminated early (XSLT 1.0 7.3: a processor recovers from a generated `?>` by inserting a space after the `?`). The serializer emitted the `?>` raw, producing a malformed instruction. Closes Apache Xalan conformance case `output72`.
|
||||
|
||||
@ -99,7 +99,7 @@ extension PureXML.XSLT.Transformer {
|
||||
let name = createdName(nameTemplate, namespaceTemplate, namespaces, context, isAttribute: true)
|
||||
// xsl:attribute content that creates a non-text node ignores it with its
|
||||
// content (XSLT 1.0 errata E27), like xsl:comment/processing-instruction.
|
||||
return [.attribute(.init(name: name, value: Self.textNodesOnly(of: instantiate(body, context))))]
|
||||
return [.attribute(.init(name: name, value: escapedTextValue(of: instantiate(body, context))))]
|
||||
}
|
||||
|
||||
func copyInstruction(_ useAttributeSets: [String], _ body: [PureXML.XSLT.Instruction], _ context: XSLTContext) -> [PureXML.XSLT.ResultItem] {
|
||||
@ -160,6 +160,17 @@ extension PureXML.XSLT.Transformer {
|
||||
}
|
||||
}
|
||||
|
||||
/// The text-node content of `items` as the string value of an attribute,
|
||||
/// comment, or processing-instruction node, with disable-output-escaping
|
||||
/// ignored (XSLT 1.0 16.4: disabling escaping for a value used as something
|
||||
/// other than a text node is an error; the recovery is to ignore it). The
|
||||
/// raw markers are only stripped when the stylesheet uses the feature, so a
|
||||
/// private-use character in ordinary data is never removed.
|
||||
func escapedTextValue(of items: [PureXML.XSLT.ResultItem]) -> String {
|
||||
let text = Self.textNodesOnly(of: items)
|
||||
return stylesheet.usesRawText ? PureXML.XSLT.RawText.stripped(text) : text
|
||||
}
|
||||
|
||||
/// The source element's in-scope namespace declarations as literal
|
||||
/// xmlns attributes (xsl:copy copies namespace nodes, 7.5); the fixup
|
||||
/// pass drops the ones already in scope in the result.
|
||||
|
||||
@ -302,9 +302,9 @@ extension PureXML.XSLT.Transformer {
|
||||
case .number:
|
||||
[.node(.text(numberInstruction(instruction, context)))]
|
||||
case let .comment(body):
|
||||
[.node(.comment(Self.textNodesOnly(of: instantiate(body, context))))]
|
||||
[.node(.comment(escapedTextValue(of: instantiate(body, context))))]
|
||||
case let .processingInstruction(name, body):
|
||||
[.node(.processingInstruction(target: avt(name, context), data: Self.textNodesOnly(of: instantiate(body, context))))]
|
||||
[.node(.processingInstruction(target: avt(name, context), data: escapedTextValue(of: instantiate(body, context))))]
|
||||
case let .message(terminate, body):
|
||||
message(terminate, body, context)
|
||||
case .applyImports:
|
||||
|
||||
@ -30,7 +30,6 @@ output/output26
|
||||
output/output63
|
||||
output/output70
|
||||
output/output73
|
||||
output/output75
|
||||
output/output86
|
||||
position/position106
|
||||
processorinfo/processorinfo03
|
||||
|
||||
@ -37,6 +37,23 @@ struct XSLTOutputMethodTests {
|
||||
#expect(try PureXML.XSLT.transform(stylesheet: style, source: "<x/>") == "<out>a <b> c</out>")
|
||||
}
|
||||
|
||||
@Test("disable-output-escaping is ignored for an attribute value (16.4)")
|
||||
func test_disableOutputEscapingIgnoredOffTextNode() throws {
|
||||
// 16.4: disabling escaping for a value used as an attribute (not a text
|
||||
// node) is an error; the recovery ignores it, so the attribute escapes
|
||||
// `<`/`>` while a sibling text node keeps it raw (Apache Xalan output75).
|
||||
let body = "<e><xsl:attribute name=\"a\">"
|
||||
+ "<xsl:text disable-output-escaping=\"yes\"><x></xsl:text></xsl:attribute>"
|
||||
+ "<xsl:text disable-output-escaping=\"yes\"><t></xsl:text></e>"
|
||||
let style = """
|
||||
<xsl:stylesheet version="1.0" \(xsl)>
|
||||
<xsl:output omit-xml-declaration="yes"/>
|
||||
<xsl:template match="/">\(body)</xsl:template>
|
||||
</xsl:stylesheet>
|
||||
"""
|
||||
#expect(try PureXML.XSLT.transform(stylesheet: style, source: "<x/>") == "<e a=\"<x>\"><t></e>")
|
||||
}
|
||||
|
||||
@Test("Source text holding the private-use marker characters is untouched")
|
||||
func test_markerCharactersInSource() throws {
|
||||
let style = """
|
||||
|
||||
Loading…
Reference in New Issue
Block a user