Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/openpyxl/worksheet/_writer.py : 23%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright (c) 2010-2020 openpyxl
3import atexit
4from collections import defaultdict
5from io import BytesIO
6import os
7from tempfile import NamedTemporaryFile
8from warnings import warn
10from openpyxl.xml.functions import xmlfile
11from openpyxl.xml.constants import SHEET_MAIN_NS
13from openpyxl.comments.comment_sheet import CommentRecord
14from openpyxl.packaging.relationship import Relationship, RelationshipList
15from openpyxl.styles.differential import DifferentialStyle
17from .dimensions import SheetDimension
18from .hyperlink import HyperlinkList
19from .merge import MergeCell, MergeCells
20from .related import Related
21from .table import TablePartList
23from openpyxl.cell._writer import write_cell
26ALL_TEMP_FILES = []
28@atexit.register
29def _openpyxl_shutdown():
30 for path in ALL_TEMP_FILES:
31 if os.path.exists(path):
32 os.remove(path)
35def create_temporary_file(suffix=''):
36 fobj = NamedTemporaryFile(mode='w+', suffix=suffix,
37 prefix='openpyxl.', delete=False)
38 filename = fobj.name
39 fobj.close()
40 ALL_TEMP_FILES.append(filename)
41 return filename
44class WorksheetWriter:
47 def __init__(self, ws, out=None):
48 self.ws = ws
49 self.ws._comments = []
50 if out is None:
51 out = create_temporary_file()
52 self.out = out
53 self._rels = RelationshipList()
54 self.xf = self.get_stream()
55 next(self.xf) # start generator
58 def write_properties(self):
59 props = self.ws.sheet_properties
60 self.xf.send(props.to_tree())
63 def write_dimensions(self):
64 """
65 Write worksheet size if known
66 """
67 ref = getattr(self.ws, 'calculate_dimension', None)
68 if ref:
69 dim = SheetDimension(ref())
70 self.xf.send(dim.to_tree())
73 def write_format(self):
74 self.ws.sheet_format.outlineLevelCol = self.ws.column_dimensions.max_outline
75 fmt = self.ws.sheet_format
76 self.xf.send(fmt.to_tree())
79 def write_views(self):
80 views = self.ws.views
81 self.xf.send(views.to_tree())
84 def write_cols(self):
85 cols = self.ws.column_dimensions
86 self.xf.send(cols.to_tree())
89 def write_top(self):
90 """
91 Write all elements up to rows:
92 properties
93 dimensions
94 views
95 format
96 cols
97 """
98 self.write_properties()
99 self.write_dimensions()
100 self.write_views()
101 self.write_format()
102 self.write_cols()
105 def rows(self):
106 """Return all rows, and any cells that they contain"""
107 # order cells by row
108 rows = defaultdict(list)
109 for (row, col), cell in sorted(self.ws._cells.items()):
110 rows[row].append(cell)
112 # add empty rows if styling has been applied
113 for row in self.ws.row_dimensions.keys() - rows.keys():
114 rows[row] = []
116 return sorted(rows.items())
119 def write_rows(self):
120 xf = self.xf.send(True)
122 with xf.element("sheetData"):
123 for row_idx, row in self.rows():
124 self.write_row(xf, row, row_idx)
126 self.xf.send(None) # return control to generator
129 def write_row(self, xf, row, row_idx):
130 attrs = {'r': f"{row_idx}"}
131 dims = self.ws.row_dimensions
132 attrs.update(dims.get(row_idx, {}))
134 with xf.element("row", attrs):
136 for cell in row:
137 if cell._comment is not None:
138 comment = CommentRecord.from_cell(cell)
139 self.ws._comments.append(comment)
140 if (
141 cell._value is None
142 and not cell.has_style
143 and not cell._comment
144 ):
145 continue
146 write_cell(xf, self.ws, cell, cell.has_style)
149 def write_protection(self):
150 prot = self.ws.protection
151 if prot:
152 self.xf.send(prot.to_tree())
155 def write_scenarios(self):
156 scenarios = self.ws.scenarios
157 if scenarios:
158 self.xf.send(scenarios.to_tree())
161 def write_filter(self):
162 flt = self.ws.auto_filter
163 if flt:
164 self.xf.send(flt.to_tree())
167 def write_sort(self):
168 """
169 As per discusion with the OOXML Working Group global sort state is not required.
170 openpyxl never reads it from existing files
171 """
172 pass
175 def write_merged_cells(self):
176 merged = self.ws.merged_cells
177 if merged:
178 cells = [MergeCell(str(ref)) for ref in self.ws.merged_cells]
179 self.xf.send(MergeCells(mergeCell=cells).to_tree())
182 def write_formatting(self):
183 df = DifferentialStyle()
184 wb = self.ws.parent
185 for cf in self.ws.conditional_formatting:
186 for rule in cf.rules:
187 if rule.dxf and rule.dxf != df:
188 rule.dxfId = wb._differential_styles.add(rule.dxf)
189 self.xf.send(cf.to_tree())
192 def write_validations(self):
193 dv = self.ws.data_validations
194 if dv:
195 self.xf.send(dv.to_tree())
198 def write_hyperlinks(self):
199 links = HyperlinkList()
201 for link in self.ws._hyperlinks:
202 if link.target:
203 rel = Relationship(type="hyperlink", TargetMode="External", Target=link.target)
204 self._rels.append(rel)
205 link.id = rel.id
206 links.hyperlink.append(link)
208 if links:
209 self.xf.send(links.to_tree())
212 def write_print(self):
213 print_options = self.ws.print_options
214 if print_options:
215 self.xf.send(print_options.to_tree())
218 def write_margins(self):
219 margins = self.ws.page_margins
220 if margins:
221 self.xf.send(margins.to_tree())
224 def write_page(self):
225 setup = self.ws.page_setup
226 if setup:
227 self.xf.send(setup.to_tree())
230 def write_header(self):
231 hf = self.ws.HeaderFooter
232 if hf:
233 self.xf.send(hf.to_tree())
236 def write_breaks(self):
237 brks = (self.ws.row_breaks, self.ws.col_breaks)
238 for brk in brks:
239 if brk:
240 self.xf.send(brk.to_tree())
243 def write_drawings(self):
244 if self.ws._charts or self.ws._images:
245 rel = Relationship(type="drawing", Target="")
246 self._rels.append(rel)
247 drawing = Related()
248 drawing.id = rel.id
249 self.xf.send(drawing.to_tree("drawing"))
252 def write_legacy(self):
253 """
254 Comments & VBA controls use VML and require an additional element
255 that is no longer in the specification.
256 """
257 if (self.ws.legacy_drawing is not None or self.ws._comments):
258 legacy = Related(id="anysvml")
259 self.xf.send(legacy.to_tree("legacyDrawing"))
262 def write_tables(self):
263 tables = TablePartList()
265 for table in self.ws._tables.values():
266 if not table.tableColumns:
267 table._initialise_columns()
268 if table.headerRowCount:
269 try:
270 row = self.ws[table.ref][0]
271 for cell, col in zip(row, table.tableColumns):
272 if cell.data_type != "s":
273 warn("File may not be readable: column headings must be strings.")
274 col.name = str(cell.value)
275 except TypeError:
276 warn("Column headings are missing, file may not be readable")
277 rel = Relationship(Type=table._rel_type, Target="")
278 self._rels.append(rel)
279 table._rel_id = rel.Id
280 tables.append(Related(id=rel.Id))
282 if tables:
283 self.xf.send(tables.to_tree())
286 def get_stream(self):
287 with xmlfile(self.out) as xf:
288 with xf.element("worksheet", xmlns=SHEET_MAIN_NS):
289 try:
290 while True:
291 el = (yield)
292 if el is True:
293 yield xf
294 elif el is None: # et_xmlfile chokes
295 continue
296 else:
297 xf.write(el)
298 except GeneratorExit:
299 pass
302 def write_tail(self):
303 """
304 Write all elements after the rows
305 calc properties
306 protection
307 protected ranges #
308 scenarios
309 filters
310 sorts # always ignored
311 data consolidation #
312 custom views #
313 merged cells
314 phonetic properties #
315 conditional formatting
316 data validation
317 hyperlinks
318 print options
319 page margins
320 page setup
321 header
322 row breaks
323 col breaks
324 custom properties #
325 cell watches #
326 ignored errors #
327 smart tags #
328 drawing
329 drawingHF #
330 background #
331 OLE objects #
332 controls #
333 web publishing #
334 tables
335 """
336 self.write_protection()
337 self.write_scenarios()
338 self.write_filter()
339 self.write_merged_cells()
340 self.write_formatting()
341 self.write_validations()
342 self.write_hyperlinks()
343 self.write_print()
344 self.write_margins()
345 self.write_page()
346 self.write_header()
347 self.write_breaks()
348 self.write_drawings()
349 self.write_legacy()
350 self.write_tables()
353 def write(self):
354 """
355 High level
356 """
357 self.write_top()
358 self.write_rows()
359 self.write_tail()
360 self.close()
363 def close(self):
364 """
365 Close the context manager
366 """
367 if self.xf:
368 self.xf.close()
371 def read(self):
372 """
373 Close the context manager and return serialised XML
374 """
375 self.close()
376 if isinstance(self.out, BytesIO):
377 return self.out.getvalue()
378 with open(self.out, "rb") as src:
379 out = src.read()
381 return out
384 def cleanup(self):
385 """
386 Remove tempfile
387 """
388 os.remove(self.out)
389 ALL_TEMP_FILES.remove(self.out)