PureXML/Sources/Canonical/CanonicalEscaping.swift
Mihaela Mihaljevic ddfe14370c feat(canonical): C14N 1.0 spec-example vectors, three conformance fixes
The six worked examples of the C14N 1.0 Recommendation's section 3 run
as input/expected-output tests. They exposed and fixed: top-level
siblings now separated by single line feeds in the canonical form;
superfluous empty default-namespace declarations dropped in inclusive
mode (xmlns="" renders only when undeclaring a rendered non-empty
default); and scalar-level escape loops, so a CR clustered with a
following LF still escapes to 
. Escaping split to its own file
for the length caps.

Closes #132
2026-06-10 08:51:47 +02:00

40 lines
1.4 KiB
Swift

/// The C14N escaping rules, split from the canonicalizer body to keep it
/// under the length caps: text escapes & < > and CR; attribute values escape
/// & < " TAB LF CR. Both iterate Unicode scalars so a CR that clustered with
/// a following LF is still escaped.
extension PureXML.Canonical.Canonicalizer {
// MARK: Escaping
static func escapeText(_ value: String) -> String {
// Scalar-level: a CR that clustered with a following LF must still
// be escaped, so the loop cannot compare grapheme Characters.
var result = ""
for scalar in value.unicodeScalars {
switch scalar {
case "&": result += "&amp;"
case "<": result += "&lt;"
case ">": result += "&gt;"
case "\r": result += "&#xD;"
default: result.unicodeScalars.append(scalar)
}
}
return result
}
static func escapeAttribute(_ value: String) -> String {
var result = ""
for scalar in value.unicodeScalars {
switch scalar {
case "&": result += "&amp;"
case "<": result += "&lt;"
case "\"": result += "&quot;"
case "\t": result += "&#x9;"
case "\n": result += "&#xA;"
case "\r": result += "&#xD;"
default: result.unicodeScalars.append(scalar)
}
}
return result
}
}