{"record":{"id":"69cbc4d7686f026c","repo":"flxzt/rnote","slug":"failed-to-parse-tool-attribute-in-xoppstroke-wit","errorCode":null,"errorMessage":"failed to parse `tool` attribute in XoppStroke with node id {:?}, could not find attribute","messagePattern":"failed to parse `tool` attribute in XoppStroke with node id (.+?), could not find attribute","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/rnote-engine/src/fileformats/xoppformat.rs","lineNumber":704,"sourceCode":"    ///\n    /// The first element is the width of the entire stroke, and if existent,\n    /// every following is a absolute width for the corresponding coordinate.\n    /// If they don't exist, the stroke has the first width as constant width.\n    pub width: Vec<f64>,\n    /// The stroke coordinates.\n    ///\n    /// As points where the vector (1.0, 0.0) has length 1/72 inch.\n    pub coords: Vec<Vector2>,\n    /// Optional timestamp.\n    pub timestamp: Option<u64>,\n    /// Optional audio filename.\n    pub audio_filename: Option<String>,\n}\n\nimpl XmlLoadable for XoppStroke {\n    fn load_from_xml(&mut self, node: Node) -> anyhow::Result<()> {\n        match node.attribute(\"tool\").ok_or_else(|| {\n            anyhow::anyhow!(\n                \"failed to parse `tool` attribute in XoppStroke with node id {:?}, could not find attribute\",\n                node.id()\n            )\n        })? {\n            \"pen\" => {\n                self.tool = XoppTool::Pen;\n            }\n            \"highlighter\" => {\n                self.tool = XoppTool::Highlighter;\n            }\n            \"eraser\" => {\n                self.tool = XoppTool::Eraser;\n            }\n            _ => {}\n        }\n\n        self.color =\n            XoppColor::from_strokecolor_attr_value(node.attribute(\"color\").ok_or_else(|| {","sourceCodeStart":686,"sourceCodeEnd":722,"githubUrl":"https://github.com/flxzt/rnote/blob/bbc5354502ba2fc83eec2670b535348825e679a6/crates/rnote-engine/src/fileformats/xoppformat.rs#L686-L722","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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++)."],"exampleFix":"// before (file XML)\n<stroke width=\"2.26\" color=\"black\">...</stroke>\n// after (file XML)\n<stroke tool=\"pen\" width=\"2.26\" color=\"black\">...</stroke>","handlingStrategy":"validation","validationCode":"fn stroke_elements_have_tool(xml: &str) -> Result<(), String> {\n    for (i, chunk) in xml.split(\"<stroke\").skip(1).enumerate() {\n        let head = chunk.split('>').next().unwrap_or(\"\");\n        if !head.contains(\"tool=\") {\n            return Err(format!(\"stroke #{i} missing `tool` attribute\"));\n        }\n    }\n    Ok(())\n}","typeGuard":"fn stroke_attrs_ok(attrs: &[(&str, &str)]) -> bool {\n    attrs.iter().any(|(k, _)| *k == \"tool\")\n}","tryCatchPattern":"match import_xopp(path) {\n    Err(e) if e.to_string().contains(\"`tool` attribute in XoppStroke\") => {\n        eprintln!(\"File has a stroke without a tool attribute; repair or re-save in Xournal++: {e:#}\");\n    }\n    Err(e) => eprintln!(\"import failed: {e:#}\"),\n    Ok(doc) => { /* proceed */ }\n}","preventionTips":["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)"],"tags":["parsing","xml","missing-attribute","xopp"],"backgroundTag":"missing-required-argument","analyzedSha":"bbc5354502ba2fc83eec2670b535348825e679a6","analyzedAt":"2026-09-08T13:20:33.747Z","contentChangedAt":"2026-09-08T13:20:33.747Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}