use roxmltree::ParsingOptions;
use typst_syntax::Spanned;
use crate::diag::{LoadError, LoadedWithin, SourceResult, format_xml_like_error};
use crate::engine::Engine;
use crate::foundations::{Array, Dict, IntoValue, Str, Value, dict, func, scope};
use crate::loading::{DataSource, Load, Readable};
#[func(scope, title = "XML")]
pub fn xml(
engine: &mut Engine,
source: Spanned<DataSource>,
) -> SourceResult<Value> {
let loaded = source.load(engine.world)?;
let text = loaded.data.as_str().within(&loaded)?;
let document = roxmltree::Document::parse_with_options(
text,
ParsingOptions { allow_dtd: true, ..Default::default() },
)
.map_err(format_xml_error)
.within(&loaded)?;
Ok(convert_xml(document.root()))
}
#[scope]
impl xml {
#[func(title = "Decode XML")]
#[deprecated(
message = "`xml.decode` is deprecated, directly pass bytes to `xml` instead",
until = "0.15.0"
)]
pub fn decode(
engine: &mut Engine,
data: Spanned<Readable>,
) -> SourceResult<Value> {
xml(engine, data.map(Readable::into_source))
}
}
fn convert_xml(node: roxmltree::Node) -> Value {
if node.is_text() {
return node.text().unwrap_or_default().into_value();
}
let children: Array = node.children().map(convert_xml).collect();
if node.is_root() {
return Value::Array(children);
}
let tag: Str = node.tag_name().name().into();
let attrs: Dict = node
.attributes()
.map(|attr| (attr.name().into(), attr.value().into_value()))
.collect();
Value::Dict(dict! {
"tag" => tag,
"attrs" => attrs,
"children" => children,
})
}
fn format_xml_error(error: roxmltree::Error) -> LoadError {
format_xml_like_error("XML", error)
}