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.
83 lines
3.5 KiB
Swift
83 lines
3.5 KiB
Swift
extension PureXML.Emitting {
|
|
/// The shared XML output escaping rules, used by both the tree serializer and
|
|
/// the incremental writer so their output is identical. Element content
|
|
/// escapes the markup characters; attribute values additionally escape the
|
|
/// quote and the whitespace characters as numeric references (matching
|
|
/// libxml2), so attributes survive attribute-value normalization.
|
|
enum Escaping {
|
|
static func text(_ value: String, asciiOnly: Bool = false, escapeCarriageReturn: Bool = false) -> String {
|
|
var result = ""
|
|
for character in value {
|
|
switch character {
|
|
case "&": result += "&"
|
|
case "<": result += "<"
|
|
case ">": result += ">"
|
|
case "\r" where escapeCarriageReturn: result += "
"
|
|
default: result += plain(character, asciiOnly: asciiOnly)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
/// A character verbatim, or, in ASCII-only mode, each of its non-ASCII
|
|
/// scalars as a numeric character reference so the output is pure ASCII.
|
|
private static func plain(_ character: Character, asciiOnly: Bool) -> String {
|
|
guard asciiOnly else { return String(character) }
|
|
var result = ""
|
|
for scalar in character.unicodeScalars {
|
|
result += scalar.value > 0x7F ? "&#x\(String(scalar.value, radix: 16, uppercase: true));" : String(scalar)
|
|
}
|
|
return result
|
|
}
|
|
|
|
static func attribute(_ value: String, quote: Character = "\"", asciiOnly: Bool = false) -> String {
|
|
var result = ""
|
|
for character in value {
|
|
if character == quote {
|
|
result += quote == "'" ? "'" : """
|
|
continue
|
|
}
|
|
switch character {
|
|
case "&": result += "&"
|
|
case "<": result += "<"
|
|
case ">": result += ">"
|
|
case "\t": result += "	"
|
|
case "\n": result += " "
|
|
case "\r": result += " "
|
|
default: result += plain(character, asciiOnly: asciiOnly)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
/// Makes `value` safe inside a comment: a comment may not contain `--`
|
|
/// or end with `-`, so a space is inserted after each offending hyphen
|
|
/// (the XSLT 1.0 16 recovery, so `after-` becomes `after- `).
|
|
static func comment(_ value: String) -> String {
|
|
var result = ""
|
|
var previousHyphen = false
|
|
for character in value {
|
|
if character == "-", previousHyphen { result.append(" ") }
|
|
result.append(character)
|
|
previousHyphen = character == "-"
|
|
}
|
|
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
|
|
}
|
|
}
|
|
}
|