///|
/// Decode predefined (`&` `<` `>` `"` `'`) and numeric
/// (`NNN;`, `HHH;`) character references. Unknown or malformed
/// references are left verbatim (lenient, mirroring quick-xml defaults).
pub fn decode_entities(input : String) -> String {
let len = input.length()
let mut has_amp = false
for i in 0.. {
sb.write_string(text)
i = next
}
None => {
sb.write_char(cu('&').unsafe_to_char())
i += 1
}
}
}
sb.to_string()
}
///|
/// Try to decode one entity whose `&` sits at offset `i`.
/// Returns the replacement text and the offset just past the terminating `;`.
fn decode_one_entity(input : String, amp : Int) -> (String, Int)? {
let len = input.length()
let name_start = amp + 1
let mut j = name_start
while j < len &&
input[j].to_int() != cu(';') &&
input[j].to_int() != cu('&') &&
j - name_start < 32 {
j += 1
}
if j >= len || input[j].to_int() != cu(';') {
return None
}
let name = substring(input, name_start, j)
let semi = j + 1
if name.length() == 0 {
return None
}
// numeric references
if name[0].to_int() == cu('#') {
let cp : Int? = if name.length() > 1 &&
(name[1].to_int() == cu('x') || name[1].to_int() == cu('X')) {
parse_hex_cp(substring(name, 2, name.length()))
} else {
parse_dec_cp(substring(name, 1, name.length()))
}
match cp {
Some(cp) =>
match code_point_to_string(cp) {
Some(text) => Some((text, semi))
None => None
}
None => None
}
} else {
// named references
let repl : String? = if name is "amp" {
Some("&")
} else if name is "lt" {
Some("<")
} else if name is "gt" {
Some(">")
} else if name is "quot" {
Some("\"")
} else if name is "apos" {
Some("'")
} else {
None
}
match repl {
Some(text) => Some((text, semi))
None => None
}
}
}
///|
fn parse_dec_cp(s : String) -> Int? {
if s.length() == 0 {
return None
}
let mut v = 0
for i in 0..= cu('0') && c <= cu('9') else { return None }
v = v * 10 + (c - cu('0'))
guard v <= 0x110000 else { return None }
}
Some(v)
}
///|
fn parse_hex_cp(s : String) -> Int? {
if s.length() == 0 {
return None
}
let mut v = 0
for i in 0..= cu('0') && c <= cu('9') {
c - cu('0')
} else if c >= cu('a') && c <= cu('f') {
c - cu('a') + 10
} else if c >= cu('A') && c <= cu('F') {
c - cu('A') + 10
} else {
return None
}
v = v * 16 + digit
guard v <= 0x110000 else { return None }
}
Some(v)
}
///|
/// Convert a Unicode code point to its UTF-16 string form.
/// Rejects surrogates and out-of-range values.
fn code_point_to_string(cp : Int) -> String? {
if cp >= 0xD800 && cp <= 0xDFFF {
return None
}
if cp < 0 || cp > 0x10FFFF {
return None
}
let sb = StringBuilder()
if cp < 0x10000 {
sb.write_char(cp.unsafe_to_char())
} else {
let v = cp - 0x10000
let hi = 0xD800 + (v >> 10)
let lo = 0xDC00 + (v & 0x3FF)
sb.write_char(hi.unsafe_to_char())
sb.write_char(lo.unsafe_to_char())
}
Some(sb.to_string())
}