From 9bef93fda30e3815efe605d762327ca801397c21 Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Fri, 13 Feb 2026 04:30:23 -0800 Subject: [PATCH 1/2] Fix hyperlink tooltip parsing and XML unescape --- src/reader/xlsx/worksheet.rs | 36 ++++++++++-------------------------- src/writer/xlsx/worksheet.rs | 35 ++++++++--------------------------- 2 files changed, 18 insertions(+), 53 deletions(-) diff --git a/src/reader/xlsx/worksheet.rs b/src/reader/xlsx/worksheet.rs index 3c995f02..650ad8d2 100644 --- a/src/reader/xlsx/worksheet.rs +++ b/src/reader/xlsx/worksheet.rs @@ -1,37 +1,18 @@ use std::collections::HashMap; -use quick_xml::{ - Reader, - events::Event, -}; +use quick_xml::{Reader, escape, events::Event}; use super::{ XlsxError, - driver::{ - get_attribute, - get_attribute_value, - xml_read_loop, - }, + driver::{get_attribute, get_attribute_value, xml_read_loop}, }; use crate::{ helper::formula::FormulaToken, structs::{ - Cells, - Columns, - ConditionalFormatting, - DataValidations, - Hyperlink, - OleObjects, - Row, - SharedStringTable, - SheetProtection, - Stylesheet, - Worksheet, + Cells, Columns, ConditionalFormatting, DataValidations, Hyperlink, OleObjects, Row, + SharedStringTable, SheetProtection, Stylesheet, Worksheet, office2010::excel::DataValidations as DataValidations2010, - raw::{ - RawRelationships, - RawWorksheet, - }, + raw::{RawRelationships, RawWorksheet}, }, }; @@ -287,12 +268,15 @@ fn get_hyperlink( let coordition = get_attribute(e, b"ref").unwrap_or_default(); if let Some(v) = get_attribute(e, b"location") { - hyperlink.set_url(v); + hyperlink.set_url(escape::unescape(&v).unwrap().to_string()); hyperlink.set_location(true); } + if let Some(v) = get_attribute(e, b"tooltip") { + hyperlink.set_tooltip(escape::unescape(&v).unwrap().to_string()); + } if let Some(v) = get_attribute(e, b"r:id") { let relationship = raw_relationships.unwrap().relationship_by_rid(&v); - hyperlink.set_url(relationship.target()); + hyperlink.set_url(escape::unescape(relationship.target()).unwrap().to_string()); } (coordition, hyperlink) } diff --git a/src/writer/xlsx/worksheet.rs b/src/writer/xlsx/worksheet.rs index 54e8099e..a0291db4 100644 --- a/src/writer/xlsx/worksheet.rs +++ b/src/writer/xlsx/worksheet.rs @@ -1,43 +1,21 @@ -use std::{ - collections::HashMap, - io, - sync::RwLock, -}; +use std::{collections::HashMap, io, sync::RwLock}; use quick_xml::{ Writer, - events::{ - BytesDecl, - Event, - }, + events::{BytesDecl, Event}, }; use super::{ XlsxError, - driver::{ - write_end_tag, - write_new_line, - write_start_tag, - }, + driver::{write_end_tag, write_new_line, write_start_tag}, }; use crate::{ Row, helper::const_str::{ - MC_NS, - PKG_SHEET, - REL_OFC_NS, - SHEET_DRAWING_NS, - SHEET_MAIN_NS, - SHEET_MS_MAIN_NS, + MC_NS, PKG_SHEET, REL_OFC_NS, SHEET_DRAWING_NS, SHEET_MAIN_NS, SHEET_MS_MAIN_NS, SHEETML_AC_NS, }, - structs::{ - Cell, - SharedStringTable, - Stylesheet, - Worksheet, - WriterManager, - }, + structs::{Cell, SharedStringTable, Stylesheet, Worksheet, WriterManager}, }; type InternalWriter = Writer>>; @@ -418,6 +396,9 @@ fn write_hyperlinks(writer: &mut InternalWriter, worksheet: &Worksheet) -> i32 { attributes.push(("r:id", &r_id_str).into()); r_id += 1; } + if !hyperlink.tooltip().is_empty() { + attributes.push(("tooltip", hyperlink.tooltip()).into()); + } write_start_tag(writer, "hyperlink", attributes, true); } From 4112b8670afaf889cd6fa0f28bb91582e37c92f3 Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Fri, 13 Feb 2026 14:42:21 -0800 Subject: [PATCH 2/2] fix: parse unprefixed drawing tags --- src/reader/xlsx/drawing.rs | 2 +- src/structs/drawing/spreadsheet/blip_fill.rs | 4 +- .../drawing/spreadsheet/marker_type.rs | 36 +++++--------- .../non_visual_drawing_properties.rs | 4 +- .../non_visual_picture_drawing_properties.rs | 4 +- .../non_visual_picture_properties.rs | 12 ++--- .../drawing/spreadsheet/one_cell_anchor.rs | 14 +++--- src/structs/drawing/spreadsheet/picture.rs | 10 ++-- .../drawing/spreadsheet/shape_properties.rs | 4 +- .../drawing/spreadsheet/two_cell_anchor.rs | 18 +++---- .../drawing/spreadsheet/worksheet_drawing.rs | 49 +++++-------------- 11 files changed, 61 insertions(+), 96 deletions(-) diff --git a/src/reader/xlsx/drawing.rs b/src/reader/xlsx/drawing.rs index 538e4e2a..5a67a5e5 100644 --- a/src/reader/xlsx/drawing.rs +++ b/src/reader/xlsx/drawing.rs @@ -27,7 +27,7 @@ pub(crate) fn read( xml_read_loop!( reader, Event::Start(ref e) => { - if e.name().into_inner() == b"xdr:wsDr" { + if matches!(e.name().into_inner(), b"xdr:wsDr" | b"wsDr") { let mut obj = WorksheetDrawing::default(); obj.set_attributes( &mut reader, diff --git a/src/structs/drawing/spreadsheet/blip_fill.rs b/src/structs/drawing/spreadsheet/blip_fill.rs index 5f20cb3d..83e6c55e 100644 --- a/src/structs/drawing/spreadsheet/blip_fill.rs +++ b/src/structs/drawing/spreadsheet/blip_fill.rs @@ -184,11 +184,11 @@ impl BlipFill { } }, Event::End(ref e) => { - if e.name().into_inner() == b"xdr:blipFill" { + if matches!(e.name().into_inner(), b"xdr:blipFill" | b"blipFill") { return; } }, - Event::Eof => panic!("Error: Could not find {} end element", "xdr:blipFill") + Event::Eof => panic!("Error: Could not find {} end element", "blipFill") ); } diff --git a/src/structs/drawing/spreadsheet/marker_type.rs b/src/structs/drawing/spreadsheet/marker_type.rs index d4dce1a7..b97b1b19 100644 --- a/src/structs/drawing/spreadsheet/marker_type.rs +++ b/src/structs/drawing/spreadsheet/marker_type.rs @@ -2,35 +2,24 @@ use std::io::Cursor; use quick_xml::{ - Reader, - Writer, - events::{ - BytesStart, - Event, - }, + Reader, Writer, + events::{BytesStart, Event}, }; use crate::{ helper::coordinate::{ - adjustment_insert_coordinate, - adjustment_remove_coordinate, - coordinate_from_index, - index_from_coordinate, - is_remove_coordinate, + adjustment_insert_coordinate, adjustment_remove_coordinate, coordinate_from_index, + index_from_coordinate, is_remove_coordinate, }, traits::AdjustmentCoordinate, - writer::driver::{ - write_end_tag, - write_start_tag, - write_text_node, - }, + writer::driver::{write_end_tag, write_start_tag, write_text_node}, }; #[derive(Clone, Default, Debug)] pub struct MarkerType { - col: u32, + col: u32, col_off: i32, - row: u32, + row: u32, row_off: i32, } impl MarkerType { @@ -153,19 +142,20 @@ impl MarkerType { match reader.read_event_into(&mut buf) { Ok(Event::Text(e)) => string_value = e.unescape().unwrap().to_string(), Ok(Event::End(ref e)) => match e.name().into_inner() { - b"xdr:col" => { + b"xdr:col" | b"col" => { self.col = string_value.parse::().unwrap(); } - b"xdr:colOff" => { + b"xdr:colOff" | b"colOff" => { self.col_off = string_value.parse::().unwrap(); } - b"xdr:row" => { + b"xdr:row" | b"row" => { self.row = string_value.parse::().unwrap(); } - b"xdr:rowOff" => { + b"xdr:rowOff" | b"rowOff" => { self.row_off = string_value.parse::().unwrap(); } - b"xdr:from" | b"xdr:to" => return, + b"xdr:from" | b"from" => return, + b"xdr:to" | b"to" => return, _ => (), }, Ok(Event::Eof) => { diff --git a/src/structs/drawing/spreadsheet/non_visual_drawing_properties.rs b/src/structs/drawing/spreadsheet/non_visual_drawing_properties.rs index e979a281..3450b9cd 100644 --- a/src/structs/drawing/spreadsheet/non_visual_drawing_properties.rs +++ b/src/structs/drawing/spreadsheet/non_visual_drawing_properties.rs @@ -110,11 +110,11 @@ impl NonVisualDrawingProperties { xml_read_loop!( reader, Event::End(ref e) => { - if e.name().into_inner() == b"xdr:cNvPr" { + if matches!(e.name().into_inner(), b"xdr:cNvPr" | b"cNvPr") { return; } }, - Event::Eof => panic!("Error: Could not find {} end element", "xdr:cNvPr") + Event::Eof => panic!("Error: Could not find {} end element", "cNvPr") ); } diff --git a/src/structs/drawing/spreadsheet/non_visual_picture_drawing_properties.rs b/src/structs/drawing/spreadsheet/non_visual_picture_drawing_properties.rs index ebbfffd4..2873dd23 100644 --- a/src/structs/drawing/spreadsheet/non_visual_picture_drawing_properties.rs +++ b/src/structs/drawing/spreadsheet/non_visual_picture_drawing_properties.rs @@ -100,11 +100,11 @@ impl NonVisualPictureDrawingProperties { } }, Event::End(ref e) => { - if e.name().into_inner() == b"xdr:cNvPicPr" { + if matches!(e.name().into_inner(), b"xdr:cNvPicPr" | b"cNvPicPr") { return } }, - Event::Eof => panic!("Error: Could not find {} end element", "xdr:cNvPicPr") + Event::Eof => panic!("Error: Could not find {} end element", "cNvPicPr") ); } diff --git a/src/structs/drawing/spreadsheet/non_visual_picture_properties.rs b/src/structs/drawing/spreadsheet/non_visual_picture_properties.rs index b3bb03b0..f1bb60e8 100644 --- a/src/structs/drawing/spreadsheet/non_visual_picture_properties.rs +++ b/src/structs/drawing/spreadsheet/non_visual_picture_properties.rs @@ -106,11 +106,11 @@ impl NonVisualPictureProperties { reader, Event::Start(ref e) => { match e.name().into_inner() { - b"xdr:cNvPicPr" => { + b"xdr:cNvPicPr" | b"cNvPicPr" => { self.non_visual_picture_drawing_properties .set_attributes(reader, e, false); } - b"xdr:cNvPr" => { + b"xdr:cNvPr" | b"cNvPr" => { self.non_visual_drawing_properties .set_attributes(reader, e, false); } @@ -119,11 +119,11 @@ impl NonVisualPictureProperties { }, Event::Empty(ref e) => { match e.name().into_inner() { - b"xdr:cNvPicPr" => { + b"xdr:cNvPicPr" | b"cNvPicPr" => { self.non_visual_picture_drawing_properties .set_attributes(reader, e, true); } - b"xdr:cNvPr" => { + b"xdr:cNvPr" | b"cNvPr" => { self.non_visual_drawing_properties .set_attributes(reader, e, true); } @@ -131,11 +131,11 @@ impl NonVisualPictureProperties { } }, Event::End(ref e) => { - if e.name().into_inner() == b"xdr:nvPicPr" { + if matches!(e.name().into_inner(), b"xdr:nvPicPr" | b"nvPicPr") { return; } }, - Event::Eof => panic!("Error: Could not find {} end element", "xdr:nvPicPr") + Event::Eof => panic!("Error: Could not find {} end element", "nvPicPr") ); } diff --git a/src/structs/drawing/spreadsheet/one_cell_anchor.rs b/src/structs/drawing/spreadsheet/one_cell_anchor.rs index 8f7ece90..86348d92 100644 --- a/src/structs/drawing/spreadsheet/one_cell_anchor.rs +++ b/src/structs/drawing/spreadsheet/one_cell_anchor.rs @@ -202,20 +202,20 @@ impl OneCellAnchor { reader, Event::Start(ref e) => { match e.name().into_inner() { - b"xdr:from" => { + b"xdr:from" | b"from" => { self.from_marker.set_attributes(reader, e); } - b"xdr:grpSp" => { + b"xdr:grpSp" | b"grpSp" => { let mut obj = GroupShape::default(); obj.set_attributes(reader, e, drawing_relationships); self.set_group_shape(obj); } - b"xdr:sp" => { + b"xdr:sp" | b"sp" => { let mut obj = Shape::default(); obj.set_attributes(reader, e, drawing_relationships); self.set_shape(obj); } - b"xdr:pic" => { + b"xdr:pic" | b"pic" => { let mut obj = Picture::default(); obj.set_attributes(reader, e, drawing_relationships); self.set_picture(obj); @@ -224,16 +224,16 @@ impl OneCellAnchor { } }, Event::Empty(ref e) => { - if e.name().into_inner() == b"xdr:ext" { + if matches!(e.name().into_inner(), b"xdr:ext" | b"ext") { self.extent.set_attributes(reader, e); } }, Event::End(ref e) => { - if e.name().into_inner() == b"xdr:oneCellAnchor" { + if matches!(e.name().into_inner(), b"xdr:oneCellAnchor" | b"oneCellAnchor") { return } }, - Event::Eof => panic!("Error: Could not find {} end element", "xdr:oneCellAnchor") + Event::Eof => panic!("Error: Could not find {} end element", "oneCellAnchor") ); } diff --git a/src/structs/drawing/spreadsheet/picture.rs b/src/structs/drawing/spreadsheet/picture.rs index ef3b10d4..d5883446 100644 --- a/src/structs/drawing/spreadsheet/picture.rs +++ b/src/structs/drawing/spreadsheet/picture.rs @@ -129,25 +129,25 @@ impl Picture { reader, Event::Start(ref e) => { match e.name().into_inner() { - b"xdr:nvPicPr" => { + b"xdr:nvPicPr" | b"nvPicPr" => { self.non_visual_picture_properties.set_attributes(reader, e); } - b"xdr:blipFill" => { + b"xdr:blipFill" | b"blipFill" => { self.blip_fill .set_attributes(reader, e, drawing_relationships); } - b"xdr:spPr" => { + b"xdr:spPr" | b"spPr" => { self.shape_properties.set_attributes(reader, e, drawing_relationships); } _ => (), } }, Event::End(ref e) => { - if e.name().into_inner() == b"xdr:pic" { + if matches!(e.name().into_inner(), b"xdr:pic" | b"pic") { return; } }, - Event::Eof => panic!("Error: Could not find {} end element", "xdr:pic") + Event::Eof => panic!("Error: Could not find {} end element", "pic") ); } diff --git a/src/structs/drawing/spreadsheet/shape_properties.rs b/src/structs/drawing/spreadsheet/shape_properties.rs index 12ce3b0f..84cc546a 100644 --- a/src/structs/drawing/spreadsheet/shape_properties.rs +++ b/src/structs/drawing/spreadsheet/shape_properties.rs @@ -402,11 +402,11 @@ impl ShapeProperties { } }, Event::End(ref e) => { - if e.name().into_inner() == b"xdr:spPr" { + if matches!(e.name().into_inner(), b"xdr:spPr" | b"spPr") { return; } }, - Event::Eof => panic!("Error: Could not find {} end element", "xdr:spPr") + Event::Eof => panic!("Error: Could not find {} end element", "spPr") ); } diff --git a/src/structs/drawing/spreadsheet/two_cell_anchor.rs b/src/structs/drawing/spreadsheet/two_cell_anchor.rs index 42705534..4b4fbca4 100644 --- a/src/structs/drawing/spreadsheet/two_cell_anchor.rs +++ b/src/structs/drawing/spreadsheet/two_cell_anchor.rs @@ -342,33 +342,33 @@ impl TwoCellAnchor { reader, Event::Start(ref e) => { match e.name().into_inner() { - b"xdr:from" => { + b"xdr:from" | b"from" => { self.from_marker.set_attributes(reader, e); } - b"xdr:to" => { + b"xdr:to" | b"to" => { self.to_marker.set_attributes(reader, e); } - b"xdr:grpSp" => { + b"xdr:grpSp" | b"grpSp" => { let mut obj = GroupShape::default(); obj.set_attributes(reader, e, drawing_relationships); self.set_group_shape(obj); } - b"xdr:graphicFrame" => { + b"xdr:graphicFrame" | b"graphicFrame" => { let mut obj = GraphicFrame::default(); obj.set_attributes(reader, e, drawing_relationships); self.set_graphic_frame(obj); } - b"xdr:sp" => { + b"xdr:sp" | b"sp" => { let mut obj = Shape::default(); obj.set_attributes(reader, e, drawing_relationships); self.set_shape(obj); } - b"xdr:cxnSp" => { + b"xdr:cxnSp" | b"cxnSp" => { let mut obj = ConnectionShape::default(); obj.set_attributes(reader, e, drawing_relationships); self.set_connection_shape(obj); } - b"xdr:pic" => { + b"xdr:pic" | b"pic" => { let mut obj = Picture::default(); obj.set_attributes(reader, e, drawing_relationships); self.set_picture(obj); @@ -377,11 +377,11 @@ impl TwoCellAnchor { } }, Event::End(ref e) => { - if e.name().into_inner() == b"xdr:twoCellAnchor" { + if matches!(e.name().into_inner(), b"xdr:twoCellAnchor" | b"twoCellAnchor") { return } }, - Event::Eof => panic!("Error: Could not find {} end element", "xdr:twoCellAnchor") + Event::Eof => panic!("Error: Could not find {} end element", "twoCellAnchor") ); } diff --git a/src/structs/drawing/spreadsheet/worksheet_drawing.rs b/src/structs/drawing/spreadsheet/worksheet_drawing.rs index 9ef22ef2..6b5cdbd9 100644 --- a/src/structs/drawing/spreadsheet/worksheet_drawing.rs +++ b/src/structs/drawing/spreadsheet/worksheet_drawing.rs @@ -2,48 +2,23 @@ use std::io::Cursor; use quick_xml::{ - Reader, - Writer, - events::{ - BytesStart, - Event, - }, + Reader, Writer, + events::{BytesStart, Event}, }; -use super::{ - ConnectionShape, - GraphicFrame, - OneCellAnchor, - Picture, - Shape, - TwoCellAnchor, -}; +use super::{ConnectionShape, GraphicFrame, OneCellAnchor, Picture, Shape, TwoCellAnchor}; use crate::{ - helper::const_str::{ - DRAWINGML_MAIN_NS, - SHEET_DRAWING_NS, - }, + helper::const_str::{DRAWINGML_MAIN_NS, SHEET_DRAWING_NS}, reader::driver::xml_read_loop, - structs::{ - Chart, - Image, - OleObjects, - raw::RawRelationships, - }, - traits::{ - AdjustmentCoordinate, - AdjustmentCoordinateWithSheet, - }, - writer::driver::{ - write_end_tag, - write_start_tag, - }, + structs::{Chart, Image, OleObjects, raw::RawRelationships}, + traits::{AdjustmentCoordinate, AdjustmentCoordinateWithSheet}, + writer::driver::{write_end_tag, write_start_tag}, }; #[derive(Clone, Default, Debug)] pub struct WorksheetDrawing { - image_collection: Vec, - chart_collection: Vec, + image_collection: Vec, + chart_collection: Vec, one_cell_anchor_collection: Vec, two_cell_anchor_collection: Vec, } @@ -448,7 +423,7 @@ impl WorksheetDrawing { b"mc:AlternateContent" => { is_alternate_content = true; } - b"xdr:oneCellAnchor" => { + b"xdr:oneCellAnchor" | b"oneCellAnchor" => { if is_alternate_content { continue; } @@ -462,7 +437,7 @@ impl WorksheetDrawing { self.add_one_cell_anchor_collection(obj); } } - b"xdr:twoCellAnchor" => { + b"xdr:twoCellAnchor" | b"twoCellAnchor" => { let os = ole_objects.ole_object_mut(); if is_alternate_content && !os.is_empty() { os[ole_index] @@ -501,7 +476,7 @@ impl WorksheetDrawing { b"mc:AlternateContent" => { is_alternate_content = false; } - b"xdr:wsDr" => return, + b"xdr:wsDr" | b"wsDr" => return, _ => (), } },