flxzt/rnote · error
failed to parse `tool` attribute in XoppStroke with node id
Error message
failed to parse `tool` attribute in XoppStroke with node id {:?}, could not find attribute What it means
Thrown by XoppStroke::load_from_xml when a `<stroke>` element in the imported .xopp file has no `tool` attribute at all. rnote requires the tool kind (pen, eraser, highlighter, etc.) to map the stroke to its internal stroke types, so a missing attribute aborts parsing with this anyhow error containing the XML node id.
Solutions
- Add the missing `tool` attribute to the offending `<stroke>` element (e.g. <stroke tool="pen" ...>) and retry the import.
- Regenerate the .xopp file with the tool that produced it, ensuring strokes include tool="pen"/"eraser"/"highlighter".
- Open the file in Xournal++ and re-save it to normalize/repair the XML, then import again.
- Check the file is not truncated/corrupted (compare file size or reopen in Xournal++).
Example fix
// before (file XML) <stroke width="2.26" color="black">...</stroke> // after (file XML) <stroke tool="pen" width="2.26" color="black">...</stroke>
Defensive patterns
Strategy: validation
Validate before calling
fn stroke_elements_have_tool(xml: &str) -> Result<(), String> {
for (i, chunk) in xml.split("<stroke").skip(1).enumerate() {
let head = chunk.split('>').next().unwrap_or("");
if !head.contains("tool=") {
return Err(format!("stroke #{i} missing `tool` attribute"));
}
}
Ok(())
} Type guard
fn stroke_attrs_ok(attrs: &[(&str, &str)]) -> bool {
attrs.iter().any(|(k, _)| *k == "tool")
} Try / catch
match import_xopp(path) {
Err(e) if e.to_string().contains("`tool` attribute in XoppStroke") => {
eprintln!("File has a stroke without a tool attribute; repair or re-save in Xournal++: {e:#}");
}
Err(e) => eprintln!("import failed: {e:#}"),
Ok(doc) => { /* proceed */ }
} Prevention
- Validate generated xopp XML against required stroke attributes (tool, color, width) before import
- Prefer building files through Xournal++ APIs rather than raw XML templates
- Check file integrity after downloads/syncs (partial files lose trailing attributes)
When it happens
Trigger: Importing a .xopp file whose `<stroke>` element lacks the required `tool` attribute — e.g. a hand-edited, programmatically generated, truncated, or corrupted file, or an output of a tool that wrote strokes without the tool field.
Common situations: Programmatic xopp file generation (scripts writing raw XML) that omits `tool`; files damaged by partial download/sync; hand-editing XML and deleting the attribute by accident.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to parse `color` attribute in XoppStroke with node id
- failed to parse `width` attribute in XoppStroke with node id
- failed to parse `font` attribute in XoppText with node id
- failed to parse `size` attribute in XoppText with node id
- failed to parse `x` attribute in XoppText with node id
AI-assisted analysis of flxzt/rnote@bbc5354502 (2026-09-08).
Data as JSON: /api/errors/69cbc4d7686f026c.
Report an issue: GitHub.
Appendix: source
Thrown at crates/rnote-engine/src/fileformats/xoppformat.rs:704
///
/// The first element is the width of the entire stroke, and if existent,
/// every following is a absolute width for the corresponding coordinate.
/// If they don't exist, the stroke has the first width as constant width.
pub width: Vec<f64>,
/// The stroke coordinates.
///
/// As points where the vector (1.0, 0.0) has length 1/72 inch.
pub coords: Vec<Vector2>,
/// Optional timestamp.
pub timestamp: Option<u64>,
/// Optional audio filename.
pub audio_filename: Option<String>,
}
impl XmlLoadable for XoppStroke {
fn load_from_xml(&mut self, node: Node) -> anyhow::Result<()> {
match node.attribute("tool").ok_or_else(|| {
anyhow::anyhow!(
"failed to parse `tool` attribute in XoppStroke with node id {:?}, could not find attribute",
node.id()
)
})? {
"pen" => {
self.tool = XoppTool::Pen;
}
"highlighter" => {
self.tool = XoppTool::Highlighter;
}
"eraser" => {
self.tool = XoppTool::Eraser;
}
_ => {}
}
self.color =
XoppColor::from_strokecolor_attr_value(node.attribute("color").ok_or_else(|| {View on GitHub (pinned to bbc5354502)