Files
bussines_case_automation/venv/lib/python3.12/site-packages/xlsxwriter/comments.py
andrei 0e2e1bddba Add xlsxwriter-based Excel generation scripts with openpyxl implementation
- Created create_excel_xlsxwriter.py and update_excel_xlsxwriter.py
- Uses openpyxl exclusively to preserve Excel formatting and formulas
- Updated server.js to use new xlsxwriter scripts for form submissions
- Maintains all original functionality while ensuring proper Excel file handling

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-22 13:53:06 +00:00

393 lines
11 KiB
Python

###############################################################################
#
# Comments - A class for writing the Excel XLSX Worksheet file.
#
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2013-2025, John McNamara, jmcnamara@cpan.org
#
from typing import Dict, List, Optional, Union
from xlsxwriter.color import Color
from . import xmlwriter
from .utility import _preserve_whitespace, xl_cell_to_rowcol, xl_rowcol_to_cell
###########################################################################
#
# A comment type class.
#
###########################################################################
class CommentType:
"""
A class to represent a comment in an Excel worksheet.
"""
def __init__(
self,
row: int,
col: int,
text: str,
options: Optional[Dict[str, Union[str, int, float]]] = None,
) -> None:
"""
Initialize a Comment instance.
Args:
row (int): The row number of the comment.
col (int): The column number of the comment.
text (str): The text of the comment.
options (dict): Additional options for the comment.
"""
self.row: int = row
self.col: int = col
self.text: str = text
self.author: Optional[str] = None
self.color: Color = Color("#ffffe1")
self.start_row: int = 0
self.start_col: int = 0
self.is_visible: Optional[bool] = None
self.width: float = 128
self.height: float = 74
self.x_scale: float = 1
self.y_scale: float = 1
self.x_offset: int = 0
self.y_offset: int = 0
self.font_size: float = 8
self.font_name: str = "Tahoma"
self.font_family: int = 2
self.vertices: List[Union[int, float]] = []
# Set the default start cell and offsets for the comment.
self.set_offsets(self.row, self.col)
# Set any user supplied options.
self._set_user_options(options)
def _set_user_options(
self, options: Optional[Dict[str, Union[str, int, float]]] = None
) -> None:
"""
This method handles the additional optional parameters to
``write_comment()``.
"""
if options is None:
return
# Overwrite the defaults with any user supplied values. Incorrect or
# misspelled parameters are silently ignored.
width = options.get("width")
if width and isinstance(width, (int, float)):
self.width = width
height = options.get("height")
if height and isinstance(height, (int, float)):
self.height = height
x_offset = options.get("x_offset")
if x_offset and isinstance(x_offset, int):
self.x_offset = x_offset
y_offset = options.get("y_offset")
if y_offset and isinstance(y_offset, int):
self.y_offset = y_offset
start_col = options.get("start_col")
if start_col and isinstance(start_col, int):
self.start_col = start_col
start_row = options.get("start_row")
if start_row and isinstance(start_row, int):
self.start_row = start_row
font_size = options.get("font_size")
if font_size and isinstance(font_size, (int, float)):
self.font_size = font_size
font_name = options.get("font_name")
if font_name and isinstance(font_name, str):
self.font_name = font_name
font_family = options.get("font_family")
if font_family and isinstance(font_family, int):
self.font_family = font_family
author = options.get("author")
if author and isinstance(author, str):
self.author = author
visible = options.get("visible")
if visible is not None and isinstance(visible, bool):
self.is_visible = visible
if options.get("color"):
# Set the comment background color.
self.color = Color._from_value(options["color"])
# Convert a cell reference to a row and column.
start_cell = options.get("start_cell")
if start_cell and isinstance(start_cell, str):
(start_row, start_col) = xl_cell_to_rowcol(start_cell)
self.start_row = start_row
self.start_col = start_col
# Scale the size of the comment box if required.
x_scale = options.get("x_scale")
if x_scale and isinstance(x_scale, (int, float)):
self.width = self.width * x_scale
y_scale = options.get("y_scale")
if y_scale and isinstance(y_scale, (int, float)):
self.height = self.height * y_scale
# Round the dimensions to the nearest pixel.
self.width = int(0.5 + self.width)
self.height = int(0.5 + self.height)
def set_offsets(self, row: int, col: int) -> None:
"""
Set the default start cell and offsets for the comment. These are
generally a fixed offset relative to the parent cell. However there are
some edge cases for cells at the, well, edges.
"""
row_max = 1048576
col_max = 16384
if self.row == 0:
self.y_offset = 2
self.start_row = 0
elif self.row == row_max - 3:
self.y_offset = 16
self.start_row = row_max - 7
elif self.row == row_max - 2:
self.y_offset = 16
self.start_row = row_max - 6
elif self.row == row_max - 1:
self.y_offset = 14
self.start_row = row_max - 5
else:
self.y_offset = 10
self.start_row = row - 1
if self.col == col_max - 3:
self.x_offset = 49
self.start_col = col_max - 6
elif self.col == col_max - 2:
self.x_offset = 49
self.start_col = col_max - 5
elif self.col == col_max - 1:
self.x_offset = 49
self.start_col = col_max - 4
else:
self.x_offset = 15
self.start_col = col + 1
###########################################################################
#
# The file writer class for the Excel XLSX Comments file.
#
###########################################################################
class Comments(xmlwriter.XMLwriter):
"""
A class for writing the Excel XLSX Comments file.
"""
###########################################################################
#
# Public API.
#
###########################################################################
def __init__(self) -> None:
"""
Constructor.
"""
super().__init__()
self.author_ids = {}
###########################################################################
#
# Private API.
#
###########################################################################
def _assemble_xml_file(
self, comments_data: Optional[List[CommentType]] = None
) -> None:
# Assemble and write the XML file.
if comments_data is None:
comments_data = []
# Write the XML declaration.
self._xml_declaration()
# Write the comments element.
self._write_comments()
# Write the authors element.
self._write_authors(comments_data)
# Write the commentList element.
self._write_comment_list(comments_data)
self._xml_end_tag("comments")
# Close the file.
self._xml_close()
###########################################################################
#
# XML methods.
#
###########################################################################
def _write_comments(self) -> None:
# Write the <comments> element.
xmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
attributes = [("xmlns", xmlns)]
self._xml_start_tag("comments", attributes)
def _write_authors(self, comment_data: List[CommentType]) -> None:
# Write the <authors> element.
author_count = 0
self._xml_start_tag("authors")
for comment in comment_data:
author = comment.author
if author is not None and author not in self.author_ids:
# Store the author id.
self.author_ids[author] = author_count
author_count += 1
# Write the author element.
self._write_author(author)
self._xml_end_tag("authors")
def _write_author(self, data: str) -> None:
# Write the <author> element.
self._xml_data_element("author", data)
def _write_comment_list(self, comment_data: List[CommentType]) -> None:
# Write the <commentList> element.
self._xml_start_tag("commentList")
for comment in comment_data:
# Look up the author id.
author_id = -1
if comment.author is not None:
author_id = self.author_ids[comment.author]
# Write the comment element.
self._write_comment(comment, author_id)
self._xml_end_tag("commentList")
def _write_comment(self, comment: CommentType, author_id: int) -> None:
# Write the <comment> element.
ref = xl_rowcol_to_cell(comment.row, comment.col)
attributes = [("ref", ref)]
if author_id != -1:
attributes.append(("authorId", f"{author_id}"))
self._xml_start_tag("comment", attributes)
# Write the text element.
self._write_text(comment)
self._xml_end_tag("comment")
def _write_text(self, comment: CommentType) -> None:
# Write the <text> element.
self._xml_start_tag("text")
# Write the text r element.
self._write_text_r(comment)
self._xml_end_tag("text")
def _write_text_r(self, comment: CommentType) -> None:
# Write the <r> element.
self._xml_start_tag("r")
# Write the rPr element.
self._write_r_pr(comment)
# Write the text r element.
self._write_text_t(comment.text)
self._xml_end_tag("r")
def _write_text_t(self, text: str) -> None:
# Write the text <t> element.
attributes = []
if _preserve_whitespace(text):
attributes.append(("xml:space", "preserve"))
self._xml_data_element("t", text, attributes)
def _write_r_pr(self, comment: CommentType) -> None:
# Write the <rPr> element.
self._xml_start_tag("rPr")
# Write the sz element.
self._write_sz(comment.font_size)
# Write the color element.
self._write_color()
# Write the rFont element.
self._write_r_font(comment.font_name)
# Write the family element.
self._write_family(comment.font_family)
self._xml_end_tag("rPr")
def _write_sz(self, font_size: float) -> None:
# Write the <sz> element.
attributes = [("val", font_size)]
self._xml_empty_tag("sz", attributes)
def _write_color(self) -> None:
# Write the <color> element.
attributes = [("indexed", 81)]
self._xml_empty_tag("color", attributes)
def _write_r_font(self, font_name: str) -> None:
# Write the <rFont> element.
attributes = [("val", font_name)]
self._xml_empty_tag("rFont", attributes)
def _write_family(self, font_family: int) -> None:
# Write the <family> element.
attributes = [("val", font_family)]
self._xml_empty_tag("family", attributes)