#[derive(Debug, Clone)]
pub struct TreemapNode {
pub name: String,
pub value: Option<f64>,
pub children: Vec<TreemapNode>,
pub class_selector: Option<String>,
}
#[derive(Debug)]
pub struct TreemapDiagram {
pub title: Option<String>,
pub roots: Vec<TreemapNode>,
}
pub fn parse(input: &str) -> crate::error::ParseResult<TreemapDiagram> {
let mut title: Option<String> = None;
let mut items: Vec<(usize, String, Option<f64>, Option<String>)> = Vec::new();
let mut in_header = true;
for raw in input.lines() {
let line = if let Some(pos) = raw.find("%%") {
&raw[..pos]
} else {
raw
};
let indent = line.len() - line.trim_start().len();
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
if in_header && (trimmed == "treemap-beta" || trimmed == "treemap") {
in_header = false;
continue;
}
if in_header {
if trimmed.starts_with("treemap") {
in_header = false;
}
continue;
}
if let Some(rest) = trimmed
.strip_prefix("title ")
.or_else(|| trimmed.strip_prefix("title\t"))
{
title = Some(rest.trim().to_string());
continue;
}
if trimmed == "title" {
title = Some(String::new());
continue;
}
if trimmed.starts_with("classDef ") {
continue;
}
if trimmed.starts_with("accTitle") || trimmed.starts_with("accDescr") {
continue;
}
let (name_part, value, class_selector) = parse_node_line(trimmed);
if !name_part.is_empty() {
items.push((indent, name_part, value, class_selector));
}
}
let roots = build_hierarchy(&items);
crate::error::ParseResult::ok(TreemapDiagram { title, roots })
}
fn strip_quotes(s: &str) -> String {
let s = s.trim();
if s.starts_with('"') && s.ends_with('"') && s.len() >= 2 {
s[1..s.len() - 1].to_string()
} else {
s.to_string()
}
}
fn parse_node_line(line: &str) -> (String, Option<f64>, Option<String>) {
let (body, class_sel) = if let Some(pos) = line.find(":::") {
(&line[..pos], Some(line[pos + 3..].trim().to_string()))
} else {
(line, None)
};
if let Some(colon) = body.find(':') {
let name = strip_quotes(body[..colon].trim());
let val_str = body[colon + 1..].trim();
let value = val_str.parse::<f64>().ok();
(name, value, class_sel)
} else {
(strip_quotes(body.trim()), None, class_sel)
}
}
fn build_hierarchy(items: &[(usize, String, Option<f64>, Option<String>)]) -> Vec<TreemapNode> {
if items.is_empty() {
return Vec::new();
}
let mut nodes: Vec<TreemapNode> = Vec::new();
let mut parents: Vec<Option<usize>> = Vec::new();
let mut stack: Vec<(usize, usize)> = Vec::new();
for (indent, name, value, class_sel) in items {
let node = TreemapNode {
name: name.clone(),
value: *value,
children: Vec::new(),
class_selector: class_sel.clone(),
};
let idx = nodes.len();
nodes.push(node);
while let Some(&(top_indent, _)) = stack.last() {
if top_indent >= *indent {
stack.pop();
} else {
break;
}
}
let parent = stack.last().map(|&(_, i)| i);
parents.push(parent);
stack.push((*indent, idx));
}
let n = nodes.len();
let mut children_map: Vec<Vec<usize>> = vec![Vec::new(); n];
for (i, &parent) in parents.iter().enumerate().take(n) {
if let Some(p) = parent {
children_map[p].push(i);
}
}
let root_indices: Vec<usize> = (0..n).filter(|&i| parents[i].is_none()).collect();
fn build_node(
idx: usize,
all_nodes: &[TreemapNode],
children_map: &[Vec<usize>],
) -> TreemapNode {
let src = &all_nodes[idx];
let children: Vec<TreemapNode> = children_map[idx]
.iter()
.map(|&ci| build_node(ci, all_nodes, children_map))
.collect();
TreemapNode {
name: src.name.clone(),
value: src.value,
children,
class_selector: src.class_selector.clone(),
}
}
root_indices
.iter()
.map(|&ri| build_node(ri, &nodes, &children_map))
.collect()
}