cell.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. from __future__ import absolute_import
  2. # Copyright (c) 2010-2019 openpyxl
  3. """Manage individual cells in a spreadsheet.
  4. The Cell class is required to know its value and type, display options,
  5. and any other features of an Excel cell. Utilities for referencing
  6. cells using Excel's 'A1' column/row nomenclature are also provided.
  7. """
  8. __docformat__ = "restructuredtext en"
  9. # Python stdlib imports
  10. from copy import copy
  11. import datetime
  12. import re
  13. from itertools import islice, product
  14. from openpyxl.compat import (
  15. unicode,
  16. basestring,
  17. bytes,
  18. NUMERIC_TYPES,
  19. deprecated,
  20. )
  21. from openpyxl.utils.units import (
  22. DEFAULT_ROW_HEIGHT,
  23. DEFAULT_COLUMN_WIDTH
  24. )
  25. from openpyxl.utils.datetime import (
  26. to_excel,
  27. time_to_days,
  28. timedelta_to_days,
  29. from_excel
  30. )
  31. from openpyxl.utils.exceptions import (
  32. IllegalCharacterError
  33. )
  34. from openpyxl.utils.units import points_to_pixels
  35. from openpyxl.utils import (
  36. get_column_letter,
  37. column_index_from_string,
  38. )
  39. from openpyxl.utils.inference import (
  40. cast_numeric,
  41. cast_percentage,
  42. cast_percentage,
  43. )
  44. from openpyxl.styles import numbers, is_date_format
  45. from openpyxl.styles.styleable import StyleableObject
  46. from openpyxl.worksheet.hyperlink import Hyperlink
  47. # constants
  48. TIME_TYPES = (datetime.datetime, datetime.date, datetime.time, datetime.timedelta)
  49. TIME_FORMATS = {
  50. datetime.datetime:numbers.FORMAT_DATE_DATETIME,
  51. datetime.date:numbers.FORMAT_DATE_YYYYMMDD2,
  52. datetime.time:numbers.FORMAT_DATE_TIME6,
  53. datetime.timedelta:numbers.FORMAT_DATE_TIMEDELTA,
  54. }
  55. try:
  56. from pandas import Timestamp
  57. TIME_TYPES = TIME_TYPES + (Timestamp,)
  58. TIME_FORMATS[Timestamp] = numbers.FORMAT_DATE_DATETIME
  59. except ImportError:
  60. pass
  61. STRING_TYPES = (basestring, unicode, bytes)
  62. KNOWN_TYPES = NUMERIC_TYPES + TIME_TYPES + STRING_TYPES + (bool, type(None))
  63. ILLEGAL_CHARACTERS_RE = re.compile(r'[\000-\010]|[\013-\014]|[\016-\037]')
  64. ERROR_CODES = ('#NULL!', '#DIV/0!', '#VALUE!', '#REF!', '#NAME?', '#NUM!',
  65. '#N/A')
  66. ERROR_CODES = ERROR_CODES
  67. TYPE_STRING = 's'
  68. TYPE_FORMULA = 'f'
  69. TYPE_NUMERIC = 'n'
  70. TYPE_BOOL = 'b'
  71. TYPE_NULL = 'n'
  72. TYPE_INLINE = 'inlineStr'
  73. TYPE_ERROR = 'e'
  74. TYPE_FORMULA_CACHE_STRING = 'str'
  75. VALID_TYPES = (TYPE_STRING, TYPE_FORMULA, TYPE_NUMERIC, TYPE_BOOL,
  76. TYPE_NULL, TYPE_INLINE, TYPE_ERROR, TYPE_FORMULA_CACHE_STRING)
  77. _TYPES = {int:'n', float:'n', unicode:'s', basestring:'s', bool:'b'}
  78. def get_type(t, value):
  79. if isinstance(value, NUMERIC_TYPES):
  80. dt = 'n'
  81. elif isinstance(value, STRING_TYPES):
  82. dt = 's'
  83. elif isinstance(value, TIME_TYPES):
  84. dt = 'd'
  85. else:
  86. return
  87. _TYPES[t] = dt
  88. return dt
  89. class Cell(StyleableObject):
  90. """Describes cell associated properties.
  91. Properties of interest include style, type, value, and address.
  92. """
  93. __slots__ = (
  94. 'row',
  95. 'column',
  96. '_value',
  97. 'data_type',
  98. 'parent',
  99. '_hyperlink',
  100. '_comment',
  101. )
  102. def __init__(self, worksheet, row=None, column=None, value=None, style_array=None):
  103. super(Cell, self).__init__(worksheet, style_array)
  104. self.row = row
  105. """Row number of this cell (1-based)"""
  106. self.column = column
  107. """Column number of this cell (1-based)"""
  108. # _value is the stored value, while value is the displayed value
  109. self._value = None
  110. self._hyperlink = None
  111. self.data_type = 'n'
  112. if value is not None:
  113. self.value = value
  114. self._comment = None
  115. @property
  116. def coordinate(self):
  117. """This cell's coordinate (ex. 'A5')"""
  118. col = get_column_letter(self.column)
  119. return "%s%d" % (col, self.row)
  120. @property
  121. def col_idx(self):
  122. """The numerical index of the column"""
  123. return self.column
  124. @property
  125. def column_letter(self):
  126. return get_column_letter(self.column)
  127. @property
  128. def encoding(self):
  129. return self.parent.encoding
  130. @property
  131. def base_date(self):
  132. return self.parent.parent.epoch
  133. @property
  134. def guess_types(self):
  135. return getattr(self.parent.parent, 'guess_types', False)
  136. def __repr__(self):
  137. return "<Cell {0!r}.{1}>".format(self.parent.title, self.coordinate)
  138. def check_string(self, value):
  139. """Check string coding, length, and line break character"""
  140. if value is None:
  141. return
  142. # convert to unicode string
  143. if not isinstance(value, unicode):
  144. value = unicode(value, self.encoding)
  145. value = unicode(value)
  146. # string must never be longer than 32,767 characters
  147. # truncate if necessary
  148. value = value[:32767]
  149. if next(ILLEGAL_CHARACTERS_RE.finditer(value), None):
  150. raise IllegalCharacterError
  151. return value
  152. def check_error(self, value):
  153. """Tries to convert Error" else N/A"""
  154. try:
  155. return unicode(value)
  156. except UnicodeDecodeError:
  157. return u'#N/A'
  158. @deprecated("Type coercion will no longer be supported")
  159. def set_explicit_value(self, value=None, data_type=TYPE_STRING):
  160. """Coerce values according to their explicit type"""
  161. if data_type not in VALID_TYPES:
  162. raise ValueError('Invalid data type: %s' % data_type)
  163. if isinstance(value, STRING_TYPES):
  164. value = self.check_string(value)
  165. self._value = value
  166. self.data_type = data_type
  167. def _bind_value(self, value):
  168. """Given a value, infer the correct data type"""
  169. self.data_type = "n"
  170. t = type(value)
  171. try:
  172. dt = _TYPES[t]
  173. except KeyError:
  174. dt = get_type(t, value)
  175. if dt is not None:
  176. self.data_type = dt
  177. if dt == 'n' or dt == 'b':
  178. pass
  179. elif dt == 'd':
  180. if not is_date_format(self.number_format):
  181. self.number_format = TIME_FORMATS[t]
  182. self.data_type = "d"
  183. elif dt == "s":
  184. value = self.check_string(value)
  185. if len(value) > 1 and value.startswith("="):
  186. self.data_type = 'f'
  187. elif value in ERROR_CODES:
  188. self.data_type = 'e'
  189. elif self.guess_types: # deprecated
  190. value = self._infer_value(value)
  191. elif value is not None:
  192. raise ValueError("Cannot convert {0!r} to Excel".format(value))
  193. self._value = value
  194. def _infer_value(self, value):
  195. """Given a string, infer type and formatting options."""
  196. if not isinstance(value, unicode):
  197. value = str(value)
  198. # number detection
  199. v = cast_numeric(value)
  200. if v is None:
  201. # percentage detection
  202. v = cast_percentage(value)
  203. if v is None:
  204. # time detection
  205. v = cast_percentage(value)
  206. return value
  207. @property
  208. def value(self):
  209. """Get or set the value held in the cell.
  210. :type: depends on the value (string, float, int or
  211. :class:`datetime.datetime`)
  212. """
  213. return self._value
  214. @value.setter
  215. def value(self, value):
  216. """Set the value and infer type and display options."""
  217. self._bind_value(value)
  218. @property
  219. def internal_value(self):
  220. """Always returns the value for excel."""
  221. return self._value
  222. @property
  223. def hyperlink(self):
  224. """Return the hyperlink target or an empty string"""
  225. return self._hyperlink
  226. @hyperlink.setter
  227. def hyperlink(self, val):
  228. """Set value and display for hyperlinks in a cell.
  229. Automatically sets the `value` of the cell with link text,
  230. but you can modify it afterwards by setting the `value`
  231. property, and the hyperlink will remain.
  232. Hyperlink is removed if set to ``None``."""
  233. if val is None:
  234. self._hyperlink = None
  235. else:
  236. if not isinstance(val, Hyperlink):
  237. val = Hyperlink(ref="", target=val)
  238. val.ref = self.coordinate
  239. self._hyperlink = val
  240. if self._value is None:
  241. self.value = val.target or val.location
  242. @property
  243. def is_date(self):
  244. """True if the value is formatted as a date
  245. :type: bool
  246. """
  247. return self.data_type == 'd' or (
  248. self.data_type == 'n' and is_date_format(self.number_format)
  249. )
  250. def offset(self, row=0, column=0):
  251. """Returns a cell location relative to this cell.
  252. :param row: number of rows to offset
  253. :type row: int
  254. :param column: number of columns to offset
  255. :type column: int
  256. :rtype: :class:`openpyxl.cell.Cell`
  257. """
  258. offset_column = self.col_idx + column
  259. offset_row = self.row + row
  260. return self.parent.cell(column=offset_column, row=offset_row)
  261. @property
  262. def comment(self):
  263. """ Returns the comment associated with this cell
  264. :type: :class:`openpyxl.comments.Comment`
  265. """
  266. return self._comment
  267. @comment.setter
  268. def comment(self, value):
  269. """
  270. Assign a comment to a cell
  271. """
  272. if value is not None:
  273. if value.parent:
  274. value = copy(value)
  275. value.bind(self)
  276. elif value is None and self._comment:
  277. self._comment.unbind()
  278. self._comment = value
  279. class MergedCell(StyleableObject):
  280. """
  281. Describes the properties of a cell in a merged cell and helps to
  282. display the borders of the merged cell.
  283. The value of a MergedCell is always None.
  284. """
  285. __slots__ = ('row', 'column')
  286. _value = None
  287. data_type = "n"
  288. comment = None
  289. hyperlink = None
  290. def __init__(self, worksheet, row=None, column=None):
  291. super(MergedCell, self).__init__(worksheet)
  292. self.row = row
  293. self.column = column
  294. def __repr__(self):
  295. return "<MergedCell {0!r}.{1}>".format(self.parent.title, self.coordinate)
  296. coordinate = Cell.coordinate
  297. _comment = comment
  298. value = _value
  299. def WriteOnlyCell(ws=None, value=None):
  300. return Cell(worksheet=ws, column=1, row=1, value=value)