use crate::error::{Result, TemplateError};
use roxmltree::Document;
use std::rc::Rc;
#[derive(Debug, Clone)]
pub(crate) struct DoctypeInfo {
pub(crate) name: String,
pub(crate) raw: String,
}
pub(crate) struct XmlTree {
pub(crate) doc: Document<'static>,
_text: Rc<str>,
pub(crate) doctype: Option<DoctypeInfo>,
}
impl XmlTree {
pub(crate) fn parse(text: &str) -> Result<Rc<XmlTree>> {
let doctype = scan_doctype(text);
let parse_text = if let Some(ref dt) = doctype {
text.replace(&dt.raw, "")
} else {
text.to_string()
};
let text: Rc<str> = Rc::from(parse_text);
let doc = Document::parse(&text)
.map_err(|e| TemplateError::misc(format!("XML parsing failed: {e}")))?;
let doc: Document<'static> = unsafe { std::mem::transmute(doc) };
Ok(Rc::new(XmlTree {
doc,
_text: text,
doctype,
}))
}
}
fn scan_doctype(text: &str) -> Option<DoctypeInfo> {
let upper = text.to_uppercase();
let start = upper.find("<!DOCTYPE")?;
let rest = &text[start..];
let end = rest.find('>')?;
let raw = rest[..=end].to_string();
let after_keyword = &raw["<!DOCTYPE".len()..];
let after_keyword = after_keyword.trim_start();
let name = after_keyword
.split_whitespace()
.next()
.unwrap_or("")
.trim_end_matches('>')
.to_string();
if name.is_empty() {
return None;
}
Some(DoctypeInfo { name, raw })
}