123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 |
- from __future__ import absolute_import
- # Copyright (c) 2010-2019 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.compat import unicode
- 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._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 set(self.ws.row_dimensions.keys()) - set(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': '%d' % 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 = HyperlinkList()
- for link in self.ws._hyperlinks:
- if link.target:
- rel = Relationship(type="hyperlink", TargetMode="External", Target=link.target)
- self._rels.append(rel)
- link.id = rel.id
- links.hyperlink.append(link)
- if links:
- self.xf.send(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.page_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:
- if not table.tableColumns:
- table._initialise_columns()
- if table.headerRowCount:
- 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 = unicode(cell.value)
- 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)
|