defined_name.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. # Copyright (c) 2010-2024 openpyxl
  2. from collections import defaultdict
  3. import re
  4. from openpyxl.descriptors.serialisable import Serialisable
  5. from openpyxl.descriptors import (
  6. Alias,
  7. String,
  8. Integer,
  9. Bool,
  10. Sequence,
  11. Descriptor,
  12. )
  13. from openpyxl.compat import safe_string
  14. from openpyxl.formula import Tokenizer
  15. from openpyxl.utils.cell import SHEETRANGE_RE
  16. RESERVED = frozenset(["Print_Area", "Print_Titles", "Criteria",
  17. "_FilterDatabase", "Extract", "Consolidate_Area",
  18. "Sheet_Title"])
  19. _names = "|".join(RESERVED)
  20. RESERVED_REGEX = re.compile(r"^_xlnm\.(?P<name>{0})".format(_names))
  21. class DefinedName(Serialisable):
  22. tagname = "definedName"
  23. name = String() # unique per workbook/worksheet
  24. comment = String(allow_none=True)
  25. customMenu = String(allow_none=True)
  26. description = String(allow_none=True)
  27. help = String(allow_none=True)
  28. statusBar = String(allow_none=True)
  29. localSheetId = Integer(allow_none=True)
  30. hidden = Bool(allow_none=True)
  31. function = Bool(allow_none=True)
  32. vbProcedure = Bool(allow_none=True)
  33. xlm = Bool(allow_none=True)
  34. functionGroupId = Integer(allow_none=True)
  35. shortcutKey = String(allow_none=True)
  36. publishToServer = Bool(allow_none=True)
  37. workbookParameter = Bool(allow_none=True)
  38. attr_text = Descriptor()
  39. value = Alias("attr_text")
  40. def __init__(self,
  41. name=None,
  42. comment=None,
  43. customMenu=None,
  44. description=None,
  45. help=None,
  46. statusBar=None,
  47. localSheetId=None,
  48. hidden=None,
  49. function=None,
  50. vbProcedure=None,
  51. xlm=None,
  52. functionGroupId=None,
  53. shortcutKey=None,
  54. publishToServer=None,
  55. workbookParameter=None,
  56. attr_text=None
  57. ):
  58. self.name = name
  59. self.comment = comment
  60. self.customMenu = customMenu
  61. self.description = description
  62. self.help = help
  63. self.statusBar = statusBar
  64. self.localSheetId = localSheetId
  65. self.hidden = hidden
  66. self.function = function
  67. self.vbProcedure = vbProcedure
  68. self.xlm = xlm
  69. self.functionGroupId = functionGroupId
  70. self.shortcutKey = shortcutKey
  71. self.publishToServer = publishToServer
  72. self.workbookParameter = workbookParameter
  73. self.attr_text = attr_text
  74. @property
  75. def type(self):
  76. tok = Tokenizer("=" + self.value)
  77. parsed = tok.items[0]
  78. if parsed.type == "OPERAND":
  79. return parsed.subtype
  80. return parsed.type
  81. @property
  82. def destinations(self):
  83. if self.type == "RANGE":
  84. tok = Tokenizer("=" + self.value)
  85. for part in tok.items:
  86. if part.subtype == "RANGE":
  87. m = SHEETRANGE_RE.match(part.value)
  88. sheetname = m.group('notquoted') or m.group('quoted')
  89. yield sheetname, m.group('cells')
  90. @property
  91. def is_reserved(self):
  92. m = RESERVED_REGEX.match(self.name)
  93. if m:
  94. return m.group("name")
  95. @property
  96. def is_external(self):
  97. return re.compile(r"^\[\d+\].*").match(self.value) is not None
  98. def __iter__(self):
  99. for key in self.__attrs__:
  100. if key == "attr_text":
  101. continue
  102. v = getattr(self, key)
  103. if v is not None:
  104. if v in RESERVED:
  105. v = "_xlnm." + v
  106. yield key, safe_string(v)
  107. class DefinedNameDict(dict):
  108. """
  109. Utility class for storing defined names.
  110. Allows access by name and separation of global and scoped names
  111. """
  112. def __setitem__(self, key, value):
  113. if not isinstance(value, DefinedName):
  114. raise TypeError("Value must be a an instance of DefinedName")
  115. elif value.name != key:
  116. raise ValueError("Key must be the same as the name")
  117. super().__setitem__(key, value)
  118. def add(self, value):
  119. """
  120. Add names without worrying about key and name matching.
  121. """
  122. self[value.name] = value
  123. class DefinedNameList(Serialisable):
  124. tagname = "definedNames"
  125. definedName = Sequence(expected_type=DefinedName)
  126. def __init__(self, definedName=()):
  127. self.definedName = definedName
  128. def by_sheet(self):
  129. """
  130. Break names down into sheet locals and globals
  131. """
  132. names = defaultdict(DefinedNameDict)
  133. for defn in self.definedName:
  134. if defn.localSheetId is None:
  135. if defn.name in ("_xlnm.Print_Titles", "_xlnm.Print_Area", "_xlnm._FilterDatabase"):
  136. continue
  137. names["global"][defn.name] = defn
  138. else:
  139. sheet = int(defn.localSheetId)
  140. names[sheet][defn.name] = defn
  141. return names
  142. def _duplicate(self, defn):
  143. """
  144. Check for whether DefinedName with the same name and scope already
  145. exists
  146. """
  147. for d in self.definedName:
  148. if d.name == defn.name and d.localSheetId == defn.localSheetId:
  149. return True
  150. def __len__(self):
  151. return len(self.definedName)