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>
This commit is contained in:
392
venv/lib/python3.12/site-packages/xlsxwriter/comments.py
Normal file
392
venv/lib/python3.12/site-packages/xlsxwriter/comments.py
Normal file
@@ -0,0 +1,392 @@
|
||||
###############################################################################
|
||||
#
|
||||
# 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)
|
||||
Reference in New Issue
Block a user