import re
from typing import Dict, Optional
import xlsxwriter
from .styling import ExcelStyle
# Forward declarations
# class ExcelStyle:
# pass
[docs]
class ExcelWorkbook:
"""Wrapper for xlsxwriter.Workbook with format caching."""
# Regex pattern for invalid characters in worksheet names
_INVALID_CHARS_PATTERN = r"[/\\?*:\[\]]"
# Excel's reserved worksheet name
_RESERVED_NAMES = ["History"]
def __init__(self, filename: str):
self.filename = filename
self._workbook = xlsxwriter.Workbook(filename)
self._format_cache: Dict[tuple, object] = {} # Cache for combined formats
[docs]
def validate_worksheet_name(self, name: Optional[str]) -> None:
r"""
Validate worksheet name according to Excel rules.
Rules:
- Names cannot be blank
- Names cannot contain more than 31 characters
- Names cannot contain any of these characters: / \ ? * : [ ]
- Names cannot begin or end with an apostrophe (')
- Names cannot be the reserved word "History"
Raises ValueError if the name is invalid.
"""
if name is None:
return # Allow None to use default worksheet name
if name == "":
raise ValueError("Worksheet name cannot be blank")
if len(name) > 31:
raise ValueError(f"Worksheet name cannot contain more than 31 characters (got {len(name)})")
if re.search(self._INVALID_CHARS_PATTERN, name):
raise ValueError(r"Worksheet name contains invalid characters. Cannot use any of: / \ ? * : [ ]")
if name.startswith("'") or name.endswith("'"):
raise ValueError("Worksheet name cannot begin or end with an apostrophe (')")
if name in self._RESERVED_NAMES:
raise ValueError(f"'{name}' is a reserved worksheet name in Excel")
[docs]
def add_worksheet(self, name: Optional[str] = None):
"""Add a new worksheet to the workbook with name validation."""
self.validate_worksheet_name(name)
return self._workbook.add_worksheet(name)
[docs]
def close(self):
"""Close the workbook file."""
self._workbook.close()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()