use std::ops::RangeInclusive;
use bladvak::eframe::egui::{self, CollapsingHeader};
use roxmltree::{Document, Node};
#[derive(Debug)]
pub(crate) struct XmlData {
inner: String,
}
impl XmlData {
pub(crate) fn parse(binary_data: &[u8]) -> Self {
let xml_str = String::from_utf8_lossy(binary_data);
Self {
inner: xml_str.to_string(),
}
}
}
pub fn xml_tree_ui(ui: &mut egui::Ui, xml: Option<&XmlData>) -> Option<RangeInclusive<usize>> {
let Some(xml) = xml else {
ui.label("Failed to parse xml");
return None;
};
let mut return_range = None;
match Document::parse(&xml.inner) {
Ok(doc) => {
let root = doc.root_element();
return_range = draw_node(ui, root, 0);
}
Err(err) => {
ui.colored_label(egui::Color32::RED, err.to_string());
}
}
return_range
}
fn draw_node(ui: &mut egui::Ui, node: Node<'_, '_>, idx: usize) -> Option<RangeInclusive<usize>> {
let mut count = idx;
let mut return_range = None;
match node.node_type() {
roxmltree::NodeType::Element => {
let label = format_element_label(node);
count += 1;
CollapsingHeader::new(&label)
.id_salt(format!("{label}-{idx}-{count}"))
.default_open(false)
.show(ui, |ui| {
let range = node.range();
if ui
.button(format!("Position {}-{}", range.start, range.end - 1))
.clicked()
{
return_range = Some(range.start..=range.end - 1);
}
for attr in node.attributes() {
ui.label(format!("@{}=\"{}\"", attr.name(), attr.value()));
}
for child in node.children() {
count += 1;
if let Some(range) = draw_node(ui, child, count) {
return_range = Some(range);
}
}
});
}
roxmltree::NodeType::Text => {
let text = node.text().unwrap_or("").trim();
if !text.is_empty() {
ui.label(text);
}
}
roxmltree::NodeType::Comment => {
ui.label(format!("<!-- {} -->", node.text().unwrap_or("")));
}
_ => {}
}
return_range
}
fn format_element_label(node: Node<'_, '_>) -> String {
use std::fmt::Write;
let mut label = format!("<{}", node.tag_name().name());
if let Some(id) = node.attribute("id")
&& write!(label, " id=\"{id}\"").is_err()
{
return label;
}
label.push('>');
label
}