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 } } }