use std::collections::HashMap;
use std::str::FromStr;
use quick_xml::events::BytesStart;
use quick_xml::reader::Reader;
use crate::units::Emu;
pub fn escape(s: &str) -> String {
crate::escape::xml_escape(s)
}
pub fn unescape(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut i = 0;
let bytes = s.as_bytes();
while i < bytes.len() {
if bytes[i] == b'&' {
if let Some(end) = s[i..].find(';') {
let entity = &s[i + 1..i + end];
let replacement = match entity {
"amp" => Some('&'),
"lt" => Some('<'),
"gt" => Some('>'),
"quot" => Some('"'),
"apos" => Some('\''),
_ => None,
};
if let Some(c) = replacement {
out.push(c);
i += end + 1;
continue;
}
}
}
out.push(bytes[i] as char);
i += 1;
}
out
}
pub type AttrMap = HashMap<Vec<u8>, String>;
pub fn attrs_of(e: &BytesStart<'_>) -> AttrMap {
let mut m = HashMap::new();
for a in e.attributes().flatten() {
let k = a.key.as_ref().to_vec();
let v = a
.normalized_value(quick_xml::XmlVersion::Implicit1_0)
.map(|c| c.to_string())
.unwrap_or_default();
m.insert(k, v);
}
m
}
pub fn get_attr<'a>(m: &'a AttrMap, key: &[u8]) -> Option<&'a String> {
m.get(key)
}
pub fn get_attr_or<'a>(m: &'a AttrMap, key: &[u8], default: &'a str) -> &'a str {
m.get(key).map(|s| s.as_str()).unwrap_or(default)
}
pub fn parse_i64(s: &str) -> Option<i64> {
s.parse().ok()
}
pub fn parse_u32(s: &str) -> Option<u32> {
s.parse().ok()
}
pub fn parse_u16(s: &str) -> Option<u16> {
s.parse().ok()
}
pub fn parse_u8(s: &str) -> Option<u8> {
s.parse().ok()
}
pub fn parse_f64(s: &str) -> Option<f64> {
s.parse().ok()
}
pub fn parse_bool(s: &str) -> bool {
matches!(s, "1" | "true" | "on" | "True")
}
pub fn parse_attr<T: FromStr>(m: &AttrMap, key: &[u8]) -> Option<T> {
m.get(key).and_then(|v| v.parse().ok())
}
pub fn parse_attr_or<T: FromStr>(m: &AttrMap, key: &[u8], default: T) -> T {
m.get(key).and_then(|v| v.parse().ok()).unwrap_or(default)
}
pub fn i64_to_str(v: i64) -> String {
v.to_string()
}
pub fn emu_str(v: Emu) -> String {
v.value().to_string()
}
pub fn emu_opt(v: Option<Emu>) -> String {
v.map(|x| x.value().to_string()).unwrap_or_default()
}
pub fn render_attrs(pairs: &[(&str, &str)]) -> String {
let mut s = String::new();
for (k, v) in pairs {
s.push(' ');
s.push_str(k);
s.push_str("=\"");
s.push_str(&escape(v));
s.push('"');
}
s
}
pub fn render_start(name: &[u8], attrs: &[(&str, &str)]) -> String {
let mut s = String::from("<");
s.push_str(std::str::from_utf8(name).unwrap_or("?"));
for (k, v) in attrs {
s.push(' ');
s.push_str(k);
s.push_str("=\"");
s.push_str(&escape(v));
s.push('"');
}
s.push('>');
s
}
pub fn render_empty(name: &[u8], attrs: &[(&str, &str)]) -> String {
let mut s = String::from("<");
s.push_str(std::str::from_utf8(name).unwrap_or("?"));
for (k, v) in attrs {
s.push(' ');
s.push_str(k);
s.push_str("=\"");
s.push_str(&escape(v));
s.push('"');
}
s.push_str("/>");
s
}
pub fn render_end(name: &[u8]) -> String {
let mut s = String::from("</");
s.push_str(std::str::from_utf8(name).unwrap_or("?"));
s.push('>');
s
}
pub fn collect_inner_text<R: std::io::BufRead>(
rd: &mut Reader<R>,
name: &[u8],
) -> crate::Result<String> {
use quick_xml::events::Event;
let mut depth = 0usize;
let mut out = String::new();
let mut buf = Vec::new();
loop {
match rd.read_event_into(&mut buf) {
Ok(Event::Start(e)) => {
if e.name().as_ref() == name {
depth = 1;
continue;
}
if depth > 0 {
depth += 1;
}
}
Ok(Event::End(_e)) => {
if depth > 0 {
depth -= 1;
if depth == 0 {
return Ok(out);
}
}
}
Ok(Event::Text(t)) => {
if depth > 0 {
out.push_str(&t.decode().unwrap_or_default());
}
}
Ok(Event::CData(c)) => {
if depth > 0 {
out.push_str(std::str::from_utf8(&c).unwrap_or(""));
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(crate::Error::Xml(format!("xml: {e}"))),
_ => {}
}
buf.clear();
}
Err(crate::Error::oxml(format!(
"unexpected EOF when collecting <{}>",
std::str::from_utf8(name).unwrap_or("?")
)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn escape_unescape_round() {
let s = "a < b & \"c\" 'd' > z";
let e = escape(s);
let u = unescape(&e);
assert_eq!(u, s);
}
#[test]
fn render_attrs_works() {
let s = render_attrs(&[("a", "1"), ("b", "x\"y")]);
assert_eq!(s, " a=\"1\" b=\"x"y\"");
}
}