fix(emit): split ?> in processing-instruction data with a space (#262)
PI data may not contain ?>, which would terminate the instruction early. The serializer emitted it raw, producing malformed output. Insert a space between an offending ? and > (XSLT 1.0 7.3 recovery: a processor recovers from a generated ?> by inserting a space after the ?), via a shared Escaping helper applied at both XML PI serialization sites. A trailing ? is left alone. Closes Apache Xalan conformance case output72.
This commit is contained in:
parent
c355daa00e
commit
f1bc46e99b
@ -52,6 +52,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- 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`.
|
||||
|
||||
- The XSLT conformance harness now decodes source and stylesheet files through the library's own encoding detection (a BOM or the XML declaration) rather than assuming UTF-8, so a non-UTF-8 corpus input is fed to the transform instead of refused. This confirms the library round-trips a UTF-16 source correctly and closes Apache Xalan conformance cases `output80` (UTF-16) and `attribvaltemplate08`, which were unreadable by the harness, not library failures.
|
||||
|
||||
- The `html` output method now serializes an element whose expanded-name has a non-null namespace URI as XML rather than HTML (XSLT 1.0 16.2). When the result's document element is namespaced, the tree is serialized through the XML path (empty elements self-close, the namespace is declared once and inherited) while the html method still omits the XML declaration; previously such a tree was emitted as HTML, leaving empty namespaced elements unclosed and repeating the `xmlns` declaration on every element, which is not well-formed XML. A null-namespace tree is unchanged. The rule is applied at document-element granularity rather than per element, so a tree mixing null- and non-null-namespace elements follows its document element's path (the realistic result is all-null or all-namespaced). (Apache Xalan `output63` exercises this rule but remains baselined: its gold differs further only in attribute order, which is not normative.)
|
||||
|
||||
@ -64,5 +64,19 @@ extension PureXML.Emitting {
|
||||
if previousHyphen { result.append(" ") }
|
||||
return result
|
||||
}
|
||||
|
||||
/// Makes `data` safe inside a processing instruction: PI data may not
|
||||
/// contain `?>`, so a space is inserted between an offending `?` and `>`
|
||||
/// (the XSLT 1.0 7.3 recovery, so `a?>b` becomes `a? >b`).
|
||||
static func processingInstruction(_ data: String) -> String {
|
||||
var result = ""
|
||||
var previousQuestion = false
|
||||
for character in data {
|
||||
if character == ">", previousQuestion { result.append(" ") }
|
||||
result.append(character)
|
||||
previousQuestion = character == "?"
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,7 +87,7 @@ public extension PureXML.Emitting {
|
||||
case let .comment(value):
|
||||
output += "<!--\(Escaping.comment(value))-->"
|
||||
case let .processingInstruction(target, data):
|
||||
output += data.isEmpty ? "<?\(target)?>" : "<?\(target) \(data)?>"
|
||||
output += data.isEmpty ? "<?\(target)?>" : "<?\(target) \(Escaping.processingInstruction(data))?>"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -109,7 +109,7 @@ public extension PureXML.Emitting {
|
||||
if options.prettyPrint, !open.isEmpty {
|
||||
output += options.lineEnding + pad(open.count)
|
||||
}
|
||||
output += data.isEmpty ? "<?\(target)?>" : "<?\(target) \(data)?>"
|
||||
output += data.isEmpty ? "<?\(target)?>" : "<?\(target) \(Escaping.processingInstruction(data))?>"
|
||||
markParentHasElements()
|
||||
}
|
||||
|
||||
|
||||
@ -30,7 +30,6 @@ output/output26
|
||||
output/output32
|
||||
output/output63
|
||||
output/output70
|
||||
output/output72
|
||||
output/output73
|
||||
output/output75
|
||||
output/output86
|
||||
|
||||
@ -9,6 +9,14 @@ struct SerializerTests {
|
||||
#expect(PureXML.serialize(node, options: .compact) == "<br/>")
|
||||
}
|
||||
|
||||
@Test("A ?> inside processing-instruction data is split by a space")
|
||||
func test_processingInstructionEscapesTerminator() {
|
||||
// PI data may not contain ?>; the XSLT 1.0 7.3 recovery inserts a space
|
||||
// (Apache Xalan output72). A trailing ? is safe and is left alone.
|
||||
let instruction = PureXML.Model.Node.processingInstruction(target: "p", data: "a ?> b ?")
|
||||
#expect(PureXML.serialize(instruction, options: .compact) == "<?p a ? > b ??>")
|
||||
}
|
||||
|
||||
@Test("Attributes and text are escaped")
|
||||
func test_escapesAttributesAndText() {
|
||||
let element = PureXML.Model.Element(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user