157 lines
4.7 KiB
Python
157 lines
4.7 KiB
Python
from typing import TYPE_CHECKING, Dict
|
|
|
|
import pandas._libs.json as json
|
|
from pandas._typing import StorageOptions
|
|
|
|
from pandas.io.excel._base import ExcelWriter
|
|
from pandas.io.excel._util import validate_freeze_panes
|
|
|
|
if TYPE_CHECKING:
|
|
from xlwt import XFStyle
|
|
|
|
|
|
class XlwtWriter(ExcelWriter):
|
|
engine = "xlwt"
|
|
supported_extensions = (".xls",)
|
|
|
|
def __init__(
|
|
self,
|
|
path,
|
|
engine=None,
|
|
encoding=None,
|
|
mode: str = "w",
|
|
storage_options: StorageOptions = None,
|
|
**engine_kwargs,
|
|
):
|
|
# Use the xlwt module as the Excel writer.
|
|
import xlwt
|
|
|
|
engine_kwargs["engine"] = engine
|
|
|
|
if mode == "a":
|
|
raise ValueError("Append mode is not supported with xlwt!")
|
|
|
|
super().__init__(
|
|
path, mode=mode, storage_options=storage_options, **engine_kwargs
|
|
)
|
|
|
|
if encoding is None:
|
|
encoding = "ascii"
|
|
self.book = xlwt.Workbook(encoding=encoding)
|
|
self.fm_datetime = xlwt.easyxf(num_format_str=self.datetime_format)
|
|
self.fm_date = xlwt.easyxf(num_format_str=self.date_format)
|
|
|
|
def save(self):
|
|
"""
|
|
Save workbook to disk.
|
|
"""
|
|
if self.sheets:
|
|
# fails when the ExcelWriter is just opened and then closed
|
|
self.book.save(self.handles.handle)
|
|
|
|
def write_cells(
|
|
self, cells, sheet_name=None, startrow=0, startcol=0, freeze_panes=None
|
|
):
|
|
|
|
sheet_name = self._get_sheet_name(sheet_name)
|
|
|
|
if sheet_name in self.sheets:
|
|
wks = self.sheets[sheet_name]
|
|
else:
|
|
wks = self.book.add_sheet(sheet_name)
|
|
self.sheets[sheet_name] = wks
|
|
|
|
if validate_freeze_panes(freeze_panes):
|
|
wks.set_panes_frozen(True)
|
|
wks.set_horz_split_pos(freeze_panes[0])
|
|
wks.set_vert_split_pos(freeze_panes[1])
|
|
|
|
style_dict: Dict[str, XFStyle] = {}
|
|
|
|
for cell in cells:
|
|
val, fmt = self._value_with_fmt(cell.val)
|
|
|
|
stylekey = json.dumps(cell.style)
|
|
if fmt:
|
|
stylekey += fmt
|
|
|
|
if stylekey in style_dict:
|
|
style = style_dict[stylekey]
|
|
else:
|
|
style = self._convert_to_style(cell.style, fmt)
|
|
style_dict[stylekey] = style
|
|
|
|
if cell.mergestart is not None and cell.mergeend is not None:
|
|
wks.write_merge(
|
|
startrow + cell.row,
|
|
startrow + cell.mergestart,
|
|
startcol + cell.col,
|
|
startcol + cell.mergeend,
|
|
val,
|
|
style,
|
|
)
|
|
else:
|
|
wks.write(startrow + cell.row, startcol + cell.col, val, style)
|
|
|
|
@classmethod
|
|
def _style_to_xlwt(
|
|
cls, item, firstlevel: bool = True, field_sep=",", line_sep=";"
|
|
) -> str:
|
|
"""
|
|
helper which recursively generate an xlwt easy style string
|
|
for example:
|
|
|
|
hstyle = {"font": {"bold": True},
|
|
"border": {"top": "thin",
|
|
"right": "thin",
|
|
"bottom": "thin",
|
|
"left": "thin"},
|
|
"align": {"horiz": "center"}}
|
|
will be converted to
|
|
font: bold on; \
|
|
border: top thin, right thin, bottom thin, left thin; \
|
|
align: horiz center;
|
|
"""
|
|
if hasattr(item, "items"):
|
|
if firstlevel:
|
|
it = [
|
|
f"{key}: {cls._style_to_xlwt(value, False)}"
|
|
for key, value in item.items()
|
|
]
|
|
out = f"{line_sep.join(it)} "
|
|
return out
|
|
else:
|
|
it = [
|
|
f"{key} {cls._style_to_xlwt(value, False)}"
|
|
for key, value in item.items()
|
|
]
|
|
out = f"{field_sep.join(it)} "
|
|
return out
|
|
else:
|
|
item = f"{item}"
|
|
item = item.replace("True", "on")
|
|
item = item.replace("False", "off")
|
|
return item
|
|
|
|
@classmethod
|
|
def _convert_to_style(cls, style_dict, num_format_str=None):
|
|
"""
|
|
converts a style_dict to an xlwt style object
|
|
|
|
Parameters
|
|
----------
|
|
style_dict : style dictionary to convert
|
|
num_format_str : optional number format string
|
|
"""
|
|
import xlwt
|
|
|
|
if style_dict:
|
|
xlwt_stylestr = cls._style_to_xlwt(style_dict)
|
|
style = xlwt.easyxf(xlwt_stylestr, field_sep=",", line_sep=";")
|
|
else:
|
|
style = xlwt.XFStyle()
|
|
if num_format_str is not None:
|
|
style.num_format_str = num_format_str
|
|
|
|
return style
|