From 84768cd62fb9924a5c32befb0533ce8678d08301 Mon Sep 17 00:00:00 2001 From: John McNamara Date: Sun, 21 Jul 2024 21:19:06 +0100 Subject: [PATCH] note: add initial support for cell notes/comments --- src/button.rs | 10 +- src/chart.rs | 8 +- src/comment.rs | 188 ++++++++++++++++++++++ src/content_types.rs | 9 ++ src/drawing.rs | 4 +- src/image.rs | 10 +- src/lib.rs | 4 + src/note.rs | 280 +++++++++++++++++++++++++++++++++ src/packager.rs | 51 +++++- src/styles.rs | 28 +++- src/styles/tests.rs | 12 +- src/vml.rs | 189 ++++++++++++++++++++-- src/vml/tests.rs | 10 +- src/workbook.rs | 23 ++- src/worksheet.rs | 202 ++++++++++++++++++++++-- tests/input/comment01.xlsx | Bin 0 -> 8828 bytes tests/input/comment02.xlsx | Bin 0 -> 8916 bytes tests/input/comment03.xlsx | Bin 0 -> 8955 bytes tests/input/comment04.xlsx | Bin 0 -> 11207 bytes tests/input/comment05.xlsx | Bin 0 -> 72801 bytes tests/input/comment09.xlsx | Bin 0 -> 8602 bytes tests/integration/comment01.rs | 66 ++++++++ tests/integration/comment02.rs | 39 +++++ tests/integration/comment03.rs | 38 +++++ tests/integration/comment04.rs | 45 ++++++ tests/integration/comment05.rs | 50 ++++++ tests/integration/comment09.rs | 43 +++++ tests/integration/main.rs | 6 + 28 files changed, 1270 insertions(+), 45 deletions(-) create mode 100644 src/comment.rs create mode 100644 src/note.rs create mode 100644 tests/input/comment01.xlsx create mode 100644 tests/input/comment02.xlsx create mode 100644 tests/input/comment03.xlsx create mode 100644 tests/input/comment04.xlsx create mode 100644 tests/input/comment05.xlsx create mode 100644 tests/input/comment09.xlsx create mode 100644 tests/integration/comment01.rs create mode 100644 tests/integration/comment02.rs create mode 100644 tests/integration/comment03.rs create mode 100644 tests/integration/comment04.rs create mode 100644 tests/integration/comment05.rs create mode 100644 tests/integration/comment09.rs diff --git a/src/button.rs b/src/button.rs index 21d76eee..5afc68f4 100644 --- a/src/button.rs +++ b/src/button.rs @@ -10,7 +10,7 @@ use crate::drawing::{DrawingObject, DrawingType}; use crate::vml::VmlInfo; use crate::{ObjectMovement, DEFAULT_COL_WIDTH_PIXELS, DEFAULT_ROW_HEIGHT_PIXELS}; -#[derive(Clone, Debug)] +#[derive(Clone)] /// The `Button` struct represents an worksheet button object. /// /// The `Button` struct is used to create an Excel "Form Control" button object @@ -167,7 +167,7 @@ impl Button { } /// Set the macro associated with the button. - + /// /// The `set_macro()` method can be used to associate an existing VBA macro /// with a button object. See [Working with VBA macros](crate::macros) for /// more details on macros in `rust_xlsxwriter`. @@ -263,7 +263,7 @@ impl Button { pub fn set_alt_text(mut self, alt_text: impl Into) -> Button { let alt_text = alt_text.into(); if alt_text.chars().count() > 255 { - eprintln!("Button caption is greater than Excel's limit of 255 characters."); + eprintln!("Alternative text is greater than Excel's limit of 255 characters."); return self; } @@ -305,7 +305,7 @@ impl Button { VmlInfo { width: self.width, height: self.height, - name: self.name.clone(), + text: self.name.clone(), alt_text: self.alt_text.clone(), macro_name: self.macro_name.clone(), ..Default::default() @@ -352,6 +352,6 @@ impl DrawingObject for Button { } fn drawing_type(&self) -> DrawingType { - DrawingType::Button + DrawingType::Vml } } diff --git a/src/chart.rs b/src/chart.rs index 49059b88..3a5803b0 100644 --- a/src/chart.rs +++ b/src/chart.rs @@ -2619,7 +2619,13 @@ impl Chart { /// - `alt_text`: The alt text string to add to the chart. /// pub fn set_alt_text(&mut self, alt_text: impl Into) -> &mut Chart { - self.alt_text = alt_text.into(); + let alt_text = alt_text.into(); + if alt_text.chars().count() > 255 { + eprintln!("Alternative text is greater than Excel's limit of 255 characters."); + return self; + } + + self.alt_text = alt_text; self } diff --git a/src/comment.rs b/src/comment.rs new file mode 100644 index 00000000..0856eb4f --- /dev/null +++ b/src/comment.rs @@ -0,0 +1,188 @@ +// comment - A module for creating the Excel Comment.xml file. +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright 2022-2023, John McNamara, jmcnamara@cpan.org + +#![warn(missing_docs)] + +use std::collections::BTreeMap; + +use crate::{utility, xmlwriter::XMLWriter, ColNum, Note, RowNum}; + +/// A struct to represent a Comment. +/// +/// TODO. +pub struct Comment { + pub(crate) writer: XMLWriter, + pub(crate) notes: BTreeMap>, + pub(crate) note_authors: BTreeMap, +} + +impl Comment { + // ----------------------------------------------------------------------- + // Public (and crate public) methods. + // ----------------------------------------------------------------------- + + // Create a new Comment struct. + pub(crate) fn new() -> Comment { + let writer = XMLWriter::new(); + + Comment { + writer, + notes: BTreeMap::new(), + note_authors: BTreeMap::new(), + } + } + + // ----------------------------------------------------------------------- + // XML assembly methods. + // ----------------------------------------------------------------------- + + // Assemble and write the XML file. + pub(crate) fn assemble_xml_file(&mut self) { + self.writer.xml_declaration(); + + // Write the comments element. + self.write_comments(); + + // Write the authors element. + self.write_authors(); + + // Write the commentList element. + self.write_comment_list(); + + // Close the comments tag. + self.writer.xml_end_tag("comments"); + } + + // Write the element. + fn write_comments(&mut self) { + let attributes = [( + "xmlns", + "http://schemas.openxmlformats.org/spreadsheetml/2006/main", + )]; + + self.writer.xml_start_tag("comments", &attributes); + } + + // Write the element. + fn write_authors(&mut self) { + if self.note_authors.is_empty() { + return; + } + + self.writer.xml_start_tag_only("authors"); + + let authors: Vec = self.note_authors.keys().cloned().collect(); + for author in &authors { + // Write the author element. + self.write_author(author); + } + + self.writer.xml_end_tag("authors"); + } + + // Write the element. + fn write_author(&mut self, author: &str) { + self.writer.xml_data_element_only("author", author); + } + + // Write the element. + fn write_comment_list(&mut self) { + self.writer.xml_start_tag_only("commentList"); + + for (row, columns) in &self.notes.clone() { + for (col, note) in columns { + // Write the comment element. + self.write_comment(*row, *col, note); + } + } + + self.writer.xml_end_tag("commentList"); + } + + // Write the element. + fn write_comment(&mut self, row: RowNum, col: ColNum, note: &Note) { + let cell = utility::row_col_to_cell(row, col); + let mut attributes = vec![("ref", cell)]; + + if let Some(id) = note.author_id { + attributes.push(("authorId", id.to_string())); + } + + self.writer.xml_start_tag("comment", &attributes); + + // Write the text element. + self.write_text_block(¬e.text); + + self.writer.xml_end_tag("comment"); + } + + // Write the element. + fn write_text_block(&mut self, text: &str) { + self.writer.xml_start_tag_only("text"); + self.writer.xml_start_tag_only("r"); + + // Write the rPr element. + self.write_paragraph_run(); + + // Write the t text element. + self.write_text(text); + + self.writer.xml_end_tag("r"); + self.writer.xml_end_tag("text"); + } + + // Write the element. + fn write_paragraph_run(&mut self) { + self.writer.xml_start_tag_only("rPr"); + + // Write the sz element. + self.write_font_size(); + + // Write the color element. + self.write_font_color(); + + // Write the rFont element. + self.write_font_name(); + + // Write the family element. + self.write_font_family(); + + self.writer.xml_end_tag("rPr"); + } + + // Write the element. + fn write_font_size(&mut self) { + let attributes = [("val", "8".to_string())]; + + self.writer.xml_empty_tag("sz", &attributes); + } + + // Write the element. + fn write_font_color(&mut self) { + let attributes = [("indexed", "81".to_string())]; + + self.writer.xml_empty_tag("color", &attributes); + } + + // Write the element. + fn write_font_name(&mut self) { + let attributes = [("val", "Tahoma".to_string())]; + + self.writer.xml_empty_tag("rFont", &attributes); + } + + // Write the element. + fn write_font_family(&mut self) { + let attributes = [("val", "2".to_string())]; + + self.writer.xml_empty_tag("family", &attributes); + } + + // Write the element. + fn write_text(&mut self, text: &str) { + self.writer.xml_data_element_only("t", text); + } +} diff --git a/src/content_types.rs b/src/content_types.rs index e54c2a0a..0c653c70 100644 --- a/src/content_types.rs +++ b/src/content_types.rs @@ -101,6 +101,15 @@ impl ContentTypes { self.add_override(&part_name, content_type); } + // Add the name of a comment file to the ContentTypes overrides. + pub(crate) fn add_comments_name(&mut self, index: u16) { + let content_type = + "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"; + let part_name = format!("/xl/comments{index}.xml"); + + self.add_override(&part_name, content_type); + } + // Add the sharedStrings link to the ContentTypes overrides. pub(crate) fn add_share_strings(&mut self) { self.add_override( diff --git a/src/drawing.rs b/src/drawing.rs index 08881022..e8f5a3e3 100644 --- a/src/drawing.rs +++ b/src/drawing.rs @@ -87,7 +87,7 @@ impl Drawing { match drawing_info.drawing_type { DrawingType::Image => self.write_pic(index, drawing_info), DrawingType::Chart => self.write_graphic_frame(index, drawing_info), - DrawingType::Button => {} + DrawingType::Vml => {} } self.writer.xml_empty_tag_only("xdr:clientData"); @@ -495,7 +495,7 @@ pub(crate) struct DrawingInfo { pub(crate) enum DrawingType { Image, Chart, - Button, + Vml, } // Trait for object such as Images and Charts that translate to a Drawing object. diff --git a/src/image.rs b/src/image.rs index a895e4d2..5a3a3e8d 100644 --- a/src/image.rs +++ b/src/image.rs @@ -605,7 +605,13 @@ impl Image { /// src="https://rustxlsxwriter.github.io/images/image_set_alt_text.png"> /// pub fn set_alt_text(mut self, alt_text: impl Into) -> Image { - self.alt_text = alt_text.into(); + let alt_text = alt_text.into(); + if alt_text.chars().count() > 255 { + eprintln!("Alternative text is greater than Excel's limit of 255 characters."); + return self; + } + + self.alt_text = alt_text; self } @@ -839,7 +845,7 @@ impl Image { VmlInfo { width: self.vml_width(), height: self.vml_height(), - name: self.vml_name(), + text: self.vml_name(), header_position: self.vml_position(), is_scaled: self.is_scaled(), ..Default::default() diff --git a/src/lib.rs b/src/lib.rs index 7ab9bf8a..180f8423 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -188,6 +188,7 @@ //! mod app; mod button; +mod comment; mod content_types; mod core; mod custom; @@ -200,6 +201,7 @@ mod format; mod formula; mod image; mod metadata; +mod note; mod packager; mod properties; mod protection; @@ -237,6 +239,7 @@ mod test_functions; // Re-export the public APIs. pub use button::*; +pub use comment::*; pub use data_validation::*; pub use datetime::*; pub use error::*; @@ -244,6 +247,7 @@ pub use filter::*; pub use format::*; pub use formula::*; pub use image::*; +pub use note::*; pub use properties::*; pub use protection::*; pub use table::*; diff --git a/src/note.rs b/src/note.rs new file mode 100644 index 00000000..6bb489d7 --- /dev/null +++ b/src/note.rs @@ -0,0 +1,280 @@ +// note - A module to represent Excel cell notes. +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright 2022-2024, John McNamara, jmcnamara@cpan.org + +#![warn(missing_docs)] + +use crate::drawing::{DrawingObject, DrawingType}; +use crate::vml::VmlInfo; +use crate::{ColNum, ObjectMovement, RowNum, COL_MAX, ROW_MAX}; + +#[derive(Clone)] +/// The `Note` struct represents an worksheet note object. +/// +/// TODO +/// +pub struct Note { + height: f64, + width: f64, + row: Option, + col: Option, + x_offset: Option, + y_offset: Option, + + pub(crate) author: Option, + pub(crate) author_id: Option, + pub(crate) cell_row: RowNum, + pub(crate) cell_col: ColNum, + pub(crate) text: String, + pub(crate) alt_text: String, + pub(crate) object_movement: ObjectMovement, + pub(crate) decorative: bool, +} + +impl Note { + // ----------------------------------------------------------------------- + // Public (and crate public) methods. + // ----------------------------------------------------------------------- + + /// Create a new Note object to represent an Excel Form Control note. + /// + pub fn new(text: impl Into) -> Note { + Note { + row: None, + col: None, + x_offset: None, + y_offset: None, + + cell_row: 0, + cell_col: 0, + + author: None, + author_id: None, + width: 128.0, + height: 74.0, + text: text.into(), + alt_text: String::new(), + object_movement: ObjectMovement::DontMoveOrSizeWithCells, + decorative: false, + } + } + + /// Set the width of the note in pixels. + /// + /// # Parameters + /// + /// - `width`: The note width in pixels. + /// + pub fn set_width(mut self, width: u32) -> Note { + if width == 0 { + return self; + } + + self.width = f64::from(width); + self + } + + /// Set the height of the note in pixels. + /// + /// # Parameters + /// + /// - `height`: The note height in pixels. + /// + pub fn set_height(mut self, height: u32) -> Note { + if height == 0 { + return self; + } + self.height = f64::from(height); + self + } + + /// Set the TODO + /// + /// # Parameters + /// + /// - `name`: The aTODO + /// + pub fn set_author(mut self, name: impl Into) -> Note { + let author = name.into(); + if author.chars().count() > 54 { + eprintln!("Author name is greater than Excel's limit of 54 characters."); + return self; + } + + self.author = Some(author); + self + } + + /// Set the alt text for the note to help accessibility. + /// + /// The alt text is used with screen readers to help people with visual + /// disabilities. + /// + /// See the following Microsoft documentation on [Everything you need to + /// know to write effective alt + /// text](https://support.microsoft.com/en-us/office/everything-you-need-to-know-to-write-effective-alt-text-df98f884-ca3d-456c-807b-1a1fa82f5dc2). + /// + /// # Parameters + /// + /// - `alt_text`: The alt text string to add to the note. + /// + pub fn set_alt_text(mut self, alt_text: impl Into) -> Note { + let alt_text = alt_text.into(); + if alt_text.chars().count() > 255 { + eprintln!("Alternative text is greater than Excel's limit of 255 characters."); + return self; + } + + self.alt_text = alt_text; + self + } + + /// Set the object movement options for a worksheet note. + /// + /// Set the option to define how an note will behave in Excel if the cells + /// under the note are moved, deleted, or have their size changed. In + /// Excel the options are: + /// + /// 1. Move and size with cells. + /// 2. Move but don't size with cells. + /// 3. Don't move or size with cells. + /// + /// + /// + /// These values are defined in the [`ObjectMovement`] enum. + /// + /// The [`ObjectMovement`] enum also provides an additional option to "Move + /// and size with cells - after the note is inserted" to allow notes to + /// be hidden in rows or columns. In Excel this equates to option 1 above + /// but the internal note position calculations are handled differently. + /// + /// # Parameters + /// + /// - `option`: An note/object positioning behavior defined by the + /// [`ObjectMovement`] enum. + pub fn set_object_movement(mut self, option: ObjectMovement) -> Note { + self.object_movement = option; + self + } + + // Notes are stored in a vmlDrawing file. We create a struct to store the + // required image information in that format. + pub(crate) fn vml_info(&self) -> VmlInfo { + VmlInfo { + width: self.width, + height: self.height, + text: self.text.clone(), + alt_text: self.alt_text.clone(), + ..Default::default() + } + } + + //TODO + pub(crate) fn row(&self) -> RowNum { + match self.row { + Some(row) => row, + None => { + if self.cell_row == 0 { + 0 + } else if self.cell_row == ROW_MAX - 3 { + ROW_MAX - 7 + } else if self.cell_row == ROW_MAX - 2 { + ROW_MAX - 6 + } else if self.cell_row == ROW_MAX - 1 { + ROW_MAX - 5 + } else { + self.cell_row - 1 + } + } + } + } + + //TODO + pub(crate) fn col(&self) -> ColNum { + match self.col { + Some(col) => col, + None => { + if self.cell_col == COL_MAX - 3 { + COL_MAX - 6 + } else if self.cell_col == COL_MAX - 2 { + COL_MAX - 5 + } else if self.cell_col == COL_MAX - 1 { + COL_MAX - 4 + } else { + self.cell_col + 1 + } + } + } + } +} + +// Trait for objects that have a component stored in the drawing.xml file. +impl DrawingObject for Note { + #[allow(clippy::if_same_then_else)] + fn x_offset(&self) -> u32 { + match self.x_offset { + Some(offset) => offset, + None => { + if self.cell_col == COL_MAX - 3 { + 49 + } else if self.cell_col == COL_MAX - 2 { + 49 + } else if self.cell_col == COL_MAX - 1 { + 49 + } else { + 15 + } + } + } + } + + #[allow(clippy::if_same_then_else)] + fn y_offset(&self) -> u32 { + match self.y_offset { + Some(offset) => offset, + None => { + if self.cell_row == 0 { + 2 + } else if self.cell_row == ROW_MAX - 3 { + 16 + } else if self.cell_row == ROW_MAX - 2 { + 16 + } else if self.cell_row == ROW_MAX - 1 { + 14 + } else { + 10 + } + } + } + } + + fn width_scaled(&self) -> f64 { + self.width + } + + fn height_scaled(&self) -> f64 { + self.height + } + + fn object_movement(&self) -> ObjectMovement { + self.object_movement + } + + fn name(&self) -> String { + self.text.clone() + } + + fn alt_text(&self) -> String { + self.alt_text.clone() + } + + fn decorative(&self) -> bool { + self.decorative + } + + fn drawing_type(&self) -> DrawingType { + DrawingType::Vml + } +} diff --git a/src/packager.rs b/src/packager.rs index 794d25d8..e226d0fa 100644 --- a/src/packager.rs +++ b/src/packager.rs @@ -67,7 +67,7 @@ use crate::theme::Theme; use crate::vml::Vml; use crate::workbook::Workbook; use crate::worksheet::Worksheet; -use crate::{DocProperties, NUM_IMAGE_FORMATS}; +use crate::{Comment, DocProperties, NUM_IMAGE_FORMATS}; // Packager struct to assembler the xlsx file. pub struct Packager { @@ -157,6 +157,7 @@ impl Packager { self.write_drawing_files(workbook)?; self.write_vml_files(workbook)?; + self.write_comment_files(workbook)?; self.write_image_files(workbook)?; self.write_chart_files(workbook)?; self.write_table_files(workbook)?; @@ -236,6 +237,10 @@ impl Packager { content_types.add_table_name(i + 1); } + for i in 0..options.num_comments { + content_types.add_comments_name(i + 1); + } + if options.has_sst_table { content_types.add_share_strings(); } @@ -400,6 +405,10 @@ impl Packager { rels.add_document_relationship(&relationship.0, &relationship.1, &relationship.2); } + for relationship in &worksheet.comment_relationships { + rels.add_document_relationship(&relationship.0, &relationship.1, &relationship.2); + } + let filename = format!("xl/worksheets/_rels/sheet{index}.xml.rels"); self.zip.start_file(filename, self.zip_options)?; @@ -531,6 +540,7 @@ impl Packager { workbook.border_count, workbook.num_formats.clone(), workbook.has_hyperlink_style, + workbook.has_comments, false, ); @@ -728,9 +738,32 @@ impl Packager { Ok(()) } + // Write the comment files. + fn write_comment_files(&mut self, workbook: &mut Workbook) -> Result<(), XlsxError> { + let mut index = 1; + for worksheet in &mut workbook.worksheets { + if !worksheet.notes.is_empty() { + let filename = format!("xl/comments{index}.xml"); + self.zip.start_file(filename, self.zip_options)?; + + let mut comment = Comment::new(); + comment.notes = worksheet.notes.clone(); + comment.note_authors = worksheet.note_authors.clone(); + + comment.assemble_xml_file(); + + self.zip.write_all(comment.writer.xmlfile.get_ref())?; + index += 1; + } + } + + Ok(()) + } + // Write the vml files. fn write_vml_files(&mut self, workbook: &mut Workbook) -> Result<(), XlsxError> { let mut index = 1; + let mut header_data_id = 1; for worksheet in &mut workbook.worksheets { if worksheet.has_header_footer_images() || worksheet.has_vml { if worksheet.has_vml { @@ -738,9 +771,12 @@ impl Packager { self.zip.start_file(filename, self.zip_options)?; let mut vml = Vml::new(); - vml.buttons.append(&mut worksheet.button_vml_info); - vml.data_id = index; - vml.shape_id = 1024 * index; + vml.buttons.append(&mut worksheet.buttons_vml_info); + vml.comments.append(&mut worksheet.comments_vml_info); + + vml.data_id.clone_from(&worksheet.vml_data_id); + + vml.shape_id = worksheet.vml_shape_id; vml.assemble_xml_file(); self.zip.write_all(vml.writer.xmlfile.get_ref())?; @@ -754,7 +790,10 @@ impl Packager { let mut vml = Vml::new(); vml.header_images .append(&mut worksheet.header_footer_vml_info); - vml.data_id = index; + + vml.data_id = format!("{header_data_id}"); + header_data_id += 1; + vml.shape_id = 1024 * index; vml.assemble_xml_file(); @@ -893,6 +932,7 @@ pub(crate) struct PackagerOptions { pub(crate) num_drawings: u16, pub(crate) num_charts: u16, pub(crate) num_tables: u16, + pub(crate) num_comments: u16, pub(crate) doc_security: u8, pub(crate) worksheet_names: Vec, pub(crate) defined_names: Vec, @@ -917,6 +957,7 @@ impl PackagerOptions { num_drawings: 0, num_charts: 0, num_tables: 0, + num_comments: 0, doc_security: 0, worksheet_names: vec![], defined_names: vec![], diff --git a/src/styles.rs b/src/styles.rs index 1e45532f..afcfce10 100644 --- a/src/styles.rs +++ b/src/styles.rs @@ -22,6 +22,7 @@ pub struct Styles<'a> { border_count: u16, num_formats: Vec, has_hyperlink_style: bool, + has_comments: bool, is_rich_string_style: bool, } @@ -41,6 +42,7 @@ impl<'a> Styles<'a> { border_count: u16, num_formats: Vec, has_hyperlink_style: bool, + has_comments: bool, is_rich_string_style: bool, ) -> Styles<'a> { let writer = XMLWriter::new(); @@ -54,6 +56,7 @@ impl<'a> Styles<'a> { border_count, num_formats, has_hyperlink_style, + has_comments, is_rich_string_style, } } @@ -112,7 +115,12 @@ impl<'a> Styles<'a> { // Write the element. fn write_fonts(&mut self) { - let attributes = [("count", self.font_count.to_string())]; + let mut count = self.font_count; + if self.has_comments { + count += 1; + } + + let attributes = [("count", count.to_string())]; self.writer.xml_start_tag("fonts", &attributes); @@ -124,6 +132,12 @@ impl<'a> Styles<'a> { } } + // Add the additional font for cell comments/notes. This isn't currently + // configurable and probably won't be. + if self.has_comments { + self.write_comment_font(); + } + self.writer.xml_end_tag("fonts"); } @@ -270,6 +284,18 @@ impl<'a> Styles<'a> { self.writer.xml_empty_tag("u", &attributes); } + // Write the element for comments. + fn write_comment_font(&mut self) { + self.writer.xml_start_tag_only("font"); + + self.writer.xml_empty_tag("sz", &[("val", "8")]); + self.writer.xml_empty_tag("color", &[("indexed", "81")]); + self.writer.xml_empty_tag("name", &[("val", "Tahoma")]); + self.writer.xml_empty_tag("family", &[("val", "2")]); + + self.writer.xml_end_tag("font"); + } + // Write the element. fn write_vert_align(&mut self, font: &Font) { let mut attributes = vec![]; diff --git a/src/styles/tests.rs b/src/styles/tests.rs index 2865d1dd..484277ed 100644 --- a/src/styles/tests.rs +++ b/src/styles/tests.rs @@ -20,7 +20,17 @@ mod styles_tests { let xf_formats = vec![xf_format]; let dxf_formats = vec![]; - let mut styles = Styles::new(&xf_formats, &dxf_formats, 1, 2, 1, vec![], false, false); + let mut styles = Styles::new( + &xf_formats, + &dxf_formats, + 1, + 2, + 1, + vec![], + false, + false, + false, + ); styles.assemble_xml_file(); diff --git a/src/vml.rs b/src/vml.rs index 680130e5..1277ee5d 100644 --- a/src/vml.rs +++ b/src/vml.rs @@ -6,13 +6,14 @@ mod tests; -use crate::{drawing::DrawingInfo, xmlwriter::XMLWriter}; +use crate::{drawing::DrawingInfo, xmlwriter::XMLWriter, ColNum, RowNum}; pub struct Vml { + pub(crate) comments: Vec, pub(crate) writer: XMLWriter, pub(crate) buttons: Vec, pub(crate) header_images: Vec, - pub(crate) data_id: u32, + pub(crate) data_id: String, pub(crate) shape_id: u32, } @@ -28,8 +29,9 @@ impl Vml { Vml { writer, buttons: vec![], + comments: vec![], header_images: vec![], - data_id: 0, + data_id: String::new(), shape_id: 0, } } @@ -64,6 +66,18 @@ impl Vml { } } + if !self.comments.is_empty() { + // Write the v:shapetype element. + self.write_comment_shapetype(); + + for (z_index, vml_info) in self.comments.clone().iter().enumerate() { + self.shape_id += 1; + + // Write the v:shape element. + self.write_comment_shape(self.shape_id, z_index + 1, vml_info); + } + } + if !self.header_images.is_empty() { // Write the v:shapetype element. self.write_image_shapetype(); @@ -136,6 +150,26 @@ impl Vml { self.writer.xml_end_tag("v:shapetype"); } + // Write the element for button. + fn write_comment_shapetype(&mut self) { + let attributes = [ + ("id", "_x0000_t202"), + ("coordsize", "21600,21600"), + ("o:spt", "202"), + ("path", "m,l,21600r21600,l21600,xe"), + ]; + + self.writer.xml_start_tag("v:shapetype", &attributes); + + // Write the v:stroke element. + self.write_stroke(); + + // Write the v:path element. + self.write_comment_path(); + + self.writer.xml_end_tag("v:shapetype"); + } + // Write the element for header images. fn write_image_shapetype(&mut self) { let attributes = [ @@ -222,6 +256,20 @@ impl Vml { self.writer.xml_empty_tag("v:path", &attributes); } + // Write the element for comments. + fn write_comment_path(&mut self) { + let attributes = [("gradientshapeok", "t"), ("o:connecttype", "rect")]; + + self.writer.xml_empty_tag("v:path", &attributes); + } + + // Write the element for comments. + fn write_comment_path2(&mut self) { + let attributes = [("o:connecttype", "none")]; + + self.writer.xml_empty_tag("v:path", &attributes); + } + // Write the element. fn write_shapetype_lock(&mut self) { let attributes = [("v:ext", "edit"), ("shapetype", "t")]; @@ -271,7 +319,7 @@ impl Vml { self.writer.xml_start_tag("v:shape", &attributes); // Write the v:fill element. - self.write_fill(); + self.write_button_fill(); // Write the o:lock element. self.write_rotation_lock(vml_info); @@ -285,6 +333,56 @@ impl Vml { self.writer.xml_end_tag("v:shape"); } + // Write the element for comments. + #[allow(clippy::cast_precision_loss)] + fn write_comment_shape(&mut self, vml_shape_id: u32, z_index: usize, vml_info: &VmlInfo) { + let top = Self::vml_dpi_size(vml_info.drawing_info.row_absolute as f64); + let left = Self::vml_dpi_size(vml_info.drawing_info.col_absolute as f64); + let width = Self::vml_dpi_size(vml_info.drawing_info.width); + let height = Self::vml_dpi_size(vml_info.drawing_info.height); + + let style = format!( + "position:absolute;\ + margin-left:{left}pt;\ + margin-top:{top}pt;\ + width:{width}pt;\ + height:{height}pt;\ + z-index:{z_index};\ + visibility:hidden" + ); + + let shape_id = format!("_x0000_s{vml_shape_id}"); + + let mut attributes = vec![("id", shape_id), ("type", "#_x0000_t202".to_string())]; + + if !vml_info.alt_text.is_empty() { + attributes.push(("alt", vml_info.alt_text.clone())); + } + + attributes.push(("style", style)); + attributes.push(("fillcolor", "#ffffe1".to_string())); + attributes.push(("o:insetmode", "auto".to_string())); + + self.writer.xml_start_tag("v:shape", &attributes); + + // Write the v:fill element. + self.write_comment_fill(); + + // Write the v:shadow element. + self.write_shadow(); + + // Write the v:path element. + self.write_comment_path2(); + + // Write the v:textbox element. + self.write_comment_textbox(); + + // Write the x:ClientData element. + self.write_comment_client_data(vml_info); + + self.writer.xml_end_tag("v:shape"); + } + // Write the element for header images. fn write_image_shape(&mut self, z_index: usize, vml_info: &VmlInfo) { let width = Self::vml_dpi_size(vml_info.width); @@ -323,7 +421,7 @@ impl Vml { fn write_imagedata(&mut self, vml_info: &VmlInfo) { let attributes = [ ("o:relid", format!("rId{}", vml_info.rel_id)), - ("o:title", vml_info.name.to_string()), + ("o:title", vml_info.text.to_string()), ]; self.writer.xml_empty_tag("v:imagedata", &attributes); @@ -341,7 +439,7 @@ impl Vml { } // Write the element. - fn write_fill(&mut self) { + fn write_button_fill(&mut self) { let attributes = [ ("color2", "buttonFace [67]".to_string()), ("o:detectmouseclick", "t".to_string()), @@ -350,6 +448,13 @@ impl Vml { self.writer.xml_empty_tag("v:fill", &attributes); } + // Write the element. + fn write_comment_fill(&mut self) { + let attributes = [("color2", "#ffffe1".to_string())]; + + self.writer.xml_empty_tag("v:fill", &attributes); + } + // Write the element. fn write_button_textbox(&mut self, vml_info: &VmlInfo) { let attributes = [("style", "mso-direction-alt:auto"), ("o:singleclick", "f")]; @@ -383,7 +488,7 @@ impl Vml { ]; self.writer - .xml_data_element("font", &vml_info.name, &attributes); + .xml_data_element("font", &vml_info.text, &attributes); } // Write the element. @@ -413,6 +518,50 @@ impl Vml { self.writer.xml_end_tag("x:ClientData"); } + // Write the element. + fn write_comment_textbox(&mut self) { + let attributes = [("style", "mso-direction-alt:auto")]; + + self.writer.xml_start_tag("v:textbox", &attributes); + + // Write the div element. + self.write_comment_div(); + + self.writer.xml_end_tag("v:textbox"); + } + + // Write the
element. + fn write_comment_div(&mut self) { + let attributes = [("style", "text-align:left")]; + + self.writer.xml_start_tag("div", &attributes); + + self.writer.xml_end_tag("div"); + } + + // Write the element. + fn write_comment_client_data(&mut self, vml_info: &VmlInfo) { + let attributes = [("ObjectType", "Note")]; + + self.writer.xml_start_tag("x:ClientData", &attributes); + self.writer.xml_empty_tag_only("x:MoveWithCells"); + self.writer.xml_empty_tag_only("x:SizeWithCells"); + + // Write the x:Anchor element. + self.write_anchor(vml_info); + + // Write the x:AutoFill element. + self.write_auto_fill(); + + // Write the x:Row element. + self.write_row(vml_info.row); + + // Write the x:Column element. + self.write_column(vml_info.col); + + self.writer.xml_end_tag("x:ClientData"); + } + // Write the element. fn write_anchor(&mut self, vml_info: &VmlInfo) { let anchor = format!( @@ -455,6 +604,24 @@ impl Vml { fn write_text_valign(&mut self) { self.writer.xml_data_element_only("x:TextVAlign", "Center"); } + + // Write the element. + fn write_shadow(&mut self) { + let attributes = [("on", "t"), ("color", "black"), ("obscured", "t")]; + + self.writer.xml_empty_tag("v:shadow", &attributes); + } + + // Write the element. + fn write_row(&mut self, row: RowNum) { + self.writer.xml_data_element_only("x:Row", &row.to_string()); + } + + // Write the element. + fn write_column(&mut self, col: ColNum) { + self.writer + .xml_data_element_only("x:Column", &col.to_string()); + } } // ----------------------------------------------------------------------- @@ -462,9 +629,11 @@ impl Vml { // ----------------------------------------------------------------------- #[derive(Clone)] pub(crate) struct VmlInfo { + pub(crate) row: RowNum, + pub(crate) col: ColNum, pub(crate) width: f64, pub(crate) height: f64, - pub(crate) name: String, + pub(crate) text: String, pub(crate) alt_text: String, pub(crate) macro_name: String, pub(crate) rel_id: u32, @@ -476,9 +645,11 @@ pub(crate) struct VmlInfo { impl Default for VmlInfo { fn default() -> Self { VmlInfo { + row: 0, + col: 0, width: 0.0, height: 0.0, - name: String::new(), + text: String::new(), alt_text: String::new(), macro_name: String::new(), rel_id: 0, diff --git a/src/vml/tests.rs b/src/vml/tests.rs index 22384b9b..fbc960ed 100644 --- a/src/vml/tests.rs +++ b/src/vml/tests.rs @@ -19,7 +19,7 @@ mod theme_tests { let vml_info = VmlInfo { width: 32.0, height: 32.0, - name: "red".to_string(), + text: "red".to_string(), rel_id: 1, header_position: "LH".to_string(), is_scaled: false, @@ -27,7 +27,7 @@ mod theme_tests { }; vml.header_images.push(vml_info); - vml.data_id = 1; + vml.data_id = 1.to_string(); vml.shape_id = 1024; vml.assemble_xml_file(); @@ -78,7 +78,7 @@ mod theme_tests { let vml_info1 = VmlInfo { width: 32.0, height: 32.0, - name: "red".to_string(), + text: "red".to_string(), rel_id: 1, header_position: "LH".to_string(), is_scaled: false, @@ -88,7 +88,7 @@ mod theme_tests { let vml_info2 = VmlInfo { width: 23.0, height: 23.0, - name: "blue".to_string(), + text: "blue".to_string(), rel_id: 2, header_position: "CH".to_string(), is_scaled: false, @@ -97,7 +97,7 @@ mod theme_tests { vml.header_images.push(vml_info1); vml.header_images.push(vml_info2); - vml.data_id = 1; + vml.data_id = 1.to_string(); vml.shape_id = 1024; vml.assemble_xml_file(); diff --git a/src/workbook.rs b/src/workbook.rs index 57c3ab43..7bc8172a 100644 --- a/src/workbook.rs +++ b/src/workbook.rs @@ -320,6 +320,7 @@ pub struct Workbook { pub(crate) vba_signature: Vec, pub(crate) vba_codename: Option, pub(crate) is_xlsm_file: bool, + pub(crate) has_comments: bool, xf_indices: HashMap, dxf_indices: HashMap, @@ -402,6 +403,7 @@ impl Workbook { vba_project: vec![], vba_signature: vec![], vba_codename: None, + has_comments: false, }; // Initialize the workbook with the same function used to reset it. @@ -1706,13 +1708,26 @@ impl Workbook { // Prepare the worksheet VML elements such as buttons and header images. fn prepare_vml(&mut self) { + let mut comment_id = 1; let mut vml_drawing_id = 1; + let mut vml_data_id = 1; + let mut vml_shape_id = 1024; for worksheet in &mut self.worksheets { - if !worksheet.buttons.is_empty() { - worksheet.prepare_vml_objects(); + if worksheet.has_vml { + let note_count = worksheet.prepare_vml_objects(vml_data_id, vml_shape_id); worksheet.add_vml_drawing_rel_link(vml_drawing_id); vml_drawing_id += 1; + + if !worksheet.notes.is_empty() { + worksheet.add_comment_rel_link(comment_id); + comment_id += 1; + self.has_comments = true; + } + + // Each VML should start with a shape id incremented by 1024. + vml_data_id += (1024 + note_count) / 1024; + vml_shape_id += 1024 * ((1024 + note_count) / 1024); } if worksheet.has_header_footer_images() { @@ -2214,6 +2229,10 @@ impl Workbook { package_options.num_tables += worksheet.tables.len() as u16; } + if !worksheet.notes.is_empty() { + package_options.num_comments += 1; + } + // Store the autofilter areas which are a category of defined name. if worksheet.autofilter_defined_name.in_use { let mut defined_name = worksheet.autofilter_defined_name.clone(); diff --git a/src/worksheet.rs b/src/worksheet.rs index 9ab4bae8..a727ebd7 100644 --- a/src/worksheet.rs +++ b/src/worksheet.rs @@ -1079,7 +1079,7 @@ use crate::{ ChartRangeCacheDataType, Color, ConditionalFormat, DataValidation, DataValidationErrorStyle, DataValidationRuleInternal, DataValidationType, ExcelDateTime, FilterCondition, FilterCriteria, FilterData, FilterDataType, HeaderImagePosition, HyperlinkType, Image, IntoColor, - IntoExcelDateTime, ObjectMovement, ProtectionOptions, Sparkline, SparklineType, Table, + IntoExcelDateTime, Note, ObjectMovement, ProtectionOptions, Sparkline, SparklineType, Table, TableFunction, Url, }; @@ -1193,20 +1193,26 @@ pub struct Worksheet { pub(crate) hyperlink_relationships: Vec<(String, String, String)>, pub(crate) drawing_object_relationships: Vec<(String, String, String)>, pub(crate) drawing_relationships: Vec<(String, String, String)>, + pub(crate) comment_relationships: Vec<(String, String, String)>, pub(crate) vml_drawing_relationships: Vec<(String, String, String)>, pub(crate) images: BTreeMap<(RowNum, ColNum), Image>, - pub(crate) button_vml_info: Vec, + pub(crate) buttons_vml_info: Vec, + pub(crate) comments_vml_info: Vec, pub(crate) header_footer_vml_info: Vec, pub(crate) drawing: Drawing, pub(crate) image_types: [bool; NUM_IMAGE_FORMATS], pub(crate) header_footer_images: [Option; 6], pub(crate) charts: BTreeMap<(RowNum, ColNum), Chart>, pub(crate) buttons: BTreeMap<(RowNum, ColNum), Button>, + pub(crate) notes: BTreeMap>, pub(crate) tables: Vec, pub(crate) has_embedded_image_descriptions: bool, pub(crate) embedded_images: Vec, pub(crate) global_embedded_image_indices: Vec, pub(crate) vba_codename: Option, + pub(crate) note_authors: BTreeMap, + pub(crate) vml_data_id: String, + pub(crate) vml_shape_id: u32, data_table: BTreeMap>, merged_ranges: Vec, @@ -1277,8 +1283,8 @@ pub struct Worksheet { has_x14_conditional_formats: bool, has_sparklines: bool, sparklines: Vec, - embedded_image_ids: HashMap, + default_note_author: String, #[cfg(feature = "serde")] pub(crate) serializer_state: SerializerState, @@ -1444,13 +1450,15 @@ impl Worksheet { hyperlink_relationships: vec![], drawing_object_relationships: vec![], drawing_relationships: vec![], + comment_relationships: vec![], vml_drawing_relationships: vec![], images: BTreeMap::new(), drawing: Drawing::new(), image_types: [false; NUM_IMAGE_FORMATS], header_footer_images: [None, None, None, None, None, None], header_footer_vml_info: vec![], - button_vml_info: vec![], + buttons_vml_info: vec![], + comments_vml_info: vec![], rel_count: 0, protection_on: false, protection_hash: 0, @@ -1464,6 +1472,7 @@ impl Worksheet { filter_automatic_off: false, charts: BTreeMap::new(), buttons: BTreeMap::new(), + notes: BTreeMap::new(), has_drawing_object_linkage: false, cells_with_autofilter: HashSet::new(), conditional_formats: BTreeMap::new(), @@ -1478,6 +1487,10 @@ impl Worksheet { has_sparklines: false, sparklines: vec![], vba_codename: None, + default_note_author: "Author".to_string(), + note_authors: BTreeMap::new(), + vml_data_id: String::new(), + vml_shape_id: 0, #[cfg(feature = "serde")] serializer_state: SerializerState::new(), @@ -5097,6 +5110,103 @@ impl Worksheet { Ok(self) } + /// TODO + /// + /// # Errors + /// + /// - [`XlsxError::RowColumnLimitError`] - Row or column exceeds Excel's + /// worksheet limits. + /// - [`XlsxError::MaxStringLengthExceeded`] - Text exceeds Excel's limit + /// of 32,767 characters. + /// + /// # Parameters + /// + /// - `row`: The zero indexed row number. + /// - `col`: The zero indexed column number. + /// - `button`: The [`Button`] to insert into the cell. + /// - `x_offset`: The horizontal offset within the cell in pixels. + /// - `y_offset`: The vertical offset within the cell in pixels. + /// + pub fn insert_note( + &mut self, + row: RowNum, + col: ColNum, + note: &Note, + ) -> Result<&mut Worksheet, XlsxError> { + // Check row and columns are in the allowed range. + if !self.check_dimensions(row, col) { + return Err(XlsxError::RowColumnLimitError); + } + + // Check that the string is < Excel limit of 32767 chars. + if note.text.chars().count() > MAX_STRING_LEN { + return Err(XlsxError::MaxStringLengthExceeded); + } + + let mut note = note.clone(); + + // Set the cell that the Note refers to. This is different form the cell + // where the note appears. + note.cell_row = row; + note.cell_col = col; + + // Set the author name. + let note_author = match ¬e.author { + Some(author) => author, + None => &self.default_note_author, + }; + + // Convert the name to an id. + match self.note_authors.get(note_author) { + Some(id) => { + note.author_id = Some(*id); + } + None => { + let id = self.note_authors.len(); + self.note_authors.insert(note_author.clone(), id); + note.author_id = Some(id); + } + } + + // Store the note in a structure similar to the worksheet data table + // since notes also affect the calculation of span attributes. + match self.notes.entry(row) { + Entry::Occupied(mut entry) => { + // The row already exists. Insert/replace column value. + let columns = entry.get_mut(); + columns.insert(col, note); + } + Entry::Vacant(entry) => { + // The row doesn't exist, create a new row with columns and + // insert the cell value. + let columns = BTreeMap::from([(col, note)]); + entry.insert(columns); + } + } + + self.has_vml = true; + + Ok(self) + } + + /// TODO. + pub fn set_default_note_author(&mut self, author: impl Into) -> &mut Worksheet { + let author = author.into(); + if author.chars().count() > 52 { + eprintln!("Author string must be less than the Excel limit of 52 characters: {author}"); + return self; + } + + if !self.note_authors.contains_key(&author) { + let id = self.note_authors.len(); + self.note_authors.insert(author.clone(), id); + } + + self.default_note_author = author; + + self + } + /// Add a Excel Form Control button object to a worksheet. /// /// Add a [`Button`] to a worksheet at a cell location. The worksheet button @@ -12186,7 +12296,17 @@ impl Worksheet { // Create a Style struct object to generate the font xml. let xf_formats: Vec = vec![]; let dxf_formats: Vec = vec![]; - let mut styler = Styles::new(&xf_formats, &dxf_formats, 0, 0, 0, vec![], false, true); + let mut styler = Styles::new( + &xf_formats, + &dxf_formats, + 0, + 0, + 0, + vec![], + false, + false, + true, + ); let mut raw_string = String::new(); let mut first_segment = true; @@ -12568,7 +12688,7 @@ impl Worksheet { } } - // Store the linkage to the worksheets rels file. + // Store the vmlDrawingN.xml file linkage to the worksheets rels file. pub(crate) fn add_vml_drawing_rel_link(&mut self, drawing_id: u32) { let vml_drawing_name = format!("../drawings/vmlDrawing{drawing_id}.vml"); self.drawing_object_relationships.push(( @@ -12579,10 +12699,30 @@ impl Worksheet { } // Convert buttons into VML objects. - pub(crate) fn prepare_vml_objects(&mut self) { + pub(crate) fn prepare_vml_objects(&mut self, vml_data_id: u32, vml_shape_id: u32) -> u32 { let mut button_id = 1; + let mut note_count = 0; + + // Convert the Note objects to VmlInfo objects, along with their dimensions. + for (cell_row, columns) in &self.notes.clone() { + for (cell_col, note) in columns { + let note_row = note.row(); + let note_col = note.col(); + + let mut vml_info = note.vml_info(); + vml_info.drawing_info = self.position_object_pixels(note_row, note_col, note); + vml_info.row = *cell_row; + vml_info.col = *cell_col; + + // Store the note vml data. + self.comments_vml_info.push(vml_info); + + note_count += 1; + } + } + + // Convert the Button objects to VmlInfo objects, along with their dimensions. for ((row, col), button) in self.buttons.clone() { - // Convert the button to a VmlInfo structure for storing in a vmlDrawing file. let mut button = button.clone(); if button.name.is_empty() { button.name = format!("Button {button_id}"); @@ -12598,10 +12738,32 @@ impl Worksheet { vml_info.drawing_info = self.position_object_pixels(row, col, &button); // Store the button vml data. - self.button_vml_info.push(vml_info); + self.buttons_vml_info.push(vml_info); button_id += 1; } + + // The VML o:idmap data id contains a comma separated range when there + // is more than one 1024 block of comments, like this: data="1,2". + dbg!(note_count); + let mut oid_map = vml_data_id.to_string(); + + for i in 0..note_count / 1024 { + let next_id = vml_data_id + i + 1; + oid_map = format!("{oid_map},{next_id}"); + } + + self.vml_data_id = oid_map; + self.vml_shape_id = vml_shape_id; + + note_count + } + + // Store the commentN.xml file linkage to the worksheets rels file. + pub(crate) fn add_comment_rel_link(&mut self, comment_id: u32) { + let comment_name = format!("../comments{comment_id}.xml"); + self.comment_relationships + .push(("comments".to_string(), comment_name, String::new())); } // Convert the chart dimensions into drawing dimensions and add them to the @@ -12918,6 +13080,7 @@ impl Worksheet { self.drawing_object_relationships.clear(); self.drawing_relationships.clear(); self.vml_drawing_relationships.clear(); + self.comment_relationships.clear(); self.header_footer_vml_info.clear(); } @@ -13477,7 +13640,7 @@ impl Worksheet { // Write the element. fn write_sheet_data(&mut self) { - if self.data_table.is_empty() && self.changed_rows.is_empty() { + if self.data_table.is_empty() && self.notes.is_empty() && self.changed_rows.is_empty() { self.writer.xml_empty_tag_only("sheetData"); } else { self.writer.xml_start_tag_only("sheetData"); @@ -14016,7 +14179,7 @@ impl Worksheet { fn write_data_table(&mut self) { let spans = self.calculate_spans(); - // Swap out the worksheet data structures so we can iterate over it and + // Swap out the worksheet data structures so we can iterate over them and // still call self.write_xml() methods. let mut temp_table: BTreeMap> = BTreeMap::new(); let mut temp_changed_rows: HashMap = HashMap::new(); @@ -14028,14 +14191,17 @@ impl Worksheet { let span = spans.get(&span_index).map(AsRef::as_ref); let row_options = temp_changed_rows.get(&row_num); + let row_has_notes = self.notes.contains_key(&row_num); + // If there is no column data then only the metadata needs updating. let Some(columns) = temp_table.get(&row_num) else { - if row_options.is_some() { + if row_options.is_some() || row_has_notes { self.write_table_row(row_num, span, row_options, false); } continue; }; + // The row has data. Write it out cell by cell. self.write_table_row(row_num, span, row_options, true); for (&col_num, cell) in columns { match cell { @@ -14127,6 +14293,18 @@ impl Worksheet { } } + if let Some(columns) = self.notes.get(&row_num) { + for &col_num in columns.keys() { + if span_min == COL_MAX { + span_min = col_num; + span_max = col_num; + } else { + span_min = cmp::min(span_min, col_num); + span_max = cmp::max(span_max, col_num); + } + } + } + // Store the span range for each block or 16 rows. if (row_num + 1) % 16 == 0 || row_num == self.dimensions.last_row { let span_index = row_num / 16; diff --git a/tests/input/comment01.xlsx b/tests/input/comment01.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..69a9c2086e19cb026a4f315157c194688601c58a GIT binary patch literal 8828 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 literal 0 HcmV?d00001 diff --git a/tests/input/comment02.xlsx b/tests/input/comment02.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..d4614713787e9b7f85d281672073b39c17cfeb3a GIT binary patch literal 8916 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

wfzx`g*Xk625`KU-$Op1$^EXM(XG$|#`VmGqSw^ zRgg3fvbz+pgSs*8mrAfJ(sIy5sy>lKeBAZE=3_}D@Kc7tQ+JPX6~xDsObbJr=4)L; znLVu%%yW*Lqdw-HZZ32?%wn7wQ;0+9ZH-f;D*}9)Mk;+rbrw?<5Z*?oO__61PZ95^ z1;2wcy#D{?eRlSa}C^wQ70IQuPLSncjp8gL0XwQg9F1k!*><> zwNCo99BU+SsTNJYE~hsDt@FUnpI-8Q#U^jOlofuur}h2}=TBLzVYzS~49{ZfaD0>e z#P)Cf$gfT$M7j6XcZ!zy3|H)U%68wBqei^NXGM|OefXVzsU^OKIX#2bRkL}XG)B__ zb;iti-#}5!{STV`IJ&7~(chTyQXu!rRC5(h&!^9Cw8e%^y?FZ`dktivb=DSo6a4KUs$~q z6<^567PaOj`Aoa*7fdD-h(LM;EqUTS7#FM9-E_o0DeP59_LRO-wpROfBEucm$83S0 z9c&Xc2%luvl0>og5Eu`6c1aLW=4*%sb3C69_s4VtX~->_PL_UAD1M~T7F|`%8D^Fr zpn-&&@+rDvZ?l}Nl53!5O{iuyKjZnWD0(k+6QWFERBixv_5HB-u|k)4#s-qn<;k3+ z=P$1$j^I1VKew8Wg%=5!a3_($orL$(Nd{m`OZbGu?ni6XrzkJm&Oz9SdF8*mZ8ar1 zQNgW&bdQ-5>G>|{)Py`W6$3k4!_{k*%ENhfJTYf3Feg{`OlpeJlMq8MV#QJx-L-QL z5>LF{<{c|i-Iy4v7;H-9lt^8s($Xmq&>w`~i;{-47L)s6b;KA;Ng8^7t?x7Hq`uCKBy!5Igb>g`G`;@CxN=_m+l zM{>?RnF)jD&1=CFu|z|F^p{h3iF=4?9Pg4}Ga3-Pv1B%lKnoJdW4VsBClBRzscPxx zyu^8*R0-zornJaXf3Uw8-N|w-dN;kY?Xkb}nk}@qlr3P1?~j@L;P4Dx7;ZQXxZ&{O z44)y`K+zU#ZO3i^wl(@WR{Afo2e%v`BFaLerh53=)B6)NXA8a+UxsONRXgGp9+8 zE2!feE-|Yzq(P*8tlE~z5D9K-WU9!OQ5c1YH5QMQ;_L#q^j`RcckK3{pF$$gXgQp5 zle;~&0(s}-u)FoRsYMc_nXyhtgz!i3*UnX|@jB(LV&Gd&;uKrVK65S5T2s$n_7G*M z@jz)?YAsQ~^x`~!DXIc-gFhrd3~#Cvbg#6s;k+Ls_OA43!u>B6&E05Cnu?sB;LfWJ zu=gP{Kh`zhUeeJ0RJ^BeI6FV+Je*J!eWGYEPm?_r?v!q~`J_NuO5?@JO&F^+X~0%Cy(L0Xr4-cu_H)X|9d zCuwyVG&8H>2rMyEp~$_ESjLqByu4FtgHa&n@o=*vo>WQCw@M#6(fo{NX|SKZt@iQ3 zhg^p$L@LnE(?Cddy==FDdBDqp4+6<_9ezRnS;(bJ+NyH3i04TZsrCg+f@%Whf)wSmF8A`EaI_88bQp~l<~iWT>9Y-8u9O!`?DbM(0b|$X9U1Wquq{ms~5v z76@puOMV4Fzx`R4P0;%CWDC?`QI`W!W*u7Rw1nWvtrfja)!xg$*Tu1?0BnGO>+J^k zK#Abjt4)DAoD1!(S_l2U#BMU@&S12G#YVP59dpjHMo6Ux{5{ePkG#8cc@_YN?^nTk z>R0;*ZHKni%%>p&!V@u#7e<%xdiE!J5pF$zGpm0d$N$S)|GNHRIIk%CcLjeRBK}M8 z`!yII62FZUZwmf>BJ;bT6Fk!X_xa3CJvUkUUz*;*|8a1Gv%e{PlWzVcOoj17_#fo+ zO$|3m-d`HN{?zbWiS^SOzY47%^QM~$Z_=Z`6yC!6@k%$z(whoyHmkoBR1^GM)4yBS zo1!;)eQsfG!%Kvi8UqtvXd)(A=vljZLh5GIfEq|+wZc6{X xAowKzqPuRO0PiwsBYcXj|9pbiIqDAo6;{{b(9TlxS1 literal 0 HcmV?d00001 diff --git a/tests/input/comment04.xlsx b/tests/input/comment04.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..2bc1bcddafb20713ac02d53d33281cafb985bf75 GIT binary patch literal 11207 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 QKy4|)6QU8 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 TjyV7T1O6j`!`7Po$JPG Result<(), XlsxError> { + let mut workbook = Workbook::new(); + + let worksheet = workbook.add_worksheet(); + worksheet.set_default_note_author("John"); + + worksheet.write(0, 0, "Foo")?; + + let note = Note::new("Some text"); + worksheet.insert_note(1, 1, ¬e)?; + + workbook.save(filename)?; + + Ok(()) +} + +// Explicitly set author name. +fn create_new_xlsx_file_2(filename: &str) -> Result<(), XlsxError> { + let mut workbook = Workbook::new(); + + let worksheet = workbook.add_worksheet(); + + worksheet.write(0, 0, "Foo")?; + + let note = Note::new("Some text").set_author("John"); + worksheet.insert_note(1, 1, ¬e)?; + + workbook.save(filename)?; + + Ok(()) +} + +#[test] +fn test_comment01_1() { + let test_runner = common::TestRunner::new() + .set_name("comment01") + .unique("1") + .set_function(create_new_xlsx_file_1) + .initialize(); + + test_runner.assert_eq(); + test_runner.cleanup(); +} + +#[test] +fn test_comment01_2() { + let test_runner = common::TestRunner::new() + .set_name("comment01") + .unique("2") + .set_function(create_new_xlsx_file_2) + .initialize(); + + test_runner.assert_eq(); + test_runner.cleanup(); +} diff --git a/tests/integration/comment02.rs b/tests/integration/comment02.rs new file mode 100644 index 00000000..3599fcc2 --- /dev/null +++ b/tests/integration/comment02.rs @@ -0,0 +1,39 @@ +// Test case that compares a file generated by rust_xlsxwriter with a file +// created by Excel. +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright 2022-2024, John McNamara, jmcnamara@cpan.org + +use crate::common; +use rust_xlsxwriter::{Note, Workbook, XlsxError}; + +// Create rust_xlsxwriter file to compare against Excel file. +fn create_new_xlsx_file(filename: &str) -> Result<(), XlsxError> { + let mut workbook = Workbook::new(); + + let worksheet = workbook.add_worksheet(); + worksheet.set_default_note_author("John"); + + worksheet.write(0, 0, "Foo")?; + + let note = Note::new("Some text"); + worksheet.insert_note(1, 1, ¬e)?; + + worksheet.insert_note(16, 3, &Note::new("More text"))?; + + workbook.save(filename)?; + + Ok(()) +} + +#[test] +fn test_comment02() { + let test_runner = common::TestRunner::new() + .set_name("comment02") + .set_function(create_new_xlsx_file) + .initialize(); + + test_runner.assert_eq(); + test_runner.cleanup(); +} diff --git a/tests/integration/comment03.rs b/tests/integration/comment03.rs new file mode 100644 index 00000000..73254041 --- /dev/null +++ b/tests/integration/comment03.rs @@ -0,0 +1,38 @@ +// Test case that compares a file generated by rust_xlsxwriter with a file +// created by Excel. +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright 2022-2024, John McNamara, jmcnamara@cpan.org + +use crate::common; +use rust_xlsxwriter::{Note, Workbook, XlsxError}; + +// Create rust_xlsxwriter file to compare against Excel file. +fn create_new_xlsx_file(filename: &str) -> Result<(), XlsxError> { + let mut workbook = Workbook::new(); + + let worksheet = workbook.add_worksheet(); + worksheet.set_default_note_author("John"); + + worksheet.write(0, 0, "Foo")?; + + let note = Note::new("Some text"); + worksheet.insert_note(0, 0, ¬e)?; + worksheet.insert_note(1048575, 16383, ¬e)?; + + workbook.save(filename)?; + + Ok(()) +} + +#[test] +fn test_comment03() { + let test_runner = common::TestRunner::new() + .set_name("comment03") + .set_function(create_new_xlsx_file) + .initialize(); + + test_runner.assert_eq(); + test_runner.cleanup(); +} diff --git a/tests/integration/comment04.rs b/tests/integration/comment04.rs new file mode 100644 index 00000000..3e68e1fd --- /dev/null +++ b/tests/integration/comment04.rs @@ -0,0 +1,45 @@ +// Test case that compares a file generated by rust_xlsxwriter with a file +// created by Excel. +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright 2022-2024, John McNamara, jmcnamara@cpan.org + +use crate::common; +use rust_xlsxwriter::{Note, Workbook, XlsxError}; + +// Create rust_xlsxwriter file to compare against Excel file. +fn create_new_xlsx_file(filename: &str) -> Result<(), XlsxError> { + let mut workbook = Workbook::new(); + + let worksheet1 = workbook.add_worksheet(); + worksheet1.set_default_note_author("John"); + worksheet1.write(0, 0, "Foo")?; + + let note = Note::new("Some text"); + worksheet1.insert_note(1, 1, ¬e)?; + + let _worksheet2 = workbook.add_worksheet(); + + let worksheet3 = workbook.add_worksheet(); + worksheet3.set_default_note_author("John"); + worksheet3.write(0, 0, "Bar")?; + + let note = Note::new("More text"); + worksheet3.insert_note(6, 2, ¬e)?; + + workbook.save(filename)?; + + Ok(()) +} + +#[test] +fn test_comment04() { + let test_runner = common::TestRunner::new() + .set_name("comment04") + .set_function(create_new_xlsx_file) + .initialize(); + + test_runner.assert_eq(); + test_runner.cleanup(); +} diff --git a/tests/integration/comment05.rs b/tests/integration/comment05.rs new file mode 100644 index 00000000..7e3b62f6 --- /dev/null +++ b/tests/integration/comment05.rs @@ -0,0 +1,50 @@ +// Test case that compares a file generated by rust_xlsxwriter with a file +// created by Excel. +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright 2022-2024, John McNamara, jmcnamara@cpan.org + +use crate::common; +use rust_xlsxwriter::{Note, Workbook, XlsxError}; + +// Create rust_xlsxwriter file to compare against Excel file. +fn create_new_xlsx_file(filename: &str) -> Result<(), XlsxError> { + let mut workbook = Workbook::new(); + + let worksheet1 = workbook.add_worksheet(); + worksheet1.set_default_note_author("John"); + + let note = Note::new("Some text"); + + for row in 0..=127 { + for col in 0..=15 { + worksheet1.insert_note(row, col, ¬e)?; + } + } + + let _worksheet2 = workbook.add_worksheet(); + + let worksheet3 = workbook.add_worksheet(); + worksheet3.set_default_note_author("John"); + + let note = Note::new("More text"); + worksheet3.insert_note(0, 0, ¬e)?; + + workbook.save(filename)?; + + workbook.save(filename)?; + + Ok(()) +} + +#[test] +fn test_comment05() { + let test_runner = common::TestRunner::new() + .set_name("comment05") + .set_function(create_new_xlsx_file) + .initialize(); + + test_runner.assert_eq(); + test_runner.cleanup(); +} diff --git a/tests/integration/comment09.rs b/tests/integration/comment09.rs new file mode 100644 index 00000000..cd6e4ce7 --- /dev/null +++ b/tests/integration/comment09.rs @@ -0,0 +1,43 @@ +// Test case that compares a file generated by rust_xlsxwriter with a file +// created by Excel. +// +// SPDX-License-Identifier: MIT OR Apache-2.0 +// +// Copyright 2022-2024, John McNamara, jmcnamara@cpan.org + +use crate::common; +use rust_xlsxwriter::{Note, Workbook, XlsxError}; + +// Create rust_xlsxwriter file to compare against Excel file. +fn create_new_xlsx_file(filename: &str) -> Result<(), XlsxError> { + let mut workbook = Workbook::new(); + + let worksheet = workbook.add_worksheet(); + worksheet.set_default_note_author("John"); + + // Set explicit Note author names. + let note = Note::new("Some text").set_author("John"); + worksheet.insert_note(0, 0, ¬e)?; + + let note = Note::new("Some text").set_author("Perl"); + worksheet.insert_note(1, 0, ¬e)?; + + // This one should get the default author name. + let note = Note::new("Some text"); + worksheet.insert_note(2, 0, ¬e)?; + + workbook.save(filename)?; + + Ok(()) +} + +#[test] +fn test_comment09() { + let test_runner = common::TestRunner::new() + .set_name("comment09") + .set_function(create_new_xlsx_file) + .initialize(); + + test_runner.assert_eq(); + test_runner.cleanup(); +} diff --git a/tests/integration/main.rs b/tests/integration/main.rs index b2addb97..1112c59d 100644 --- a/tests/integration/main.rs +++ b/tests/integration/main.rs @@ -472,6 +472,12 @@ mod chart_title03; mod chart_up_down_bars01; mod chart_up_down_bars02; mod chart_up_down_bars03; +mod comment01; +mod comment02; +mod comment03; +mod comment04; +mod comment05; +mod comment09; mod cond_format01; mod cond_format02; mod cond_format03;