_xlsxwriter.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. import pandas._libs.json as json
  2. from pandas.io.excel._base import ExcelWriter
  3. from pandas.io.excel._util import _validate_freeze_panes
  4. class _XlsxStyler:
  5. # Map from openpyxl-oriented styles to flatter xlsxwriter representation
  6. # Ordering necessary for both determinism and because some are keyed by
  7. # prefixes of others.
  8. STYLE_MAPPING = {
  9. "font": [
  10. (("name",), "font_name"),
  11. (("sz",), "font_size"),
  12. (("size",), "font_size"),
  13. (("color", "rgb"), "font_color"),
  14. (("color",), "font_color"),
  15. (("b",), "bold"),
  16. (("bold",), "bold"),
  17. (("i",), "italic"),
  18. (("italic",), "italic"),
  19. (("u",), "underline"),
  20. (("underline",), "underline"),
  21. (("strike",), "font_strikeout"),
  22. (("vertAlign",), "font_script"),
  23. (("vertalign",), "font_script"),
  24. ],
  25. "number_format": [(("format_code",), "num_format"), ((), "num_format")],
  26. "protection": [(("locked",), "locked"), (("hidden",), "hidden")],
  27. "alignment": [
  28. (("horizontal",), "align"),
  29. (("vertical",), "valign"),
  30. (("text_rotation",), "rotation"),
  31. (("wrap_text",), "text_wrap"),
  32. (("indent",), "indent"),
  33. (("shrink_to_fit",), "shrink"),
  34. ],
  35. "fill": [
  36. (("patternType",), "pattern"),
  37. (("patterntype",), "pattern"),
  38. (("fill_type",), "pattern"),
  39. (("start_color", "rgb"), "fg_color"),
  40. (("fgColor", "rgb"), "fg_color"),
  41. (("fgcolor", "rgb"), "fg_color"),
  42. (("start_color",), "fg_color"),
  43. (("fgColor",), "fg_color"),
  44. (("fgcolor",), "fg_color"),
  45. (("end_color", "rgb"), "bg_color"),
  46. (("bgColor", "rgb"), "bg_color"),
  47. (("bgcolor", "rgb"), "bg_color"),
  48. (("end_color",), "bg_color"),
  49. (("bgColor",), "bg_color"),
  50. (("bgcolor",), "bg_color"),
  51. ],
  52. "border": [
  53. (("color", "rgb"), "border_color"),
  54. (("color",), "border_color"),
  55. (("style",), "border"),
  56. (("top", "color", "rgb"), "top_color"),
  57. (("top", "color"), "top_color"),
  58. (("top", "style"), "top"),
  59. (("top",), "top"),
  60. (("right", "color", "rgb"), "right_color"),
  61. (("right", "color"), "right_color"),
  62. (("right", "style"), "right"),
  63. (("right",), "right"),
  64. (("bottom", "color", "rgb"), "bottom_color"),
  65. (("bottom", "color"), "bottom_color"),
  66. (("bottom", "style"), "bottom"),
  67. (("bottom",), "bottom"),
  68. (("left", "color", "rgb"), "left_color"),
  69. (("left", "color"), "left_color"),
  70. (("left", "style"), "left"),
  71. (("left",), "left"),
  72. ],
  73. }
  74. @classmethod
  75. def convert(cls, style_dict, num_format_str=None):
  76. """
  77. converts a style_dict to an xlsxwriter format dict
  78. Parameters
  79. ----------
  80. style_dict : style dictionary to convert
  81. num_format_str : optional number format string
  82. """
  83. # Create a XlsxWriter format object.
  84. props = {}
  85. if num_format_str is not None:
  86. props["num_format"] = num_format_str
  87. if style_dict is None:
  88. return props
  89. if "borders" in style_dict:
  90. style_dict = style_dict.copy()
  91. style_dict["border"] = style_dict.pop("borders")
  92. for style_group_key, style_group in style_dict.items():
  93. for src, dst in cls.STYLE_MAPPING.get(style_group_key, []):
  94. # src is a sequence of keys into a nested dict
  95. # dst is a flat key
  96. if dst in props:
  97. continue
  98. v = style_group
  99. for k in src:
  100. try:
  101. v = v[k]
  102. except (KeyError, TypeError):
  103. break
  104. else:
  105. props[dst] = v
  106. if isinstance(props.get("pattern"), str):
  107. # TODO: support other fill patterns
  108. props["pattern"] = 0 if props["pattern"] == "none" else 1
  109. for k in ["border", "top", "right", "bottom", "left"]:
  110. if isinstance(props.get(k), str):
  111. try:
  112. props[k] = [
  113. "none",
  114. "thin",
  115. "medium",
  116. "dashed",
  117. "dotted",
  118. "thick",
  119. "double",
  120. "hair",
  121. "mediumDashed",
  122. "dashDot",
  123. "mediumDashDot",
  124. "dashDotDot",
  125. "mediumDashDotDot",
  126. "slantDashDot",
  127. ].index(props[k])
  128. except ValueError:
  129. props[k] = 2
  130. if isinstance(props.get("font_script"), str):
  131. props["font_script"] = ["baseline", "superscript", "subscript"].index(
  132. props["font_script"]
  133. )
  134. if isinstance(props.get("underline"), str):
  135. props["underline"] = {
  136. "none": 0,
  137. "single": 1,
  138. "double": 2,
  139. "singleAccounting": 33,
  140. "doubleAccounting": 34,
  141. }[props["underline"]]
  142. return props
  143. class _XlsxWriter(ExcelWriter):
  144. engine = "xlsxwriter"
  145. supported_extensions = (".xlsx",)
  146. def __init__(
  147. self,
  148. path,
  149. engine=None,
  150. date_format=None,
  151. datetime_format=None,
  152. mode="w",
  153. **engine_kwargs,
  154. ):
  155. # Use the xlsxwriter module as the Excel writer.
  156. import xlsxwriter
  157. if mode == "a":
  158. raise ValueError("Append mode is not supported with xlsxwriter!")
  159. super().__init__(
  160. path,
  161. engine=engine,
  162. date_format=date_format,
  163. datetime_format=datetime_format,
  164. mode=mode,
  165. **engine_kwargs,
  166. )
  167. self.book = xlsxwriter.Workbook(path, **engine_kwargs)
  168. def save(self):
  169. """
  170. Save workbook to disk.
  171. """
  172. return self.book.close()
  173. def write_cells(
  174. self, cells, sheet_name=None, startrow=0, startcol=0, freeze_panes=None
  175. ):
  176. # Write the frame cells using xlsxwriter.
  177. sheet_name = self._get_sheet_name(sheet_name)
  178. if sheet_name in self.sheets:
  179. wks = self.sheets[sheet_name]
  180. else:
  181. wks = self.book.add_worksheet(sheet_name)
  182. self.sheets[sheet_name] = wks
  183. style_dict = {"null": None}
  184. if _validate_freeze_panes(freeze_panes):
  185. wks.freeze_panes(*(freeze_panes))
  186. for cell in cells:
  187. val, fmt = self._value_with_fmt(cell.val)
  188. stylekey = json.dumps(cell.style)
  189. if fmt:
  190. stylekey += fmt
  191. if stylekey in style_dict:
  192. style = style_dict[stylekey]
  193. else:
  194. style = self.book.add_format(_XlsxStyler.convert(cell.style, fmt))
  195. style_dict[stylekey] = style
  196. if cell.mergestart is not None and cell.mergeend is not None:
  197. wks.merge_range(
  198. startrow + cell.row,
  199. startcol + cell.col,
  200. startrow + cell.mergestart,
  201. startcol + cell.mergeend,
  202. val,
  203. style,
  204. )
  205. else:
  206. wks.write(startrow + cell.row, startcol + cell.col, val, style)