diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/openpyxl/worksheet/_writer.py')
-rw-r--r-- | .venv/lib/python3.12/site-packages/openpyxl/worksheet/_writer.py | 390 |
1 files changed, 390 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/worksheet/_writer.py b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/_writer.py new file mode 100644 index 00000000..df381d2b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/openpyxl/worksheet/_writer.py @@ -0,0 +1,390 @@ +# Copyright (c) 2010-2024 openpyxl + +import atexit +from collections import defaultdict +from io import BytesIO +import os +from tempfile import NamedTemporaryFile +from warnings import warn + +from openpyxl.xml.functions import xmlfile +from openpyxl.xml.constants import SHEET_MAIN_NS + +from openpyxl.comments.comment_sheet import CommentRecord +from openpyxl.packaging.relationship import Relationship, RelationshipList +from openpyxl.styles.differential import DifferentialStyle + +from .dimensions import SheetDimension +from .hyperlink import HyperlinkList +from .merge import MergeCell, MergeCells +from .related import Related +from .table import TablePartList + +from openpyxl.cell._writer import write_cell + + +ALL_TEMP_FILES = [] + +@atexit.register +def _openpyxl_shutdown(): + for path in ALL_TEMP_FILES: + if os.path.exists(path): + os.remove(path) + + +def create_temporary_file(suffix=''): + fobj = NamedTemporaryFile(mode='w+', suffix=suffix, + prefix='openpyxl.', delete=False) + filename = fobj.name + fobj.close() + ALL_TEMP_FILES.append(filename) + return filename + + +class WorksheetWriter: + + + def __init__(self, ws, out=None): + self.ws = ws + self.ws._hyperlinks = [] + self.ws._comments = [] + if out is None: + out = create_temporary_file() + self.out = out + self._rels = RelationshipList() + self.xf = self.get_stream() + next(self.xf) # start generator + + + def write_properties(self): + props = self.ws.sheet_properties + self.xf.send(props.to_tree()) + + + def write_dimensions(self): + """ + Write worksheet size if known + """ + ref = getattr(self.ws, 'calculate_dimension', None) + if ref: + dim = SheetDimension(ref()) + self.xf.send(dim.to_tree()) + + + def write_format(self): + self.ws.sheet_format.outlineLevelCol = self.ws.column_dimensions.max_outline + fmt = self.ws.sheet_format + self.xf.send(fmt.to_tree()) + + + def write_views(self): + views = self.ws.views + self.xf.send(views.to_tree()) + + + def write_cols(self): + cols = self.ws.column_dimensions + self.xf.send(cols.to_tree()) + + + def write_top(self): + """ + Write all elements up to rows: + properties + dimensions + views + format + cols + """ + self.write_properties() + self.write_dimensions() + self.write_views() + self.write_format() + self.write_cols() + + + def rows(self): + """Return all rows, and any cells that they contain""" + # order cells by row + rows = defaultdict(list) + for (row, col), cell in sorted(self.ws._cells.items()): + rows[row].append(cell) + + # add empty rows if styling has been applied + for row in self.ws.row_dimensions.keys() - rows.keys(): + rows[row] = [] + + return sorted(rows.items()) + + + def write_rows(self): + xf = self.xf.send(True) + + with xf.element("sheetData"): + for row_idx, row in self.rows(): + self.write_row(xf, row, row_idx) + + self.xf.send(None) # return control to generator + + + def write_row(self, xf, row, row_idx): + attrs = {'r': f"{row_idx}"} + dims = self.ws.row_dimensions + attrs.update(dims.get(row_idx, {})) + + with xf.element("row", attrs): + + for cell in row: + if cell._comment is not None: + comment = CommentRecord.from_cell(cell) + self.ws._comments.append(comment) + if ( + cell._value is None + and not cell.has_style + and not cell._comment + ): + continue + write_cell(xf, self.ws, cell, cell.has_style) + + + def write_protection(self): + prot = self.ws.protection + if prot: + self.xf.send(prot.to_tree()) + + + def write_scenarios(self): + scenarios = self.ws.scenarios + if scenarios: + self.xf.send(scenarios.to_tree()) + + + def write_filter(self): + flt = self.ws.auto_filter + if flt: + self.xf.send(flt.to_tree()) + + + def write_sort(self): + """ + As per discusion with the OOXML Working Group global sort state is not required. + openpyxl never reads it from existing files + """ + pass + + + def write_merged_cells(self): + merged = self.ws.merged_cells + if merged: + cells = [MergeCell(str(ref)) for ref in self.ws.merged_cells] + self.xf.send(MergeCells(mergeCell=cells).to_tree()) + + + def write_formatting(self): + df = DifferentialStyle() + wb = self.ws.parent + for cf in self.ws.conditional_formatting: + for rule in cf.rules: + if rule.dxf and rule.dxf != df: + rule.dxfId = wb._differential_styles.add(rule.dxf) + self.xf.send(cf.to_tree()) + + + def write_validations(self): + dv = self.ws.data_validations + if dv: + self.xf.send(dv.to_tree()) + + + def write_hyperlinks(self): + + links = self.ws._hyperlinks + + for link in links: + if link.target: + rel = Relationship(type="hyperlink", TargetMode="External", Target=link.target) + self._rels.append(rel) + link.id = rel.id + + if links: + self.xf.send(HyperlinkList(links).to_tree()) + + + def write_print(self): + print_options = self.ws.print_options + if print_options: + self.xf.send(print_options.to_tree()) + + + def write_margins(self): + margins = self.ws.page_margins + if margins: + self.xf.send(margins.to_tree()) + + + def write_page(self): + setup = self.ws.page_setup + if setup: + self.xf.send(setup.to_tree()) + + + def write_header(self): + hf = self.ws.HeaderFooter + if hf: + self.xf.send(hf.to_tree()) + + + def write_breaks(self): + brks = (self.ws.row_breaks, self.ws.col_breaks) + for brk in brks: + if brk: + self.xf.send(brk.to_tree()) + + + def write_drawings(self): + if self.ws._charts or self.ws._images: + rel = Relationship(type="drawing", Target="") + self._rels.append(rel) + drawing = Related() + drawing.id = rel.id + self.xf.send(drawing.to_tree("drawing")) + + + def write_legacy(self): + """ + Comments & VBA controls use VML and require an additional element + that is no longer in the specification. + """ + if (self.ws.legacy_drawing is not None or self.ws._comments): + legacy = Related(id="anysvml") + self.xf.send(legacy.to_tree("legacyDrawing")) + + + def write_tables(self): + tables = TablePartList() + + for table in self.ws.tables.values(): + if not table.tableColumns: + table._initialise_columns() + if table.headerRowCount: + try: + row = self.ws[table.ref][0] + for cell, col in zip(row, table.tableColumns): + if cell.data_type != "s": + warn("File may not be readable: column headings must be strings.") + col.name = str(cell.value) + except TypeError: + warn("Column headings are missing, file may not be readable") + rel = Relationship(Type=table._rel_type, Target="") + self._rels.append(rel) + table._rel_id = rel.Id + tables.append(Related(id=rel.Id)) + + if tables: + self.xf.send(tables.to_tree()) + + + def get_stream(self): + with xmlfile(self.out) as xf: + with xf.element("worksheet", xmlns=SHEET_MAIN_NS): + try: + while True: + el = (yield) + if el is True: + yield xf + elif el is None: # et_xmlfile chokes + continue + else: + xf.write(el) + except GeneratorExit: + pass + + + def write_tail(self): + """ + Write all elements after the rows + calc properties + protection + protected ranges # + scenarios + filters + sorts # always ignored + data consolidation # + custom views # + merged cells + phonetic properties # + conditional formatting + data validation + hyperlinks + print options + page margins + page setup + header + row breaks + col breaks + custom properties # + cell watches # + ignored errors # + smart tags # + drawing + drawingHF # + background # + OLE objects # + controls # + web publishing # + tables + """ + self.write_protection() + self.write_scenarios() + self.write_filter() + self.write_merged_cells() + self.write_formatting() + self.write_validations() + self.write_hyperlinks() + self.write_print() + self.write_margins() + self.write_page() + self.write_header() + self.write_breaks() + self.write_drawings() + self.write_legacy() + self.write_tables() + + + def write(self): + """ + High level + """ + self.write_top() + self.write_rows() + self.write_tail() + self.close() + + + def close(self): + """ + Close the context manager + """ + if self.xf: + self.xf.close() + + + def read(self): + """ + Close the context manager and return serialised XML + """ + self.close() + if isinstance(self.out, BytesIO): + return self.out.getvalue() + with open(self.out, "rb") as src: + out = src.read() + + return out + + + def cleanup(self): + """ + Remove tempfile + """ + os.remove(self.out) + ALL_TEMP_FILES.remove(self.out) |