Skip to content

Commit

Permalink
note: add initial support for cell notes/comments
Browse files Browse the repository at this point in the history
  • Loading branch information
jmcnamara committed Jul 21, 2024
1 parent d0c03f9 commit 84768cd
Show file tree
Hide file tree
Showing 28 changed files with 1,270 additions and 45 deletions.
10 changes: 5 additions & 5 deletions src/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`.
Expand Down Expand Up @@ -263,7 +263,7 @@ impl Button {
pub fn set_alt_text(mut self, alt_text: impl Into<String>) -> 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;
}

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -352,6 +352,6 @@ impl DrawingObject for Button {
}

fn drawing_type(&self) -> DrawingType {
DrawingType::Button
DrawingType::Vml
}
}
8 changes: 7 additions & 1 deletion src/chart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>) -> &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
}

Expand Down
188 changes: 188 additions & 0 deletions src/comment.rs
Original file line number Diff line number Diff line change
@@ -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, [email protected]

#![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<RowNum, BTreeMap<ColNum, Note>>,
pub(crate) note_authors: BTreeMap<String, usize>,
}

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 <comments> 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 <authors> element.
fn write_authors(&mut self) {
if self.note_authors.is_empty() {
return;
}

self.writer.xml_start_tag_only("authors");

let authors: Vec<String> = 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 <author> element.
fn write_author(&mut self, author: &str) {
self.writer.xml_data_element_only("author", author);
}

// Write the <commentList> 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 <comment> 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(&note.text);

self.writer.xml_end_tag("comment");
}

// Write the <text> 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 <rPr> 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 <sz> element.
fn write_font_size(&mut self) {
let attributes = [("val", "8".to_string())];

self.writer.xml_empty_tag("sz", &attributes);
}

// Write the <color> element.
fn write_font_color(&mut self) {
let attributes = [("indexed", "81".to_string())];

self.writer.xml_empty_tag("color", &attributes);
}

// Write the <rFont> element.
fn write_font_name(&mut self) {
let attributes = [("val", "Tahoma".to_string())];

self.writer.xml_empty_tag("rFont", &attributes);
}

// Write the <family> element.
fn write_font_family(&mut self) {
let attributes = [("val", "2".to_string())];

self.writer.xml_empty_tag("family", &attributes);
}

// Write the <t> element.
fn write_text(&mut self, text: &str) {
self.writer.xml_data_element_only("t", text);
}
}
9 changes: 9 additions & 0 deletions src/content_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
4 changes: 2 additions & 2 deletions src/drawing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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.
Expand Down
10 changes: 8 additions & 2 deletions src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>) -> 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
}

Expand Down Expand Up @@ -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()
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@
//!
mod app;
mod button;
mod comment;
mod content_types;
mod core;
mod custom;
Expand All @@ -200,6 +201,7 @@ mod format;
mod formula;
mod image;
mod metadata;
mod note;
mod packager;
mod properties;
mod protection;
Expand Down Expand Up @@ -237,13 +239,15 @@ mod test_functions;

// Re-export the public APIs.
pub use button::*;
pub use comment::*;
pub use data_validation::*;
pub use datetime::*;
pub use error::*;
pub use filter::*;
pub use format::*;
pub use formula::*;
pub use image::*;
pub use note::*;
pub use properties::*;
pub use protection::*;
pub use table::*;
Expand Down
Loading

0 comments on commit 84768cd

Please sign in to comment.