defined_name.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. from __future__ import absolute_import
  2. # Copyright (c) 2010-2019 openpyxl
  3. import re
  4. from openpyxl.descriptors.serialisable import Serialisable
  5. from openpyxl.descriptors import (
  6. Alias,
  7. Typed,
  8. String,
  9. Float,
  10. Integer,
  11. Bool,
  12. NoneSet,
  13. Set,
  14. Sequence,
  15. Descriptor,
  16. )
  17. from openpyxl.compat import safe_string
  18. from openpyxl.formula import Tokenizer
  19. from openpyxl.utils.cell import (
  20. SHEETRANGE_RE,
  21. SHEET_TITLE,
  22. )
  23. RESERVED = frozenset(["Print_Area", "Print_Titles", "Criteria",
  24. "_FilterDatabase", "Extract", "Consolidate_Area",
  25. "Sheet_Title"])
  26. _names = "|".join(RESERVED)
  27. RESERVED_REGEX = re.compile(r"^_xlnm\.(?P<name>{0})".format(_names))
  28. COL_RANGE = r"""(?P<cols>[$]?[a-zA-Z]{1,3}:[$]?[a-zA-Z]{1,3})"""
  29. COL_RANGE_RE = re.compile(COL_RANGE)
  30. ROW_RANGE = r"""(?P<rows>[$]?\d+:[$]?\d+)"""
  31. ROW_RANGE_RE = re.compile(ROW_RANGE)
  32. TITLES_REGEX = re.compile("""{0}{1}?,?{2}?""".format(SHEET_TITLE, ROW_RANGE, COL_RANGE),
  33. re.VERBOSE)
  34. ### utilities
  35. def _unpack_print_titles(defn):
  36. """
  37. Extract rows and or columns from print titles so that they can be
  38. assigned to a worksheet
  39. """
  40. scanner = TITLES_REGEX.finditer(defn.value)
  41. kw = dict((k, v) for match in scanner
  42. for k, v in match.groupdict().items() if v)
  43. return kw.get('rows'), kw.get('cols')
  44. def _unpack_print_area(defn):
  45. """
  46. Extract print area
  47. """
  48. new = []
  49. for m in SHEETRANGE_RE.finditer(defn.value): # can be multiple
  50. coord = m.group("cells")
  51. if coord:
  52. new.append(coord)
  53. return new
  54. class DefinedName(Serialisable):
  55. tagname = "definedName"
  56. name = String() # unique per workbook/worksheet
  57. comment = String(allow_none=True)
  58. customMenu = String(allow_none=True)
  59. description = String(allow_none=True)
  60. help = String(allow_none=True)
  61. statusBar = String(allow_none=True)
  62. localSheetId = Integer(allow_none=True)
  63. hidden = Bool(allow_none=True)
  64. function = Bool(allow_none=True)
  65. vbProcedure = Bool(allow_none=True)
  66. xlm = Bool(allow_none=True)
  67. functionGroupId = Integer(allow_none=True)
  68. shortcutKey = String(allow_none=True)
  69. publishToServer = Bool(allow_none=True)
  70. workbookParameter = Bool(allow_none=True)
  71. attr_text = Descriptor()
  72. value = Alias("attr_text")
  73. def __init__(self,
  74. name=None,
  75. comment=None,
  76. customMenu=None,
  77. description=None,
  78. help=None,
  79. statusBar=None,
  80. localSheetId=None,
  81. hidden=None,
  82. function=None,
  83. vbProcedure=None,
  84. xlm=None,
  85. functionGroupId=None,
  86. shortcutKey=None,
  87. publishToServer=None,
  88. workbookParameter=None,
  89. attr_text=None
  90. ):
  91. self.name = name
  92. self.comment = comment
  93. self.customMenu = customMenu
  94. self.description = description
  95. self.help = help
  96. self.statusBar = statusBar
  97. self.localSheetId = localSheetId
  98. self.hidden = hidden
  99. self.function = function
  100. self.vbProcedure = vbProcedure
  101. self.xlm = xlm
  102. self.functionGroupId = functionGroupId
  103. self.shortcutKey = shortcutKey
  104. self.publishToServer = publishToServer
  105. self.workbookParameter = workbookParameter
  106. self.attr_text = attr_text
  107. @property
  108. def type(self):
  109. tok = Tokenizer("=" + self.value)
  110. parsed = tok.items[0]
  111. if parsed.type == "OPERAND":
  112. return parsed.subtype
  113. return parsed.type
  114. @property
  115. def destinations(self):
  116. if self.type == "RANGE":
  117. tok = Tokenizer("=" + self.value)
  118. for part in tok.items:
  119. if part.subtype == "RANGE":
  120. m = SHEETRANGE_RE.match(part.value)
  121. sheetname = m.group('notquoted') or m.group('quoted')
  122. yield sheetname, m.group('cells')
  123. @property
  124. def is_reserved(self):
  125. m = RESERVED_REGEX.match(self.name)
  126. if m:
  127. return m.group("name")
  128. @property
  129. def is_external(self):
  130. return re.compile(r"^\[\d+\].*").match(self.value) is not None
  131. def __iter__(self):
  132. for key in self.__attrs__:
  133. if key == "attr_text":
  134. continue
  135. v = getattr(self, key)
  136. if v is not None:
  137. if v in RESERVED:
  138. v = "_xlnm." + v
  139. yield key, safe_string(v)
  140. class DefinedNameList(Serialisable):
  141. tagname = "definedNames"
  142. definedName = Sequence(expected_type=DefinedName)
  143. def __init__(self, definedName=()):
  144. self.definedName = definedName
  145. def _cleanup(self):
  146. """
  147. Strip invalid definitions and remove special hidden ones
  148. """
  149. valid_names = []
  150. for n in self.definedName:
  151. if n.name in ("_xlnm.Print_Titles", "_xlnm.Print_Area") and n.localSheetId is None:
  152. continue
  153. elif n.name == "_xlnm._FilterDatabase":
  154. continue
  155. valid_names.append(n)
  156. self.definedName = valid_names
  157. def _duplicate(self, defn):
  158. """
  159. Check for whether DefinedName with the same name and scope already
  160. exists
  161. """
  162. for d in self.definedName:
  163. if d.name == defn.name and d.localSheetId == defn.localSheetId:
  164. return True
  165. def append(self, defn):
  166. if not isinstance(defn, DefinedName):
  167. raise TypeError("""You can only append DefinedNames""")
  168. if self._duplicate(defn):
  169. raise ValueError("""DefinedName with the same name and scope already exists""")
  170. names = self.definedName[:]
  171. names.append(defn)
  172. self.definedName = names
  173. def __len__(self):
  174. return len(self.definedName)
  175. def __contains__(self, name):
  176. """
  177. See if a globaly defined name exists
  178. """
  179. for defn in self.definedName:
  180. if defn.name == name and defn.localSheetId is None:
  181. return True
  182. def __getitem__(self, name):
  183. """
  184. Get globally defined name
  185. """
  186. defn = self.get(name)
  187. if not defn:
  188. raise KeyError("No definition called {0}".format(name))
  189. return defn
  190. def get(self, name, scope=None):
  191. """
  192. Get the name assigned to a specicic sheet or global
  193. """
  194. for defn in self.definedName:
  195. if defn.name == name and defn.localSheetId == scope:
  196. return defn
  197. def __delitem__(self, name):
  198. """
  199. Delete a globally defined name
  200. """
  201. if not self.delete(name):
  202. raise KeyError("No globally defined name {0}".format(name))
  203. def delete(self, name, scope=None):
  204. """
  205. Delete a name assigned to a specific or global
  206. """
  207. for idx, defn in enumerate(self.definedName):
  208. if defn.name == name and defn.localSheetId == scope:
  209. del self.definedName[idx]
  210. return True
  211. def localnames(self, scope):
  212. """
  213. Provide a list of all names for a particular worksheet
  214. """
  215. return [defn.name for defn in self.definedName if defn.localSheetId == scope]