from __future__ import annotations from datetime import UTC, date, datetime, timedelta from pathlib import Path from xml.sax.saxutils import escape from zipfile import ZIP_DEFLATED, ZipFile MAIN_NS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main" REL_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships" PKG_REL_NS = "http://schemas.openxmlformats.org/package/2006/relationships" CONTENT_TYPES_NS = "http://schemas.openxmlformats.org/package/2006/content-types" DOC_PROPS_NS = "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" CORE_PROPS_NS = "http://schemas.openxmlformats.org/package/2006/metadata/core-properties" DC_NS = "http://purl.org/dc/elements/1.1/" DCTERMS_NS = "http://purl.org/dc/terms/" XSI_NS = "http://www.w3.org/2001/XMLSchema-instance" def date_to_excel_serial(value: date | datetime | str) -> int: if isinstance(value, str): parsed = datetime.strptime(value, "%Y-%m-%d").date() elif isinstance(value, datetime): parsed = value.date() else: parsed = value return (parsed - date(1899, 12, 30)).days def _col_name(index: int) -> str: letters = "" while index: index, remainder = divmod(index - 1, 26) letters = chr(65 + remainder) + letters return letters def _cell_xml(value: object, row_index: int, col_index: int) -> str: ref = f"{_col_name(col_index)}{row_index}" if value is None or value == "": return f'' if isinstance(value, bool): return f'{1 if value else 0}' if isinstance(value, (int, float)): return f'{value}' if isinstance(value, (date, datetime)): return f'{date_to_excel_serial(value)}' return f'{escape(str(value))}' def export_xlsx(path: str | Path, sheet_name: str, rows: list[list[object]]) -> None: path = Path(path) path.parent.mkdir(parents=True, exist_ok=True) max_col = 1 max_row = max(1, len(rows)) sheet_rows = [] for row_index, row in enumerate(rows, start=1): max_col = max(max_col, len(row)) cells = "".join(_cell_xml(value, row_index, col_index) for col_index, value in enumerate(row, start=1)) sheet_rows.append(f'{cells}') dimension = f"A1:{_col_name(max_col)}{max_row}" worksheet_xml = ( '' f'' f'' '' '' '' + "".join(sheet_rows) + '' '' ) styles_xml = ( '' f'' '' '' '' '' '' '' '' '' ) content_types_xml = ( '' f'' '' '' '' '' '' '' '' '' ) root_rels_xml = ( '' f'' '' '' '' '' ) workbook_xml = ( '' f'' '' f'' '' ) workbook_rels_xml = ( '' f'' '' '' '' ) created = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ") core_xml = ( '' f'' 'Jewelry Inventory' f'{created}' f'{created}' '' ) app_xml = ( '' f'' 'Jewelry Inventory' '' ) with ZipFile(path, "w", compression=ZIP_DEFLATED) as archive: archive.writestr("[Content_Types].xml", content_types_xml) archive.writestr("_rels/.rels", root_rels_xml) archive.writestr("docProps/core.xml", core_xml) archive.writestr("docProps/app.xml", app_xml) archive.writestr("xl/workbook.xml", workbook_xml) archive.writestr("xl/_rels/workbook.xml.rels", workbook_rels_xml) archive.writestr("xl/styles.xml", styles_xml) archive.writestr("xl/worksheets/sheet1.xml", worksheet_xml)