use quick_xml::escape::resolve_predefined_entity;
use quick_xml::events::{BytesRef, BytesText};
use quick_xml::XmlVersion;
pub(crate) fn decode_text(t: &BytesText) -> String {
t.xml_content(XmlVersion::Implicit1_0)
.map(|c| c.into_owned())
.unwrap_or_default()
}
pub(crate) fn resolve_general_ref(r: &BytesRef) -> String {
let name = match r.decode() {
Ok(n) => n,
Err(_) => return String::new(),
};
if let Some(num) = name.strip_prefix('#') {
let codepoint = match num.strip_prefix(['x', 'X']) {
Some(hex) => u32::from_str_radix(hex, 16).ok(),
None => num.parse::<u32>().ok(),
};
return codepoint
.and_then(char::from_u32)
.map(String::from)
.unwrap_or_default();
}
if let Some(value) = resolve_predefined_entity(&name) {
return value.to_string();
}
format!("&{name};")
}
#[cfg(test)]
mod tests {
use super::*;
use quick_xml::events::BytesRef;
fn resolve(name: &str) -> String {
resolve_general_ref(&BytesRef::new(name))
}
#[test]
fn resolves_predefined_entities() {
assert_eq!(resolve("amp"), "&");
assert_eq!(resolve("lt"), "<");
assert_eq!(resolve("gt"), ">");
assert_eq!(resolve("quot"), "\"");
assert_eq!(resolve("apos"), "'");
}
#[test]
fn resolves_numeric_character_references() {
assert_eq!(resolve("#48"), "0"); assert_eq!(resolve("#x41"), "A"); assert_eq!(resolve("#X41"), "A"); assert_eq!(resolve("#44032"), "가"); }
#[test]
fn unknown_entity_is_preserved_literally() {
assert_eq!(resolve("nbsp"), " ");
assert_eq!(resolve("custom"), "&custom;");
}
#[test]
fn malformed_references_degrade_without_panicking() {
assert_eq!(resolve("#"), ""); assert_eq!(resolve("#x"), ""); assert_eq!(resolve("#xZZ"), ""); assert_eq!(resolve("#x110000"), ""); assert_eq!(resolve("#xD800"), ""); assert_eq!(resolve("#xFFFFFFFFFFFF"), ""); }
}