From d316f0fc2d9685ca7b8fdfbaa4bbed1eb49ac496 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Fri, 13 Nov 2026 02:02:45 +0000 Subject: [PATCH] fix(xml): walk deep KRA and ORA trees iteratively --- crates/kra/src/lib.rs | 43 ++++++++++++++++++++++++------ crates/ora/src/lib.rs | 61 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 83 insertions(+), 21 deletions(-) diff --git a/crates/kra/src/lib.rs b/crates/kra/src/lib.rs index 7485b9dd5..12ae3468b 100644 --- a/crates/kra/src/lib.rs +++ b/crates/kra/src/lib.rs @@ -161,6 +161,16 @@ fn children<'a>(el: &'a El, container: &'static str, tag: &'static str) -> impl } fn node(el: &El, is_mask: bool, count: &mut usize) -> Result { + let mut out = node_fields(el, is_mask)?; + if !is_mask { + out.layers = layers(el, count)?; + out.masks = children(el, "masks", "mask").map(|m| node(m, true, count)).collect::>()?; + } + Ok(out) +} + +/// A node's own attributes, without its children. +fn node_fields(el: &El, is_mask: bool) -> Result { let owned = |name: &str| attr(el, name).map(str::to_string); let node_type = attr(el, "nodetype").unwrap_or("").to_string(); // Only the kinds whose pixels are read need sound numbers; a node an application will leave @@ -168,7 +178,7 @@ fn node(el: &El, is_mask: bool, count: &mut usize) -> Result { let read = matches!(node_type.as_str(), "paintlayer" | "grouplayer" | "transparencymask"); let lenient = |v: Result, default: f64| if read { v } else { Ok(v.unwrap_or(default)) }; let (x, y) = if is_mask && !read { (offset(el, "x").unwrap_or(0), offset(el, "y").unwrap_or(0)) } else { (offset(el, "x")?, offset(el, "y")?) }; - let mut out = Node { + let out = Node { node_type, name: owned("name"), visible: attr(el, "visible") != Some("0"), @@ -187,22 +197,39 @@ fn node(el: &El, is_mask: bool, count: &mut usize) -> Result { layers: Vec::new(), masks: Vec::new(), }; - if !is_mask { - out.layers = layers(el, count)?; - out.masks = children(el, "masks", "mask").map(|m| node(m, true, count)).collect::>()?; - } Ok(out) } -/// The `` of `el`. Recursion is bounded by [`MAX_XML_DEPTH`]. +/// The `` of `el`, walked with an explicit stack: groups can nest up to [`MAX_XML_DEPTH`] +/// deep, more than a small thread stack can recurse. fn layers(el: &El, count: &mut usize) -> Result> { + struct Frame<'a> { + node: Node, + rest: std::vec::IntoIter<&'a El>, + } + let mut root = children(el, "layers", "layer").collect::>().into_iter(); + let mut stack: Vec = Vec::new(); let mut out = Vec::new(); - for child in children(el, "layers", "layer") { + loop { + let next = match stack.last_mut() { + Some(frame) => frame.rest.next(), + None => root.next(), + }; + let Some(child) = next else { + let Some(frame) = stack.pop() else { break }; + match stack.last_mut() { + Some(parent) => parent.node.layers.push(frame.node), + None => out.push(frame.node), + } + continue; + }; *count += 1; if *count > MAX_NODES { return Err(invalid(format!("more than {MAX_NODES} layers"))); } - out.push(node(child, false, count)?); + let mut built = node_fields(child, false)?; + built.masks = children(child, "masks", "mask").map(|m| node(m, true, count)).collect::>()?; + stack.push(Frame { node: built, rest: children(child, "layers", "layer").collect::>().into_iter() }); } Ok(out) } diff --git a/crates/ora/src/lib.rs b/crates/ora/src/lib.rs index b2a91cc40..69d494d0d 100644 --- a/crates/ora/src/lib.rs +++ b/crates/ora/src/lib.rs @@ -189,28 +189,63 @@ fn props(el: &El) -> Result { }) } -/// The elements of `stack`. Recursion is bounded by [`MAX_XML_DEPTH`]. +/// The elements of `stack`, walked with an explicit stack: a stack can nest up to +/// [`MAX_XML_DEPTH`] deep, more than a small thread stack can recurse. fn stack(el: &El, count: &mut usize) -> Result> { + struct Frame<'a> { + props: Props, + isolation: Option, + children: Vec, + rest: std::vec::IntoIter<&'a El>, + } + let mut root = el.elements().collect::>().into_iter(); + let mut frames: Vec = Vec::new(); let mut out = Vec::new(); - for child in el.elements() { + loop { + let next = match frames.last_mut() { + Some(frame) => frame.rest.next(), + None => root.next(), + }; + let Some(child) = next else { + let Some(frame) = frames.pop() else { break }; + let node = Node::Stack(Stack { props: frame.props, isolation: frame.isolation, children: frame.children }); + match frames.last_mut() { + Some(parent) => parent.children.push(node), + None => out.push(node), + } + continue; + }; *count += 1; if *count > MAX_NODES { return Err(invalid(format!("more than {MAX_NODES} layers"))); } - out.push(match child.name.as_str() { - "layer" => Node::Layer(Layer { - props: props(child)?, - x: int(child, "x")?, - y: int(child, "y")?, - src: child.attr("src").map(|s| s.trim().to_string()), - }), - "stack" => Node::Stack(Stack { + match child.name.as_str() { + "layer" => { + let node = Node::Layer(Layer { + props: props(child)?, + x: int(child, "x")?, + y: int(child, "y")?, + src: child.attr("src").map(|s| s.trim().to_string()), + }); + match frames.last_mut() { + Some(parent) => parent.children.push(node), + None => out.push(node), + } + } + "stack" => frames.push(Frame { props: props(child)?, isolation: child.attr("isolation").map(|s| s.trim().to_string()), - children: stack(child, count)?, + children: Vec::new(), + rest: child.elements().collect::>().into_iter(), }), - other => Node::Other { tag: other.to_string(), name: child.attr("name").map(str::to_string) }, - }); + other => { + let node = Node::Other { tag: other.to_string(), name: child.attr("name").map(str::to_string) }; + match frames.last_mut() { + Some(parent) => parent.children.push(node), + None => out.push(node), + } + } + } } Ok(out) }