Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/pandas/io/excel/_odswriter.py: 14%
143 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1from __future__ import annotations
3from collections import defaultdict
4import datetime
5from typing import (
6 TYPE_CHECKING,
7 Any,
8 DefaultDict,
9 Tuple,
10 cast,
11)
13import pandas._libs.json as json
14from pandas._typing import (
15 FilePath,
16 StorageOptions,
17 WriteExcelBuffer,
18)
20from pandas.io.excel._base import ExcelWriter
21from pandas.io.excel._util import (
22 combine_kwargs,
23 validate_freeze_panes,
24)
26if TYPE_CHECKING: 26 ↛ 27line 26 didn't jump to line 27, because the condition on line 26 was never true
27 from odf.opendocument import OpenDocumentSpreadsheet
29 from pandas.io.formats.excel import ExcelCell
32class ODSWriter(ExcelWriter):
33 _engine = "odf"
34 _supported_extensions = (".ods",)
36 def __init__(
37 self,
38 path: FilePath | WriteExcelBuffer | ExcelWriter,
39 engine: str | None = None,
40 date_format: str | None = None,
41 datetime_format=None,
42 mode: str = "w",
43 storage_options: StorageOptions = None,
44 if_sheet_exists: str | None = None,
45 engine_kwargs: dict[str, Any] | None = None,
46 **kwargs,
47 ) -> None:
48 from odf.opendocument import OpenDocumentSpreadsheet
50 if mode == "a":
51 raise ValueError("Append mode is not supported with odf!")
53 super().__init__(
54 path,
55 mode=mode,
56 storage_options=storage_options,
57 if_sheet_exists=if_sheet_exists,
58 engine_kwargs=engine_kwargs,
59 )
61 engine_kwargs = combine_kwargs(engine_kwargs, kwargs)
63 self._book = OpenDocumentSpreadsheet(**engine_kwargs)
64 self._style_dict: dict[str, str] = {}
66 @property
67 def book(self):
68 """
69 Book instance of class odf.opendocument.OpenDocumentSpreadsheet.
71 This attribute can be used to access engine-specific features.
72 """
73 return self._book
75 @book.setter
76 def book(self, other: OpenDocumentSpreadsheet) -> None:
77 """
78 Set book instance. Class type will depend on the engine used.
79 """
80 self._deprecate_set_book()
81 self._book = other
83 @property
84 def sheets(self) -> dict[str, Any]:
85 """Mapping of sheet names to sheet objects."""
86 from odf.table import Table
88 result = {
89 sheet.getAttribute("name"): sheet
90 for sheet in self.book.getElementsByType(Table)
91 }
92 return result
94 def _save(self) -> None:
95 """
96 Save workbook to disk.
97 """
98 for sheet in self.sheets.values():
99 self.book.spreadsheet.addElement(sheet)
100 self.book.save(self._handles.handle)
102 def _write_cells(
103 self,
104 cells: list[ExcelCell],
105 sheet_name: str | None = None,
106 startrow: int = 0,
107 startcol: int = 0,
108 freeze_panes: tuple[int, int] | None = None,
109 ) -> None:
110 """
111 Write the frame cells using odf
112 """
113 from odf.table import (
114 Table,
115 TableCell,
116 TableRow,
117 )
118 from odf.text import P
120 sheet_name = self._get_sheet_name(sheet_name)
121 assert sheet_name is not None
123 if sheet_name in self.sheets:
124 wks = self.sheets[sheet_name]
125 else:
126 wks = Table(name=sheet_name)
127 self.book.spreadsheet.addElement(wks)
129 if validate_freeze_panes(freeze_panes):
130 freeze_panes = cast(Tuple[int, int], freeze_panes)
131 self._create_freeze_panes(sheet_name, freeze_panes)
133 for _ in range(startrow):
134 wks.addElement(TableRow())
136 rows: DefaultDict = defaultdict(TableRow)
137 col_count: DefaultDict = defaultdict(int)
139 for cell in sorted(cells, key=lambda cell: (cell.row, cell.col)):
140 # only add empty cells if the row is still empty
141 if not col_count[cell.row]:
142 for _ in range(startcol):
143 rows[cell.row].addElement(TableCell())
145 # fill with empty cells if needed
146 for _ in range(cell.col - col_count[cell.row]):
147 rows[cell.row].addElement(TableCell())
148 col_count[cell.row] += 1
150 pvalue, tc = self._make_table_cell(cell)
151 rows[cell.row].addElement(tc)
152 col_count[cell.row] += 1
153 p = P(text=pvalue)
154 tc.addElement(p)
156 # add all rows to the sheet
157 if len(rows) > 0:
158 for row_nr in range(max(rows.keys()) + 1):
159 wks.addElement(rows[row_nr])
161 def _make_table_cell_attributes(self, cell) -> dict[str, int | str]:
162 """Convert cell attributes to OpenDocument attributes
164 Parameters
165 ----------
166 cell : ExcelCell
167 Spreadsheet cell data
169 Returns
170 -------
171 attributes : Dict[str, Union[int, str]]
172 Dictionary with attributes and attribute values
173 """
174 attributes: dict[str, int | str] = {}
175 style_name = self._process_style(cell.style)
176 if style_name is not None:
177 attributes["stylename"] = style_name
178 if cell.mergestart is not None and cell.mergeend is not None:
179 attributes["numberrowsspanned"] = max(1, cell.mergestart)
180 attributes["numbercolumnsspanned"] = cell.mergeend
181 return attributes
183 def _make_table_cell(self, cell) -> tuple[object, Any]:
184 """Convert cell data to an OpenDocument spreadsheet cell
186 Parameters
187 ----------
188 cell : ExcelCell
189 Spreadsheet cell data
191 Returns
192 -------
193 pvalue, cell : Tuple[str, TableCell]
194 Display value, Cell value
195 """
196 from odf.table import TableCell
198 attributes = self._make_table_cell_attributes(cell)
199 val, fmt = self._value_with_fmt(cell.val)
200 pvalue = value = val
201 if isinstance(val, bool):
202 value = str(val).lower()
203 pvalue = str(val).upper()
204 if isinstance(val, datetime.datetime):
205 # Fast formatting
206 value = val.isoformat()
207 # Slow but locale-dependent
208 pvalue = val.strftime("%c")
209 return (
210 pvalue,
211 TableCell(valuetype="date", datevalue=value, attributes=attributes),
212 )
213 elif isinstance(val, datetime.date):
214 # Fast formatting
215 value = f"{val.year}-{val.month:02d}-{val.day:02d}"
216 # Slow but locale-dependent
217 pvalue = val.strftime("%x")
218 return (
219 pvalue,
220 TableCell(valuetype="date", datevalue=value, attributes=attributes),
221 )
222 else:
223 class_to_cell_type = {
224 str: "string",
225 int: "float",
226 float: "float",
227 bool: "boolean",
228 }
229 return (
230 pvalue,
231 TableCell(
232 valuetype=class_to_cell_type[type(val)],
233 value=value,
234 attributes=attributes,
235 ),
236 )
238 def _process_style(self, style: dict[str, Any]) -> str:
239 """Convert a style dictionary to a OpenDocument style sheet
241 Parameters
242 ----------
243 style : Dict
244 Style dictionary
246 Returns
247 -------
248 style_key : str
249 Unique style key for later reference in sheet
250 """
251 from odf.style import (
252 ParagraphProperties,
253 Style,
254 TableCellProperties,
255 TextProperties,
256 )
258 if style is None:
259 return None
260 style_key = json.dumps(style)
261 if style_key in self._style_dict:
262 return self._style_dict[style_key]
263 name = f"pd{len(self._style_dict)+1}"
264 self._style_dict[style_key] = name
265 odf_style = Style(name=name, family="table-cell")
266 if "font" in style:
267 font = style["font"]
268 if font.get("bold", False):
269 odf_style.addElement(TextProperties(fontweight="bold"))
270 if "borders" in style:
271 borders = style["borders"]
272 for side, thickness in borders.items():
273 thickness_translation = {"thin": "0.75pt solid #000000"}
274 odf_style.addElement(
275 TableCellProperties(
276 attributes={f"border{side}": thickness_translation[thickness]}
277 )
278 )
279 if "alignment" in style:
280 alignment = style["alignment"]
281 horizontal = alignment.get("horizontal")
282 if horizontal:
283 odf_style.addElement(ParagraphProperties(textalign=horizontal))
284 vertical = alignment.get("vertical")
285 if vertical:
286 odf_style.addElement(TableCellProperties(verticalalign=vertical))
287 self.book.styles.addElement(odf_style)
288 return name
290 def _create_freeze_panes(
291 self, sheet_name: str, freeze_panes: tuple[int, int]
292 ) -> None:
293 """
294 Create freeze panes in the sheet.
296 Parameters
297 ----------
298 sheet_name : str
299 Name of the spreadsheet
300 freeze_panes : tuple of (int, int)
301 Freeze pane location x and y
302 """
303 from odf.config import (
304 ConfigItem,
305 ConfigItemMapEntry,
306 ConfigItemMapIndexed,
307 ConfigItemMapNamed,
308 ConfigItemSet,
309 )
311 config_item_set = ConfigItemSet(name="ooo:view-settings")
312 self.book.settings.addElement(config_item_set)
314 config_item_map_indexed = ConfigItemMapIndexed(name="Views")
315 config_item_set.addElement(config_item_map_indexed)
317 config_item_map_entry = ConfigItemMapEntry()
318 config_item_map_indexed.addElement(config_item_map_entry)
320 config_item_map_named = ConfigItemMapNamed(name="Tables")
321 config_item_map_entry.addElement(config_item_map_named)
323 config_item_map_entry = ConfigItemMapEntry(name=sheet_name)
324 config_item_map_named.addElement(config_item_map_entry)
326 config_item_map_entry.addElement(
327 ConfigItem(name="HorizontalSplitMode", type="short", text="2")
328 )
329 config_item_map_entry.addElement(
330 ConfigItem(name="VerticalSplitMode", type="short", text="2")
331 )
332 config_item_map_entry.addElement(
333 ConfigItem(
334 name="HorizontalSplitPosition", type="int", text=str(freeze_panes[0])
335 )
336 )
337 config_item_map_entry.addElement(
338 ConfigItem(
339 name="VerticalSplitPosition", type="int", text=str(freeze_panes[1])
340 )
341 )
342 config_item_map_entry.addElement(
343 ConfigItem(name="PositionRight", type="int", text=str(freeze_panes[0]))
344 )
345 config_item_map_entry.addElement(
346 ConfigItem(name="PositionBottom", type="int", text=str(freeze_panes[1]))
347 )