_reader.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. from __future__ import absolute_import
  2. # Copyright (c) 2010-2019 openpyxl
  3. """Reader for a single worksheet."""
  4. from warnings import warn
  5. # compatibility imports
  6. from openpyxl.compat import long
  7. from openpyxl.xml.functions import iterparse
  8. # package imports
  9. from openpyxl.cell import Cell
  10. from openpyxl.cell.text import Text
  11. from openpyxl.worksheet.dimensions import (
  12. ColumnDimension,
  13. RowDimension,
  14. SheetFormatProperties,
  15. )
  16. from openpyxl.xml.constants import (
  17. SHEET_MAIN_NS,
  18. EXT_TYPES,
  19. )
  20. from openpyxl.formatting.formatting import ConditionalFormatting
  21. from openpyxl.formula.translate import Translator
  22. from openpyxl.utils import (
  23. get_column_letter,
  24. coordinate_to_tuple,
  25. )
  26. from openpyxl.utils.datetime import from_excel, from_ISO8601, WINDOWS_EPOCH
  27. from openpyxl.descriptors.excel import ExtensionList
  28. from .filters import AutoFilter
  29. from .header_footer import HeaderFooter
  30. from .hyperlink import HyperlinkList
  31. from .merge import MergeCells
  32. from .page import PageMargins, PrintOptions, PrintPageSetup
  33. from .pagebreak import RowBreak, ColBreak
  34. from .protection import SheetProtection
  35. from .scenario import ScenarioList
  36. from .views import SheetViewList
  37. from .datavalidation import DataValidationList
  38. from .table import TablePartList
  39. from .properties import WorksheetProperties
  40. from .dimensions import SheetDimension
  41. from .related import Related
  42. CELL_TAG = '{%s}c' % SHEET_MAIN_NS
  43. VALUE_TAG = '{%s}v' % SHEET_MAIN_NS
  44. FORMULA_TAG = '{%s}f' % SHEET_MAIN_NS
  45. MERGE_TAG = '{%s}mergeCells' % SHEET_MAIN_NS
  46. INLINE_STRING = "{%s}is" % SHEET_MAIN_NS
  47. COL_TAG = '{%s}col' % SHEET_MAIN_NS
  48. ROW_TAG = '{%s}row' % SHEET_MAIN_NS
  49. CF_TAG = '{%s}conditionalFormatting' % SHEET_MAIN_NS
  50. LEGACY_TAG = '{%s}legacyDrawing' % SHEET_MAIN_NS
  51. PROT_TAG = '{%s}sheetProtection' % SHEET_MAIN_NS
  52. EXT_TAG = "{%s}extLst" % SHEET_MAIN_NS
  53. HYPERLINK_TAG = "{%s}hyperlinks" % SHEET_MAIN_NS
  54. TABLE_TAG = "{%s}tableParts" % SHEET_MAIN_NS
  55. PRINT_TAG = '{%s}printOptions' % SHEET_MAIN_NS
  56. MARGINS_TAG = '{%s}pageMargins' % SHEET_MAIN_NS
  57. PAGE_TAG = '{%s}pageSetup' % SHEET_MAIN_NS
  58. HEADER_TAG = '{%s}headerFooter' % SHEET_MAIN_NS
  59. FILTER_TAG = '{%s}autoFilter' % SHEET_MAIN_NS
  60. VALIDATION_TAG = '{%s}dataValidations' % SHEET_MAIN_NS
  61. PROPERTIES_TAG = '{%s}sheetPr' % SHEET_MAIN_NS
  62. VIEWS_TAG = '{%s}sheetViews' % SHEET_MAIN_NS
  63. FORMAT_TAG = '{%s}sheetFormatPr' % SHEET_MAIN_NS
  64. ROW_BREAK_TAG = '{%s}rowBreaks' % SHEET_MAIN_NS
  65. COL_BREAK_TAG = '{%s}colBreaks' % SHEET_MAIN_NS
  66. SCENARIOS_TAG = '{%s}scenarios' % SHEET_MAIN_NS
  67. DATA_TAG = '{%s}sheetData' % SHEET_MAIN_NS
  68. DIMENSION_TAG = '{%s}dimension' % SHEET_MAIN_NS
  69. CUSTOM_VIEWS_TAG = '{%s}customSheetViews' % SHEET_MAIN_NS
  70. def _cast_number(value):
  71. "Convert numbers as string to an int or float"
  72. if "." in value or "E" in value or "e" in value:
  73. return float(value)
  74. return long(value)
  75. class WorkSheetParser(object):
  76. def __init__(self, src, shared_strings, data_only=False,
  77. epoch=WINDOWS_EPOCH, date_formats=set()):
  78. self.min_row = self.min_col = self.max_row = self.max_column = None
  79. self.epoch = epoch
  80. self.source = src
  81. self.shared_strings = shared_strings
  82. self.data_only = data_only
  83. self.shared_formulae = {}
  84. self.array_formulae = {}
  85. self.max_row = self.max_column = 0
  86. self.tables = TablePartList()
  87. self.date_formats = date_formats
  88. self.row_dimensions = {}
  89. self.column_dimensions = {}
  90. self.number_formats = []
  91. self.keep_vba = False
  92. self.hyperlinks = HyperlinkList()
  93. self.formatting = []
  94. self.legacy_drawing = None
  95. self.merged_cells = None
  96. self.page_breaks = []
  97. def parse(self):
  98. dispatcher = {
  99. COL_TAG: self.parse_column_dimensions,
  100. PROT_TAG: self.parse_sheet_protection,
  101. EXT_TAG: self.parse_extensions,
  102. CF_TAG: self.parse_formatting,
  103. LEGACY_TAG: self.parse_legacy,
  104. ROW_BREAK_TAG: self.parse_row_breaks,
  105. COL_BREAK_TAG: self.parse_col_breaks,
  106. CUSTOM_VIEWS_TAG: self.parse_custom_views,
  107. }
  108. properties = {
  109. PRINT_TAG: ('print_options', PrintOptions),
  110. MARGINS_TAG: ('page_margins', PageMargins),
  111. PAGE_TAG: ('page_setup', PrintPageSetup),
  112. HEADER_TAG: ('HeaderFooter', HeaderFooter),
  113. FILTER_TAG: ('auto_filter', AutoFilter),
  114. VALIDATION_TAG: ('data_validations', DataValidationList),
  115. PROPERTIES_TAG: ('sheet_properties', WorksheetProperties),
  116. VIEWS_TAG: ('views', SheetViewList),
  117. FORMAT_TAG: ('sheet_format', SheetFormatProperties),
  118. SCENARIOS_TAG: ('scenarios', ScenarioList),
  119. TABLE_TAG: ('tables', TablePartList),
  120. HYPERLINK_TAG: ('hyperlinks', HyperlinkList),
  121. MERGE_TAG: ('merged_cells', MergeCells),
  122. }
  123. it = iterparse(self.source)
  124. for _, element in it:
  125. tag_name = element.tag
  126. if tag_name in dispatcher:
  127. dispatcher[tag_name](element)
  128. element.clear()
  129. elif tag_name in properties:
  130. prop = properties[tag_name]
  131. obj = prop[1].from_tree(element)
  132. setattr(self, prop[0], obj)
  133. element.clear()
  134. elif tag_name == ROW_TAG:
  135. row = self.parse_row(element)
  136. element.clear()
  137. yield row
  138. def parse_dimensions(self):
  139. """
  140. Get worksheet dimensions if they are provided.
  141. """
  142. it = iterparse(self.source)
  143. for _event, element in it:
  144. if element.tag == DIMENSION_TAG:
  145. dim = SheetDimension.from_tree(element)
  146. return dim.boundaries
  147. elif element.tag == DATA_TAG:
  148. # Dimensions missing
  149. break
  150. element.clear()
  151. def parse_cell(self, element):
  152. data_type = element.get('t', 'n')
  153. coordinate = element.get('r')
  154. self.max_column += 1
  155. style_id = element.get('s', 0)
  156. if style_id:
  157. style_id = int(style_id)
  158. if data_type == "inlineStr":
  159. value = None
  160. else:
  161. value = element.findtext(VALUE_TAG, None) or None
  162. if coordinate:
  163. row, column = coordinate_to_tuple(coordinate)
  164. else:
  165. row, column = self.max_row, self.max_column
  166. if not self.data_only and element.find(FORMULA_TAG) is not None:
  167. data_type = 'f'
  168. value = self.parse_formula(element)
  169. elif value is not None:
  170. if data_type == 'n':
  171. value = _cast_number(value)
  172. if style_id in self.date_formats:
  173. data_type = 'd'
  174. try:
  175. value = from_excel(value, self.epoch)
  176. except ValueError:
  177. msg = """Cell {0} is marked as a date but the serial value {1} is outside the limits for dates. The cell will be treated as an error.""".format(coordinate, value)
  178. warn(msg)
  179. data_type = "e"
  180. value = "#VALUE!"
  181. elif data_type == 's':
  182. value = self.shared_strings[int(value)]
  183. elif data_type == 'b':
  184. value = bool(int(value))
  185. elif data_type == "str":
  186. data_type = "s"
  187. elif data_type == 'd':
  188. value = from_ISO8601(value)
  189. elif data_type == 'inlineStr':
  190. child = element.find(INLINE_STRING)
  191. if child is not None:
  192. data_type = 's'
  193. richtext = Text.from_tree(child)
  194. value = richtext.content
  195. return {'row':row, 'column':column, 'value':value, 'data_type':data_type, 'style_id':style_id}
  196. def parse_formula(self, element):
  197. """
  198. possible formulae types: shared, array, datatable
  199. """
  200. formula = element.find(FORMULA_TAG)
  201. formula_type = formula.get('t')
  202. coordinate = element.get('r')
  203. value = "="
  204. if formula.text is not None:
  205. value += formula.text
  206. if formula_type == "array":
  207. self.array_formulae[coordinate] = dict(formula.attrib)
  208. elif formula_type == "shared":
  209. idx = formula.get('si')
  210. if idx in self.shared_formulae:
  211. trans = self.shared_formulae[idx]
  212. value = trans.translate_formula(coordinate)
  213. elif value != "=":
  214. self.shared_formulae[idx] = Translator(value, coordinate)
  215. return value
  216. def parse_column_dimensions(self, col):
  217. attrs = dict(col.attrib)
  218. column = get_column_letter(int(attrs['min']))
  219. attrs['index'] = column
  220. self.column_dimensions[column] = attrs
  221. def parse_row(self, row):
  222. attrs = dict(row.attrib)
  223. if "r" in attrs:
  224. self.max_row = int(attrs['r'])
  225. else:
  226. self.max_row += 1
  227. keys = set(attrs)
  228. for key in keys:
  229. if key.startswith('{'):
  230. del attrs[key]
  231. keys = set(attrs)
  232. if keys != set(['r', 'spans']) and keys != set(['r']):
  233. # don't create dimension objects unless they have relevant information
  234. self.row_dimensions[attrs['r']] = attrs
  235. cells = [self.parse_cell(el) for el in row]
  236. return self.max_row, cells
  237. def parse_formatting(self, element):
  238. cf = ConditionalFormatting.from_tree(element)
  239. self.formatting.append(cf)
  240. def parse_sheet_protection(self, element):
  241. protection = SheetProtection.from_tree(element)
  242. password = element.get("password")
  243. if password is not None:
  244. protection.set_password(password, True)
  245. self.protection = protection
  246. def parse_extensions(self, element):
  247. extLst = ExtensionList.from_tree(element)
  248. for e in extLst.ext:
  249. ext_type = EXT_TYPES.get(e.uri.upper(), "Unknown")
  250. msg = "{0} extension is not supported and will be removed".format(ext_type)
  251. warn(msg)
  252. def parse_legacy(self, element):
  253. obj = Related.from_tree(element)
  254. self.legacy_drawing = obj.id
  255. def parse_row_breaks(self, element):
  256. brk = RowBreak.from_tree(element)
  257. self.page_breaks.append(brk)
  258. def parse_col_breaks(self, element):
  259. brk = ColBreak.from_tree(element)
  260. self.page_breaks.append(brk)
  261. def parse_custom_views(self, element):
  262. # clear page_breaks to avoid duplication
  263. self.page_breaks = []
  264. class WorksheetReader(object):
  265. """
  266. Create a parser and apply it to a workbook
  267. """
  268. def __init__(self, ws, xml_source, shared_strings, data_only):
  269. self.ws = ws
  270. self.parser = WorkSheetParser(xml_source, shared_strings, data_only, ws.parent.epoch, ws.parent._date_formats)
  271. self.tables = []
  272. def bind_cells(self):
  273. for idx, row in self.parser.parse():
  274. for cell in row:
  275. style = self.ws.parent._cell_styles[cell['style_id']]
  276. c = Cell(self.ws, row=cell['row'], column=cell['column'], style_array=style)
  277. c._value = cell['value']
  278. c.data_type = cell['data_type']
  279. self.ws._cells[(cell['row'], cell['column'])] = c
  280. self.ws.formula_attributes = self.parser.array_formulae
  281. self.ws._current_row = self.parser.max_row
  282. def bind_formatting(self):
  283. for cf in self.parser.formatting:
  284. for rule in cf.rules:
  285. if rule.dxfId is not None:
  286. rule.dxf = self.ws.parent._differential_styles[rule.dxfId]
  287. self.ws.conditional_formatting[cf] = rule
  288. def bind_tables(self):
  289. for t in self.parser.tables.tablePart:
  290. rel = self.ws._rels[t.id]
  291. self.tables.append(rel.Target)
  292. def bind_merged_cells(self):
  293. if not self.parser.merged_cells:
  294. return
  295. for cr in self.parser.merged_cells.mergeCell:
  296. self.ws.merge_cells(cr.ref)
  297. def bind_hyperlinks(self):
  298. for link in self.parser.hyperlinks.hyperlink:
  299. if link.id:
  300. rel = self.ws._rels[link.id]
  301. link.target = rel.Target
  302. if ":" in link.ref:
  303. # range of cells
  304. for row in self.ws[link.ref]:
  305. for cell in row:
  306. try:
  307. cell.hyperlink = link
  308. except AttributeError:
  309. pass
  310. else:
  311. self.ws[link.ref].hyperlink = link
  312. def bind_col_dimensions(self):
  313. for col, cd in self.parser.column_dimensions.items():
  314. if 'style' in cd:
  315. key = int(cd['style'])
  316. cd['style'] = self.ws.parent._cell_styles[key]
  317. self.ws.column_dimensions[col] = ColumnDimension(self.ws, **cd)
  318. def bind_row_dimensions(self):
  319. for row, rd in self.parser.row_dimensions.items():
  320. if 's' in rd:
  321. key = int(rd['s'])
  322. rd['s'] = self.ws.parent._cell_styles[key]
  323. self.ws.row_dimensions[int(row)] = RowDimension(self.ws, **rd)
  324. def bind_properties(self):
  325. for k in ('print_options', 'page_margins', 'page_setup',
  326. 'HeaderFooter', 'auto_filter', 'data_validations',
  327. 'sheet_properties', 'views', 'sheet_format',
  328. 'page_breaks', 'scenarios', 'legacy_drawing'):
  329. v = getattr(self.parser, k, None)
  330. if v is not None:
  331. setattr(self.ws, k, v)
  332. def bind_all(self):
  333. self.bind_cells()
  334. self.bind_merged_cells()
  335. self.bind_hyperlinks()
  336. self.bind_formatting()
  337. self.bind_col_dimensions()
  338. self.bind_row_dimensions()
  339. self.bind_tables()
  340. self.bind_properties()