aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/openpyxl/workbook
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/openpyxl/workbook')
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/workbook/__init__.py4
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/workbook/_writer.py197
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/workbook/child.py166
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/workbook/defined_name.py189
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/workbook/external_link/__init__.py3
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/workbook/external_link/external.py190
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/workbook/external_reference.py18
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/workbook/function_group.py36
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/workbook/properties.py151
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/workbook/protection.py163
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/workbook/smart_tags.py56
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/workbook/views.py155
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/workbook/web.py98
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/workbook/workbook.py438
14 files changed, 1864 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/__init__.py
new file mode 100644
index 00000000..8ae4d80d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2010-2024 openpyxl
+
+
+from .workbook import Workbook
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/_writer.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/_writer.py
new file mode 100644
index 00000000..1aa6aacf
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/_writer.py
@@ -0,0 +1,197 @@
+# Copyright (c) 2010-2024 openpyxl
+
+"""Write the workbook global settings to the archive."""
+
+from openpyxl.utils import quote_sheetname
+from openpyxl.xml.constants import (
+ ARC_APP,
+ ARC_CORE,
+ ARC_CUSTOM,
+ ARC_WORKBOOK,
+ PKG_REL_NS,
+ CUSTOMUI_NS,
+ ARC_ROOT_RELS,
+)
+from openpyxl.xml.functions import tostring, fromstring
+
+from openpyxl.packaging.relationship import Relationship, RelationshipList
+from openpyxl.workbook.defined_name import (
+ DefinedName,
+ DefinedNameList,
+)
+from openpyxl.workbook.external_reference import ExternalReference
+from openpyxl.packaging.workbook import ChildSheet, WorkbookPackage, PivotCache
+from openpyxl.workbook.properties import WorkbookProperties
+from openpyxl.utils.datetime import CALENDAR_MAC_1904
+
+
+def get_active_sheet(wb):
+ """
+ Return the index of the active sheet.
+ If the sheet set to active is hidden return the next visible sheet or None
+ """
+ visible_sheets = [idx for idx, sheet in enumerate(wb._sheets) if sheet.sheet_state == "visible"]
+ if not visible_sheets:
+ raise IndexError("At least one sheet must be visible")
+
+ idx = wb._active_sheet_index
+ sheet = wb.active
+ if sheet and sheet.sheet_state == "visible":
+ return idx
+
+ for idx in visible_sheets[idx:]:
+ wb.active = idx
+ return idx
+
+ return None
+
+
+class WorkbookWriter:
+
+ def __init__(self, wb):
+ self.wb = wb
+ self.rels = RelationshipList()
+ self.package = WorkbookPackage()
+ self.package.workbookProtection = wb.security
+ self.package.calcPr = wb.calculation
+
+
+ def write_properties(self):
+
+ props = WorkbookProperties() # needs a mapping to the workbook for preservation
+ if self.wb.code_name is not None:
+ props.codeName = self.wb.code_name
+ if self.wb.excel_base_date == CALENDAR_MAC_1904:
+ props.date1904 = True
+ self.package.workbookPr = props
+
+
+ def write_worksheets(self):
+ for idx, sheet in enumerate(self.wb._sheets, 1):
+ sheet_node = ChildSheet(name=sheet.title, sheetId=idx, id="rId{0}".format(idx))
+ rel = Relationship(type=sheet._rel_type, Target=sheet.path)
+ self.rels.append(rel)
+
+ if not sheet.sheet_state == 'visible':
+ if len(self.wb._sheets) == 1:
+ raise ValueError("The only worksheet of a workbook cannot be hidden")
+ sheet_node.state = sheet.sheet_state
+ self.package.sheets.append(sheet_node)
+
+
+ def write_refs(self):
+ for link in self.wb._external_links:
+ # need to match a counter with a workbook's relations
+ rId = len(self.wb.rels) + 1
+ rel = Relationship(type=link._rel_type, Target=link.path)
+ self.rels.append(rel)
+ ext = ExternalReference(id=rel.id)
+ self.package.externalReferences.append(ext)
+
+
+ def write_names(self):
+ defined_names = list(self.wb.defined_names.values())
+
+ for idx, sheet in enumerate(self.wb.worksheets):
+ quoted = quote_sheetname(sheet.title)
+
+ # local names
+ if sheet.defined_names:
+ names = sheet.defined_names.values()
+ for n in names:
+ n.localSheetId = idx
+ defined_names.extend(names)
+
+ if sheet.auto_filter:
+ name = DefinedName(name='_FilterDatabase', localSheetId=idx, hidden=True)
+ name.value = f"{quoted}!{sheet.auto_filter}"
+ defined_names.append(name)
+
+ if sheet.print_titles:
+ name = DefinedName(name="Print_Titles", localSheetId=idx)
+ name.value = sheet.print_titles
+ defined_names.append(name)
+
+ if sheet.print_area:
+ name = DefinedName(name="Print_Area", localSheetId=idx)
+ name.value = sheet.print_area
+ defined_names.append(name)
+
+ self.package.definedNames = DefinedNameList(definedName=defined_names)
+
+
+ def write_pivots(self):
+ pivot_caches = set()
+ for pivot in self.wb._pivots:
+ if pivot.cache not in pivot_caches:
+ pivot_caches.add(pivot.cache)
+ c = PivotCache(cacheId=pivot.cacheId)
+ self.package.pivotCaches.append(c)
+ rel = Relationship(Type=pivot.cache.rel_type, Target=pivot.cache.path)
+ self.rels.append(rel)
+ c.id = rel.id
+ #self.wb._pivots = [] # reset
+
+
+ def write_views(self):
+ active = get_active_sheet(self.wb)
+ if self.wb.views:
+ self.wb.views[0].activeTab = active
+ self.package.bookViews = self.wb.views
+
+
+ def write(self):
+ """Write the core workbook xml."""
+
+ self.write_properties()
+ self.write_worksheets()
+ self.write_names()
+ self.write_pivots()
+ self.write_views()
+ self.write_refs()
+
+ return tostring(self.package.to_tree())
+
+
+ def write_rels(self):
+ """Write the workbook relationships xml."""
+
+ styles = Relationship(type='styles', Target='styles.xml')
+ self.rels.append(styles)
+
+ theme = Relationship(type='theme', Target='theme/theme1.xml')
+ self.rels.append(theme)
+
+ if self.wb.vba_archive:
+ vba = Relationship(type='', Target='vbaProject.bin')
+ vba.Type ='http://schemas.microsoft.com/office/2006/relationships/vbaProject'
+ self.rels.append(vba)
+
+ return tostring(self.rels.to_tree())
+
+
+ def write_root_rels(self):
+ """Write the package relationships"""
+
+ rels = RelationshipList()
+
+ rel = Relationship(type="officeDocument", Target=ARC_WORKBOOK)
+ rels.append(rel)
+ rel = Relationship(Type=f"{PKG_REL_NS}/metadata/core-properties", Target=ARC_CORE)
+ rels.append(rel)
+
+ rel = Relationship(type="extended-properties", Target=ARC_APP)
+ rels.append(rel)
+
+ if len(self.wb.custom_doc_props) >= 1:
+ rel = Relationship(type="custom-properties", Target=ARC_CUSTOM)
+ rels.append(rel)
+
+ if self.wb.vba_archive is not None:
+ # See if there was a customUI relation and reuse it
+ xml = fromstring(self.wb.vba_archive.read(ARC_ROOT_RELS))
+ root_rels = RelationshipList.from_tree(xml)
+ for rel in root_rels.find(CUSTOMUI_NS):
+ rels.append(rel)
+
+ return tostring(rels.to_tree())
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/child.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/child.py
new file mode 100644
index 00000000..19dd29fb
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/child.py
@@ -0,0 +1,166 @@
+# Copyright (c) 2010-2024 openpyxl
+
+import re
+import warnings
+
+from openpyxl.worksheet.header_footer import HeaderFooter
+
+"""
+Base class for worksheets, chartsheets, etc. that can be added to workbooks
+"""
+
+INVALID_TITLE_REGEX = re.compile(r'[\\*?:/\[\]]')
+
+
+def avoid_duplicate_name(names, value):
+ """
+ Naive check to see whether name already exists.
+ If name does exist suggest a name using an incrementer
+ Duplicates are case insensitive
+ """
+ # Check for an absolute match in which case we need to find an alternative
+ match = [n for n in names if n.lower() == value.lower()]
+ if match:
+ names = u",".join(names)
+ sheet_title_regex = re.compile(f'(?P<title>{re.escape(value)})(?P<count>\\d*),?', re.I)
+ matches = sheet_title_regex.findall(names)
+ if matches:
+ # use name, but append with the next highest integer
+ counts = [int(idx) for (t, idx) in matches if idx.isdigit()]
+ highest = 0
+ if counts:
+ highest = max(counts)
+ value = u"{0}{1}".format(value, highest + 1)
+ return value
+
+
+class _WorkbookChild:
+
+ __title = ""
+ _id = None
+ _path = "{0}"
+ _parent = None
+ _default_title = "Sheet"
+
+ def __init__(self, parent=None, title=None):
+ self._parent = parent
+ self.title = title or self._default_title
+ self.HeaderFooter = HeaderFooter()
+
+
+ def __repr__(self):
+ return '<{0} "{1}">'.format(self.__class__.__name__, self.title)
+
+
+ @property
+ def parent(self):
+ return self._parent
+
+
+ @property
+ def encoding(self):
+ return self._parent.encoding
+
+
+ @property
+ def title(self):
+ return self.__title
+
+
+ @title.setter
+ def title(self, value):
+ """
+ Set a sheet title, ensuring it is valid.
+ Limited to 31 characters, no special characters.
+ Duplicate titles will be incremented numerically
+ """
+ if not self._parent:
+ return
+
+ if not value:
+ raise ValueError("Title must have at least one character")
+
+ if hasattr(value, "decode"):
+ if not isinstance(value, str):
+ try:
+ value = value.decode("ascii")
+ except UnicodeDecodeError:
+ raise ValueError("Worksheet titles must be str")
+
+ m = INVALID_TITLE_REGEX.search(value)
+ if m:
+ msg = "Invalid character {0} found in sheet title".format(m.group(0))
+ raise ValueError(msg)
+
+ if self.title is not None and self.title != value:
+ value = avoid_duplicate_name(self.parent.sheetnames, value)
+
+ if len(value) > 31:
+ warnings.warn("Title is more than 31 characters. Some applications may not be able to read the file")
+
+ self.__title = value
+
+
+ @property
+ def oddHeader(self):
+ return self.HeaderFooter.oddHeader
+
+
+ @oddHeader.setter
+ def oddHeader(self, value):
+ self.HeaderFooter.oddHeader = value
+
+
+ @property
+ def oddFooter(self):
+ return self.HeaderFooter.oddFooter
+
+
+ @oddFooter.setter
+ def oddFooter(self, value):
+ self.HeaderFooter.oddFooter = value
+
+
+ @property
+ def evenHeader(self):
+ return self.HeaderFooter.evenHeader
+
+
+ @evenHeader.setter
+ def evenHeader(self, value):
+ self.HeaderFooter.evenHeader = value
+
+
+ @property
+ def evenFooter(self):
+ return self.HeaderFooter.evenFooter
+
+
+ @evenFooter.setter
+ def evenFooter(self, value):
+ self.HeaderFooter.evenFooter = value
+
+
+ @property
+ def firstHeader(self):
+ return self.HeaderFooter.firstHeader
+
+
+ @firstHeader.setter
+ def firstHeader(self, value):
+ self.HeaderFooter.firstHeader = value
+
+
+ @property
+ def firstFooter(self):
+ return self.HeaderFooter.firstFooter
+
+
+ @firstFooter.setter
+ def firstFooter(self, value):
+ self.HeaderFooter.firstFooter = value
+
+
+ @property
+ def path(self):
+ return self._path.format(self._id)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/defined_name.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/defined_name.py
new file mode 100644
index 00000000..15f0bd30
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/defined_name.py
@@ -0,0 +1,189 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from collections import defaultdict
+import re
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+ Alias,
+ String,
+ Integer,
+ Bool,
+ Sequence,
+ Descriptor,
+)
+from openpyxl.compat import safe_string
+from openpyxl.formula import Tokenizer
+from openpyxl.utils.cell import SHEETRANGE_RE
+
+RESERVED = frozenset(["Print_Area", "Print_Titles", "Criteria",
+ "_FilterDatabase", "Extract", "Consolidate_Area",
+ "Sheet_Title"])
+
+_names = "|".join(RESERVED)
+RESERVED_REGEX = re.compile(r"^_xlnm\.(?P<name>{0})".format(_names))
+
+
+class DefinedName(Serialisable):
+
+ tagname = "definedName"
+
+ name = String() # unique per workbook/worksheet
+ comment = String(allow_none=True)
+ customMenu = String(allow_none=True)
+ description = String(allow_none=True)
+ help = String(allow_none=True)
+ statusBar = String(allow_none=True)
+ localSheetId = Integer(allow_none=True)
+ hidden = Bool(allow_none=True)
+ function = Bool(allow_none=True)
+ vbProcedure = Bool(allow_none=True)
+ xlm = Bool(allow_none=True)
+ functionGroupId = Integer(allow_none=True)
+ shortcutKey = String(allow_none=True)
+ publishToServer = Bool(allow_none=True)
+ workbookParameter = Bool(allow_none=True)
+ attr_text = Descriptor()
+ value = Alias("attr_text")
+
+
+ def __init__(self,
+ name=None,
+ comment=None,
+ customMenu=None,
+ description=None,
+ help=None,
+ statusBar=None,
+ localSheetId=None,
+ hidden=None,
+ function=None,
+ vbProcedure=None,
+ xlm=None,
+ functionGroupId=None,
+ shortcutKey=None,
+ publishToServer=None,
+ workbookParameter=None,
+ attr_text=None
+ ):
+ self.name = name
+ self.comment = comment
+ self.customMenu = customMenu
+ self.description = description
+ self.help = help
+ self.statusBar = statusBar
+ self.localSheetId = localSheetId
+ self.hidden = hidden
+ self.function = function
+ self.vbProcedure = vbProcedure
+ self.xlm = xlm
+ self.functionGroupId = functionGroupId
+ self.shortcutKey = shortcutKey
+ self.publishToServer = publishToServer
+ self.workbookParameter = workbookParameter
+ self.attr_text = attr_text
+
+
+ @property
+ def type(self):
+ tok = Tokenizer("=" + self.value)
+ parsed = tok.items[0]
+ if parsed.type == "OPERAND":
+ return parsed.subtype
+ return parsed.type
+
+
+ @property
+ def destinations(self):
+ if self.type == "RANGE":
+ tok = Tokenizer("=" + self.value)
+ for part in tok.items:
+ if part.subtype == "RANGE":
+ m = SHEETRANGE_RE.match(part.value)
+ sheetname = m.group('notquoted') or m.group('quoted')
+ yield sheetname, m.group('cells')
+
+
+ @property
+ def is_reserved(self):
+ m = RESERVED_REGEX.match(self.name)
+ if m:
+ return m.group("name")
+
+
+ @property
+ def is_external(self):
+ return re.compile(r"^\[\d+\].*").match(self.value) is not None
+
+
+ def __iter__(self):
+ for key in self.__attrs__:
+ if key == "attr_text":
+ continue
+ v = getattr(self, key)
+ if v is not None:
+ if v in RESERVED:
+ v = "_xlnm." + v
+ yield key, safe_string(v)
+
+
+class DefinedNameDict(dict):
+
+ """
+ Utility class for storing defined names.
+ Allows access by name and separation of global and scoped names
+ """
+
+ def __setitem__(self, key, value):
+ if not isinstance(value, DefinedName):
+ raise TypeError("Value must be a an instance of DefinedName")
+ elif value.name != key:
+ raise ValueError("Key must be the same as the name")
+ super().__setitem__(key, value)
+
+
+ def add(self, value):
+ """
+ Add names without worrying about key and name matching.
+ """
+ self[value.name] = value
+
+
+class DefinedNameList(Serialisable):
+
+ tagname = "definedNames"
+
+ definedName = Sequence(expected_type=DefinedName)
+
+
+ def __init__(self, definedName=()):
+ self.definedName = definedName
+
+
+ def by_sheet(self):
+ """
+ Break names down into sheet locals and globals
+ """
+ names = defaultdict(DefinedNameDict)
+ for defn in self.definedName:
+ if defn.localSheetId is None:
+ if defn.name in ("_xlnm.Print_Titles", "_xlnm.Print_Area", "_xlnm._FilterDatabase"):
+ continue
+ names["global"][defn.name] = defn
+ else:
+ sheet = int(defn.localSheetId)
+ names[sheet][defn.name] = defn
+ return names
+
+
+ def _duplicate(self, defn):
+ """
+ Check for whether DefinedName with the same name and scope already
+ exists
+ """
+ for d in self.definedName:
+ if d.name == defn.name and d.localSheetId == defn.localSheetId:
+ return True
+
+
+ def __len__(self):
+ return len(self.definedName)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/external_link/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/external_link/__init__.py
new file mode 100644
index 00000000..c3cb6211
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/external_link/__init__.py
@@ -0,0 +1,3 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from .external import ExternalLink
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/external_link/external.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/external_link/external.py
new file mode 100644
index 00000000..7e2e5b20
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/external_link/external.py
@@ -0,0 +1,190 @@
+# Copyright (c) 2010-2024 openpyxl
+
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+ Typed,
+ String,
+ Bool,
+ Integer,
+ NoneSet,
+ Sequence,
+)
+from openpyxl.descriptors.excel import Relation
+from openpyxl.descriptors.nested import NestedText
+from openpyxl.descriptors.sequence import NestedSequence, ValueSequence
+
+from openpyxl.packaging.relationship import (
+ Relationship,
+ get_rels_path,
+ get_dependents
+ )
+from openpyxl.xml.constants import SHEET_MAIN_NS
+from openpyxl.xml.functions import fromstring
+
+
+"""Manage links to external Workbooks"""
+
+
+class ExternalCell(Serialisable):
+
+ r = String()
+ t = NoneSet(values=(['b', 'd', 'n', 'e', 's', 'str', 'inlineStr']))
+ vm = Integer(allow_none=True)
+ v = NestedText(allow_none=True, expected_type=str)
+
+ def __init__(self,
+ r=None,
+ t=None,
+ vm=None,
+ v=None,
+ ):
+ self.r = r
+ self.t = t
+ self.vm = vm
+ self.v = v
+
+
+class ExternalRow(Serialisable):
+
+ r = Integer()
+ cell = Sequence(expected_type=ExternalCell)
+
+ __elements__ = ('cell',)
+
+ def __init__(self,
+ r=(),
+ cell=None,
+ ):
+ self.r = r
+ self.cell = cell
+
+
+class ExternalSheetData(Serialisable):
+
+ sheetId = Integer()
+ refreshError = Bool(allow_none=True)
+ row = Sequence(expected_type=ExternalRow)
+
+ __elements__ = ('row',)
+
+ def __init__(self,
+ sheetId=None,
+ refreshError=None,
+ row=(),
+ ):
+ self.sheetId = sheetId
+ self.refreshError = refreshError
+ self.row = row
+
+
+class ExternalSheetDataSet(Serialisable):
+
+ sheetData = Sequence(expected_type=ExternalSheetData, )
+
+ __elements__ = ('sheetData',)
+
+ def __init__(self,
+ sheetData=None,
+ ):
+ self.sheetData = sheetData
+
+
+class ExternalSheetNames(Serialisable):
+
+ sheetName = ValueSequence(expected_type=str)
+
+ __elements__ = ('sheetName',)
+
+ def __init__(self,
+ sheetName=(),
+ ):
+ self.sheetName = sheetName
+
+
+class ExternalDefinedName(Serialisable):
+
+ tagname = "definedName"
+
+ name = String()
+ refersTo = String(allow_none=True)
+ sheetId = Integer(allow_none=True)
+
+ def __init__(self,
+ name=None,
+ refersTo=None,
+ sheetId=None,
+ ):
+ self.name = name
+ self.refersTo = refersTo
+ self.sheetId = sheetId
+
+
+class ExternalBook(Serialisable):
+
+ tagname = "externalBook"
+
+ sheetNames = Typed(expected_type=ExternalSheetNames, allow_none=True)
+ definedNames = NestedSequence(expected_type=ExternalDefinedName)
+ sheetDataSet = Typed(expected_type=ExternalSheetDataSet, allow_none=True)
+ id = Relation()
+
+ __elements__ = ('sheetNames', 'definedNames', 'sheetDataSet')
+
+ def __init__(self,
+ sheetNames=None,
+ definedNames=(),
+ sheetDataSet=None,
+ id=None,
+ ):
+ self.sheetNames = sheetNames
+ self.definedNames = definedNames
+ self.sheetDataSet = sheetDataSet
+ self.id = id
+
+
+class ExternalLink(Serialisable):
+
+ tagname = "externalLink"
+
+ _id = None
+ _path = "/xl/externalLinks/externalLink{0}.xml"
+ _rel_type = "externalLink"
+ mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml"
+
+ externalBook = Typed(expected_type=ExternalBook, allow_none=True)
+ file_link = Typed(expected_type=Relationship, allow_none=True) # link to external file
+
+ __elements__ = ('externalBook', )
+
+ def __init__(self,
+ externalBook=None,
+ ddeLink=None,
+ oleLink=None,
+ extLst=None,
+ ):
+ self.externalBook = externalBook
+ # ignore other items for the moment.
+
+
+ def to_tree(self):
+ node = super().to_tree()
+ node.set("xmlns", SHEET_MAIN_NS)
+ return node
+
+
+ @property
+ def path(self):
+ return self._path.format(self._id)
+
+
+def read_external_link(archive, book_path):
+ src = archive.read(book_path)
+ node = fromstring(src)
+ book = ExternalLink.from_tree(node)
+
+ link_path = get_rels_path(book_path)
+ deps = get_dependents(archive, link_path)
+ book.file_link = deps[0]
+
+ return book
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/external_reference.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/external_reference.py
new file mode 100644
index 00000000..f05802da
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/external_reference.py
@@ -0,0 +1,18 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+ Sequence
+)
+from openpyxl.descriptors.excel import (
+ Relation,
+)
+
+class ExternalReference(Serialisable):
+
+ tagname = "externalReference"
+
+ id = Relation()
+
+ def __init__(self, id):
+ self.id = id
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/function_group.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/function_group.py
new file mode 100644
index 00000000..5d7e8557
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/function_group.py
@@ -0,0 +1,36 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+ Sequence,
+ String,
+ Integer,
+)
+
+class FunctionGroup(Serialisable):
+
+ tagname = "functionGroup"
+
+ name = String()
+
+ def __init__(self,
+ name=None,
+ ):
+ self.name = name
+
+
+class FunctionGroupList(Serialisable):
+
+ tagname = "functionGroups"
+
+ builtInGroupCount = Integer(allow_none=True)
+ functionGroup = Sequence(expected_type=FunctionGroup, allow_none=True)
+
+ __elements__ = ('functionGroup',)
+
+ def __init__(self,
+ builtInGroupCount=16,
+ functionGroup=(),
+ ):
+ self.builtInGroupCount = builtInGroupCount
+ self.functionGroup = functionGroup
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/properties.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/properties.py
new file mode 100644
index 00000000..bdc9d614
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/properties.py
@@ -0,0 +1,151 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+ String,
+ Float,
+ Integer,
+ Bool,
+ NoneSet,
+ Set,
+)
+
+from openpyxl.descriptors.excel import Guid
+
+
+class WorkbookProperties(Serialisable):
+
+ tagname = "workbookPr"
+
+ date1904 = Bool(allow_none=True)
+ dateCompatibility = Bool(allow_none=True)
+ showObjects = NoneSet(values=(['all', 'placeholders']))
+ showBorderUnselectedTables = Bool(allow_none=True)
+ filterPrivacy = Bool(allow_none=True)
+ promptedSolutions = Bool(allow_none=True)
+ showInkAnnotation = Bool(allow_none=True)
+ backupFile = Bool(allow_none=True)
+ saveExternalLinkValues = Bool(allow_none=True)
+ updateLinks = NoneSet(values=(['userSet', 'never', 'always']))
+ codeName = String(allow_none=True)
+ hidePivotFieldList = Bool(allow_none=True)
+ showPivotChartFilter = Bool(allow_none=True)
+ allowRefreshQuery = Bool(allow_none=True)
+ publishItems = Bool(allow_none=True)
+ checkCompatibility = Bool(allow_none=True)
+ autoCompressPictures = Bool(allow_none=True)
+ refreshAllConnections = Bool(allow_none=True)
+ defaultThemeVersion = Integer(allow_none=True)
+
+ def __init__(self,
+ date1904=None,
+ dateCompatibility=None,
+ showObjects=None,
+ showBorderUnselectedTables=None,
+ filterPrivacy=None,
+ promptedSolutions=None,
+ showInkAnnotation=None,
+ backupFile=None,
+ saveExternalLinkValues=None,
+ updateLinks=None,
+ codeName=None,
+ hidePivotFieldList=None,
+ showPivotChartFilter=None,
+ allowRefreshQuery=None,
+ publishItems=None,
+ checkCompatibility=None,
+ autoCompressPictures=None,
+ refreshAllConnections=None,
+ defaultThemeVersion=None,
+ ):
+ self.date1904 = date1904
+ self.dateCompatibility = dateCompatibility
+ self.showObjects = showObjects
+ self.showBorderUnselectedTables = showBorderUnselectedTables
+ self.filterPrivacy = filterPrivacy
+ self.promptedSolutions = promptedSolutions
+ self.showInkAnnotation = showInkAnnotation
+ self.backupFile = backupFile
+ self.saveExternalLinkValues = saveExternalLinkValues
+ self.updateLinks = updateLinks
+ self.codeName = codeName
+ self.hidePivotFieldList = hidePivotFieldList
+ self.showPivotChartFilter = showPivotChartFilter
+ self.allowRefreshQuery = allowRefreshQuery
+ self.publishItems = publishItems
+ self.checkCompatibility = checkCompatibility
+ self.autoCompressPictures = autoCompressPictures
+ self.refreshAllConnections = refreshAllConnections
+ self.defaultThemeVersion = defaultThemeVersion
+
+
+class CalcProperties(Serialisable):
+
+ tagname = "calcPr"
+
+ calcId = Integer()
+ calcMode = NoneSet(values=(['manual', 'auto', 'autoNoTable']))
+ fullCalcOnLoad = Bool(allow_none=True)
+ refMode = NoneSet(values=(['A1', 'R1C1']))
+ iterate = Bool(allow_none=True)
+ iterateCount = Integer(allow_none=True)
+ iterateDelta = Float(allow_none=True)
+ fullPrecision = Bool(allow_none=True)
+ calcCompleted = Bool(allow_none=True)
+ calcOnSave = Bool(allow_none=True)
+ concurrentCalc = Bool(allow_none=True)
+ concurrentManualCount = Integer(allow_none=True)
+ forceFullCalc = Bool(allow_none=True)
+
+ def __init__(self,
+ calcId=124519,
+ calcMode=None,
+ fullCalcOnLoad=True,
+ refMode=None,
+ iterate=None,
+ iterateCount=None,
+ iterateDelta=None,
+ fullPrecision=None,
+ calcCompleted=None,
+ calcOnSave=None,
+ concurrentCalc=None,
+ concurrentManualCount=None,
+ forceFullCalc=None,
+ ):
+ self.calcId = calcId
+ self.calcMode = calcMode
+ self.fullCalcOnLoad = fullCalcOnLoad
+ self.refMode = refMode
+ self.iterate = iterate
+ self.iterateCount = iterateCount
+ self.iterateDelta = iterateDelta
+ self.fullPrecision = fullPrecision
+ self.calcCompleted = calcCompleted
+ self.calcOnSave = calcOnSave
+ self.concurrentCalc = concurrentCalc
+ self.concurrentManualCount = concurrentManualCount
+ self.forceFullCalc = forceFullCalc
+
+
+class FileVersion(Serialisable):
+
+ tagname = "fileVersion"
+
+ appName = String(allow_none=True)
+ lastEdited = String(allow_none=True)
+ lowestEdited = String(allow_none=True)
+ rupBuild = String(allow_none=True)
+ codeName = Guid(allow_none=True)
+
+ def __init__(self,
+ appName=None,
+ lastEdited=None,
+ lowestEdited=None,
+ rupBuild=None,
+ codeName=None,
+ ):
+ self.appName = appName
+ self.lastEdited = lastEdited
+ self.lowestEdited = lowestEdited
+ self.rupBuild = rupBuild
+ self.codeName = codeName
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/protection.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/protection.py
new file mode 100644
index 00000000..d77d64bb
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/protection.py
@@ -0,0 +1,163 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+ Alias,
+ Typed,
+ String,
+ Float,
+ Integer,
+ Bool,
+ NoneSet,
+ Set,
+)
+from openpyxl.descriptors.excel import (
+ ExtensionList,
+ HexBinary,
+ Guid,
+ Relation,
+ Base64Binary,
+)
+from openpyxl.utils.protection import hash_password
+
+
+class WorkbookProtection(Serialisable):
+
+ _workbook_password, _revisions_password = None, None
+
+ tagname = "workbookPr"
+
+ workbook_password = Alias("workbookPassword")
+ workbookPasswordCharacterSet = String(allow_none=True)
+ revision_password = Alias("revisionsPassword")
+ revisionsPasswordCharacterSet = String(allow_none=True)
+ lockStructure = Bool(allow_none=True)
+ lock_structure = Alias("lockStructure")
+ lockWindows = Bool(allow_none=True)
+ lock_windows = Alias("lockWindows")
+ lockRevision = Bool(allow_none=True)
+ lock_revision = Alias("lockRevision")
+ revisionsAlgorithmName = String(allow_none=True)
+ revisionsHashValue = Base64Binary(allow_none=True)
+ revisionsSaltValue = Base64Binary(allow_none=True)
+ revisionsSpinCount = Integer(allow_none=True)
+ workbookAlgorithmName = String(allow_none=True)
+ workbookHashValue = Base64Binary(allow_none=True)
+ workbookSaltValue = Base64Binary(allow_none=True)
+ workbookSpinCount = Integer(allow_none=True)
+
+ __attrs__ = ('workbookPassword', 'workbookPasswordCharacterSet', 'revisionsPassword',
+ 'revisionsPasswordCharacterSet', 'lockStructure', 'lockWindows', 'lockRevision',
+ 'revisionsAlgorithmName', 'revisionsHashValue', 'revisionsSaltValue',
+ 'revisionsSpinCount', 'workbookAlgorithmName', 'workbookHashValue',
+ 'workbookSaltValue', 'workbookSpinCount')
+
+ def __init__(self,
+ workbookPassword=None,
+ workbookPasswordCharacterSet=None,
+ revisionsPassword=None,
+ revisionsPasswordCharacterSet=None,
+ lockStructure=None,
+ lockWindows=None,
+ lockRevision=None,
+ revisionsAlgorithmName=None,
+ revisionsHashValue=None,
+ revisionsSaltValue=None,
+ revisionsSpinCount=None,
+ workbookAlgorithmName=None,
+ workbookHashValue=None,
+ workbookSaltValue=None,
+ workbookSpinCount=None,
+ ):
+ if workbookPassword is not None:
+ self.workbookPassword = workbookPassword
+ self.workbookPasswordCharacterSet = workbookPasswordCharacterSet
+ if revisionsPassword is not None:
+ self.revisionsPassword = revisionsPassword
+ self.revisionsPasswordCharacterSet = revisionsPasswordCharacterSet
+ self.lockStructure = lockStructure
+ self.lockWindows = lockWindows
+ self.lockRevision = lockRevision
+ self.revisionsAlgorithmName = revisionsAlgorithmName
+ self.revisionsHashValue = revisionsHashValue
+ self.revisionsSaltValue = revisionsSaltValue
+ self.revisionsSpinCount = revisionsSpinCount
+ self.workbookAlgorithmName = workbookAlgorithmName
+ self.workbookHashValue = workbookHashValue
+ self.workbookSaltValue = workbookSaltValue
+ self.workbookSpinCount = workbookSpinCount
+
+ def set_workbook_password(self, value='', already_hashed=False):
+ """Set a password on this workbook."""
+ if not already_hashed:
+ value = hash_password(value)
+ self._workbook_password = value
+
+ @property
+ def workbookPassword(self):
+ """Return the workbook password value, regardless of hash."""
+ return self._workbook_password
+
+ @workbookPassword.setter
+ def workbookPassword(self, value):
+ """Set a workbook password directly, forcing a hash step."""
+ self.set_workbook_password(value)
+
+ def set_revisions_password(self, value='', already_hashed=False):
+ """Set a revision password on this workbook."""
+ if not already_hashed:
+ value = hash_password(value)
+ self._revisions_password = value
+
+ @property
+ def revisionsPassword(self):
+ """Return the revisions password value, regardless of hash."""
+ return self._revisions_password
+
+ @revisionsPassword.setter
+ def revisionsPassword(self, value):
+ """Set a revisions password directly, forcing a hash step."""
+ self.set_revisions_password(value)
+
+ @classmethod
+ def from_tree(cls, node):
+ """Don't hash passwords when deserialising from XML"""
+ self = super().from_tree(node)
+ if self.workbookPassword:
+ self.set_workbook_password(node.get('workbookPassword'), already_hashed=True)
+ if self.revisionsPassword:
+ self.set_revisions_password(node.get('revisionsPassword'), already_hashed=True)
+ return self
+
+# Backwards compatibility
+DocumentSecurity = WorkbookProtection
+
+
+class FileSharing(Serialisable):
+
+ tagname = "fileSharing"
+
+ readOnlyRecommended = Bool(allow_none=True)
+ userName = String(allow_none=True)
+ reservationPassword = HexBinary(allow_none=True)
+ algorithmName = String(allow_none=True)
+ hashValue = Base64Binary(allow_none=True)
+ saltValue = Base64Binary(allow_none=True)
+ spinCount = Integer(allow_none=True)
+
+ def __init__(self,
+ readOnlyRecommended=None,
+ userName=None,
+ reservationPassword=None,
+ algorithmName=None,
+ hashValue=None,
+ saltValue=None,
+ spinCount=None,
+ ):
+ self.readOnlyRecommended = readOnlyRecommended
+ self.userName = userName
+ self.reservationPassword = reservationPassword
+ self.algorithmName = algorithmName
+ self.hashValue = hashValue
+ self.saltValue = saltValue
+ self.spinCount = spinCount
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/smart_tags.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/smart_tags.py
new file mode 100644
index 00000000..873e98bf
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/smart_tags.py
@@ -0,0 +1,56 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+ Sequence,
+ String,
+ Bool,
+ NoneSet,
+
+)
+
+class SmartTag(Serialisable):
+
+ tagname = "smartTagType"
+
+ namespaceUri = String(allow_none=True)
+ name = String(allow_none=True)
+ url = String(allow_none=True)
+
+ def __init__(self,
+ namespaceUri=None,
+ name=None,
+ url=None,
+ ):
+ self.namespaceUri = namespaceUri
+ self.name = name
+ self.url = url
+
+
+class SmartTagList(Serialisable):
+
+ tagname = "smartTagTypes"
+
+ smartTagType = Sequence(expected_type=SmartTag, allow_none=True)
+
+ __elements__ = ('smartTagType',)
+
+ def __init__(self,
+ smartTagType=(),
+ ):
+ self.smartTagType = smartTagType
+
+
+class SmartTagProperties(Serialisable):
+
+ tagname = "smartTagPr"
+
+ embed = Bool(allow_none=True)
+ show = NoneSet(values=(['all', 'noIndicator']))
+
+ def __init__(self,
+ embed=None,
+ show=None,
+ ):
+ self.embed = embed
+ self.show = show
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/views.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/views.py
new file mode 100644
index 00000000..bcbf0267
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/views.py
@@ -0,0 +1,155 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+ Typed,
+ Sequence,
+ String,
+ Float,
+ Integer,
+ Bool,
+ NoneSet,
+ Set,
+)
+from openpyxl.descriptors.excel import (
+ ExtensionList,
+ Guid,
+)
+
+
+class BookView(Serialisable):
+
+ tagname = "workbookView"
+
+ visibility = NoneSet(values=(['visible', 'hidden', 'veryHidden']))
+ minimized = Bool(allow_none=True)
+ showHorizontalScroll = Bool(allow_none=True)
+ showVerticalScroll = Bool(allow_none=True)
+ showSheetTabs = Bool(allow_none=True)
+ xWindow = Integer(allow_none=True)
+ yWindow = Integer(allow_none=True)
+ windowWidth = Integer(allow_none=True)
+ windowHeight = Integer(allow_none=True)
+ tabRatio = Integer(allow_none=True)
+ firstSheet = Integer(allow_none=True)
+ activeTab = Integer(allow_none=True)
+ autoFilterDateGrouping = Bool(allow_none=True)
+ extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+ __elements__ = ()
+
+ def __init__(self,
+ visibility="visible",
+ minimized=False,
+ showHorizontalScroll=True,
+ showVerticalScroll=True,
+ showSheetTabs=True,
+ xWindow=None,
+ yWindow=None,
+ windowWidth=None,
+ windowHeight=None,
+ tabRatio=600,
+ firstSheet=0,
+ activeTab=0,
+ autoFilterDateGrouping=True,
+ extLst=None,
+ ):
+ self.visibility = visibility
+ self.minimized = minimized
+ self.showHorizontalScroll = showHorizontalScroll
+ self.showVerticalScroll = showVerticalScroll
+ self.showSheetTabs = showSheetTabs
+ self.xWindow = xWindow
+ self.yWindow = yWindow
+ self.windowWidth = windowWidth
+ self.windowHeight = windowHeight
+ self.tabRatio = tabRatio
+ self.firstSheet = firstSheet
+ self.activeTab = activeTab
+ self.autoFilterDateGrouping = autoFilterDateGrouping
+
+
+class CustomWorkbookView(Serialisable):
+
+ tagname = "customWorkbookView"
+
+ name = String()
+ guid = Guid()
+ autoUpdate = Bool(allow_none=True)
+ mergeInterval = Integer(allow_none=True)
+ changesSavedWin = Bool(allow_none=True)
+ onlySync = Bool(allow_none=True)
+ personalView = Bool(allow_none=True)
+ includePrintSettings = Bool(allow_none=True)
+ includeHiddenRowCol = Bool(allow_none=True)
+ maximized = Bool(allow_none=True)
+ minimized = Bool(allow_none=True)
+ showHorizontalScroll = Bool(allow_none=True)
+ showVerticalScroll = Bool(allow_none=True)
+ showSheetTabs = Bool(allow_none=True)
+ xWindow = Integer(allow_none=True)
+ yWindow = Integer(allow_none=True)
+ windowWidth = Integer()
+ windowHeight = Integer()
+ tabRatio = Integer(allow_none=True)
+ activeSheetId = Integer()
+ showFormulaBar = Bool(allow_none=True)
+ showStatusbar = Bool(allow_none=True)
+ showComments = NoneSet(values=(['commNone', 'commIndicator',
+ 'commIndAndComment']))
+ showObjects = NoneSet(values=(['all', 'placeholders']))
+ extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+ __elements__ = ()
+
+ def __init__(self,
+ name=None,
+ guid=None,
+ autoUpdate=None,
+ mergeInterval=None,
+ changesSavedWin=None,
+ onlySync=None,
+ personalView=None,
+ includePrintSettings=None,
+ includeHiddenRowCol=None,
+ maximized=None,
+ minimized=None,
+ showHorizontalScroll=None,
+ showVerticalScroll=None,
+ showSheetTabs=None,
+ xWindow=None,
+ yWindow=None,
+ windowWidth=None,
+ windowHeight=None,
+ tabRatio=None,
+ activeSheetId=None,
+ showFormulaBar=None,
+ showStatusbar=None,
+ showComments="commIndicator",
+ showObjects="all",
+ extLst=None,
+ ):
+ self.name = name
+ self.guid = guid
+ self.autoUpdate = autoUpdate
+ self.mergeInterval = mergeInterval
+ self.changesSavedWin = changesSavedWin
+ self.onlySync = onlySync
+ self.personalView = personalView
+ self.includePrintSettings = includePrintSettings
+ self.includeHiddenRowCol = includeHiddenRowCol
+ self.maximized = maximized
+ self.minimized = minimized
+ self.showHorizontalScroll = showHorizontalScroll
+ self.showVerticalScroll = showVerticalScroll
+ self.showSheetTabs = showSheetTabs
+ self.xWindow = xWindow
+ self.yWindow = yWindow
+ self.windowWidth = windowWidth
+ self.windowHeight = windowHeight
+ self.tabRatio = tabRatio
+ self.activeSheetId = activeSheetId
+ self.showFormulaBar = showFormulaBar
+ self.showStatusbar = showStatusbar
+ self.showComments = showComments
+ self.showObjects = showObjects
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/web.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/web.py
new file mode 100644
index 00000000..e30e761a
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/web.py
@@ -0,0 +1,98 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+ Typed,
+ Sequence,
+ String,
+ Float,
+ Integer,
+ Bool,
+ NoneSet,
+)
+
+
+class WebPublishObject(Serialisable):
+
+ tagname = "webPublishingObject"
+
+ id = Integer()
+ divId = String()
+ sourceObject = String(allow_none=True)
+ destinationFile = String()
+ title = String(allow_none=True)
+ autoRepublish = Bool(allow_none=True)
+
+ def __init__(self,
+ id=None,
+ divId=None,
+ sourceObject=None,
+ destinationFile=None,
+ title=None,
+ autoRepublish=None,
+ ):
+ self.id = id
+ self.divId = divId
+ self.sourceObject = sourceObject
+ self.destinationFile = destinationFile
+ self.title = title
+ self.autoRepublish = autoRepublish
+
+
+class WebPublishObjectList(Serialisable):
+
+ tagname ="webPublishingObjects"
+
+ count = Integer(allow_none=True)
+ webPublishObject = Sequence(expected_type=WebPublishObject)
+
+ __elements__ = ('webPublishObject',)
+
+ def __init__(self,
+ count=None,
+ webPublishObject=(),
+ ):
+ self.webPublishObject = webPublishObject
+
+
+ @property
+ def count(self):
+ return len(self.webPublishObject)
+
+
+class WebPublishing(Serialisable):
+
+ tagname = "webPublishing"
+
+ css = Bool(allow_none=True)
+ thicket = Bool(allow_none=True)
+ longFileNames = Bool(allow_none=True)
+ vml = Bool(allow_none=True)
+ allowPng = Bool(allow_none=True)
+ targetScreenSize = NoneSet(values=(['544x376', '640x480', '720x512', '800x600',
+ '1024x768', '1152x882', '1152x900', '1280x1024', '1600x1200',
+ '1800x1440', '1920x1200']))
+ dpi = Integer(allow_none=True)
+ codePage = Integer(allow_none=True)
+ characterSet = String(allow_none=True)
+
+ def __init__(self,
+ css=None,
+ thicket=None,
+ longFileNames=None,
+ vml=None,
+ allowPng=None,
+ targetScreenSize='800x600',
+ dpi=None,
+ codePage=None,
+ characterSet=None,
+ ):
+ self.css = css
+ self.thicket = thicket
+ self.longFileNames = longFileNames
+ self.vml = vml
+ self.allowPng = allowPng
+ self.targetScreenSize = targetScreenSize
+ self.dpi = dpi
+ self.codePage = codePage
+ self.characterSet = characterSet
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/workbook/workbook.py b/.venv/lib/python3.12/site-packages/openpyxl/workbook/workbook.py
new file mode 100644
index 00000000..b83ac442
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/workbook/workbook.py
@@ -0,0 +1,438 @@
+# Copyright (c) 2010-2024 openpyxl
+
+"""Workbook is the top-level container for all document information."""
+from copy import copy
+
+from openpyxl.compat import deprecated
+from openpyxl.worksheet.worksheet import Worksheet
+from openpyxl.worksheet._read_only import ReadOnlyWorksheet
+from openpyxl.worksheet._write_only import WriteOnlyWorksheet
+from openpyxl.worksheet.copier import WorksheetCopy
+
+from openpyxl.utils import quote_sheetname
+from openpyxl.utils.indexed_list import IndexedList
+from openpyxl.utils.datetime import WINDOWS_EPOCH, MAC_EPOCH
+from openpyxl.utils.exceptions import ReadOnlyWorkbookException
+
+from openpyxl.writer.excel import save_workbook
+
+from openpyxl.styles.cell_style import StyleArray
+from openpyxl.styles.named_styles import NamedStyle
+from openpyxl.styles.differential import DifferentialStyleList
+from openpyxl.styles.alignment import Alignment
+from openpyxl.styles.borders import DEFAULT_BORDER
+from openpyxl.styles.fills import DEFAULT_EMPTY_FILL, DEFAULT_GRAY_FILL
+from openpyxl.styles.fonts import DEFAULT_FONT
+from openpyxl.styles.protection import Protection
+from openpyxl.styles.colors import COLOR_INDEX
+from openpyxl.styles.named_styles import NamedStyleList
+from openpyxl.styles.table import TableStyleList
+
+from openpyxl.chartsheet import Chartsheet
+from .defined_name import DefinedName, DefinedNameDict
+from openpyxl.packaging.core import DocumentProperties
+from openpyxl.packaging.custom import CustomPropertyList
+from openpyxl.packaging.relationship import RelationshipList
+from .child import _WorkbookChild
+from .protection import DocumentSecurity
+from .properties import CalcProperties
+from .views import BookView
+
+
+from openpyxl.xml.constants import (
+ XLSM,
+ XLSX,
+ XLTM,
+ XLTX
+)
+
+INTEGER_TYPES = (int,)
+
+class Workbook:
+ """Workbook is the container for all other parts of the document."""
+
+ _read_only = False
+ _data_only = False
+ template = False
+ path = "/xl/workbook.xml"
+
+ def __init__(self,
+ write_only=False,
+ iso_dates=False,
+ ):
+ self._sheets = []
+ self._pivots = []
+ self._active_sheet_index = 0
+ self.defined_names = DefinedNameDict()
+ self._external_links = []
+ self.properties = DocumentProperties()
+ self.custom_doc_props = CustomPropertyList()
+ self.security = DocumentSecurity()
+ self.__write_only = write_only
+ self.shared_strings = IndexedList()
+
+ self._setup_styles()
+
+ self.loaded_theme = None
+ self.vba_archive = None
+ self.is_template = False
+ self.code_name = None
+ self.epoch = WINDOWS_EPOCH
+ self.encoding = "utf-8"
+ self.iso_dates = iso_dates
+
+ if not self.write_only:
+ self._sheets.append(Worksheet(self))
+
+ self.rels = RelationshipList()
+ self.calculation = CalcProperties()
+ self.views = [BookView()]
+
+
+ def _setup_styles(self):
+ """Bootstrap styles"""
+
+ self._fonts = IndexedList()
+ self._fonts.add(DEFAULT_FONT)
+
+ self._alignments = IndexedList([Alignment()])
+
+ self._borders = IndexedList()
+ self._borders.add(DEFAULT_BORDER)
+
+ self._fills = IndexedList()
+ self._fills.add(DEFAULT_EMPTY_FILL)
+ self._fills.add(DEFAULT_GRAY_FILL)
+
+ self._number_formats = IndexedList()
+ self._date_formats = {}
+ self._timedelta_formats = {}
+
+ self._protections = IndexedList([Protection()])
+
+ self._colors = COLOR_INDEX
+ self._cell_styles = IndexedList([StyleArray()])
+ self._named_styles = NamedStyleList()
+ self.add_named_style(NamedStyle(font=copy(DEFAULT_FONT), border=copy(DEFAULT_BORDER), builtinId=0))
+ self._table_styles = TableStyleList()
+ self._differential_styles = DifferentialStyleList()
+
+
+ @property
+ def epoch(self):
+ if self._epoch == WINDOWS_EPOCH:
+ return WINDOWS_EPOCH
+ return MAC_EPOCH
+
+
+ @epoch.setter
+ def epoch(self, value):
+ if value not in (WINDOWS_EPOCH, MAC_EPOCH):
+ raise ValueError("The epoch must be either 1900 or 1904")
+ self._epoch = value
+
+
+ @property
+ def read_only(self):
+ return self._read_only
+
+ @property
+ def data_only(self):
+ return self._data_only
+
+ @property
+ def write_only(self):
+ return self.__write_only
+
+
+ @property
+ def excel_base_date(self):
+ return self.epoch
+
+ @property
+ def active(self):
+ """Get the currently active sheet or None
+
+ :type: :class:`openpyxl.worksheet.worksheet.Worksheet`
+ """
+ try:
+ return self._sheets[self._active_sheet_index]
+ except IndexError:
+ pass
+
+ @active.setter
+ def active(self, value):
+ """Set the active sheet"""
+ if not isinstance(value, (_WorkbookChild, INTEGER_TYPES)):
+ raise TypeError("Value must be either a worksheet, chartsheet or numerical index")
+ if isinstance(value, INTEGER_TYPES):
+ self._active_sheet_index = value
+ return
+ #if self._sheets and 0 <= value < len(self._sheets):
+ #value = self._sheets[value]
+ #else:
+ #raise ValueError("Sheet index is outside the range of possible values", value)
+ if value not in self._sheets:
+ raise ValueError("Worksheet is not in the workbook")
+ if value.sheet_state != "visible":
+ raise ValueError("Only visible sheets can be made active")
+
+ idx = self._sheets.index(value)
+ self._active_sheet_index = idx
+
+
+ def create_sheet(self, title=None, index=None):
+ """Create a worksheet (at an optional index).
+
+ :param title: optional title of the sheet
+ :type title: str
+ :param index: optional position at which the sheet will be inserted
+ :type index: int
+
+ """
+ if self.read_only:
+ raise ReadOnlyWorkbookException('Cannot create new sheet in a read-only workbook')
+
+ if self.write_only :
+ new_ws = WriteOnlyWorksheet(parent=self, title=title)
+ else:
+ new_ws = Worksheet(parent=self, title=title)
+
+ self._add_sheet(sheet=new_ws, index=index)
+ return new_ws
+
+
+ def _add_sheet(self, sheet, index=None):
+ """Add an worksheet (at an optional index)."""
+
+ if not isinstance(sheet, (Worksheet, WriteOnlyWorksheet, Chartsheet)):
+ raise TypeError("Cannot be added to a workbook")
+
+ if sheet.parent != self:
+ raise ValueError("You cannot add worksheets from another workbook.")
+
+ if index is None:
+ self._sheets.append(sheet)
+ else:
+ self._sheets.insert(index, sheet)
+
+
+ def move_sheet(self, sheet, offset=0):
+ """
+ Move a sheet or sheetname
+ """
+ if not isinstance(sheet, Worksheet):
+ sheet = self[sheet]
+ idx = self._sheets.index(sheet)
+ del self._sheets[idx]
+ new_pos = idx + offset
+ self._sheets.insert(new_pos, sheet)
+
+
+ def remove(self, worksheet):
+ """Remove `worksheet` from this workbook."""
+ idx = self._sheets.index(worksheet)
+ self._sheets.remove(worksheet)
+
+
+ @deprecated("Use wb.remove(worksheet) or del wb[sheetname]")
+ def remove_sheet(self, worksheet):
+ """Remove `worksheet` from this workbook."""
+ self.remove(worksheet)
+
+
+ def create_chartsheet(self, title=None, index=None):
+ if self.read_only:
+ raise ReadOnlyWorkbookException("Cannot create new sheet in a read-only workbook")
+ cs = Chartsheet(parent=self, title=title)
+
+ self._add_sheet(cs, index)
+ return cs
+
+
+ @deprecated("Use wb[sheetname]")
+ def get_sheet_by_name(self, name):
+ """Returns a worksheet by its name.
+
+ :param name: the name of the worksheet to look for
+ :type name: string
+
+ """
+ return self[name]
+
+ def __contains__(self, key):
+ return key in self.sheetnames
+
+
+ def index(self, worksheet):
+ """Return the index of a worksheet."""
+ return self.worksheets.index(worksheet)
+
+
+ @deprecated("Use wb.index(worksheet)")
+ def get_index(self, worksheet):
+ """Return the index of the worksheet."""
+ return self.index(worksheet)
+
+ def __getitem__(self, key):
+ """Returns a worksheet by its name.
+
+ :param name: the name of the worksheet to look for
+ :type name: string
+
+ """
+ for sheet in self.worksheets + self.chartsheets:
+ if sheet.title == key:
+ return sheet
+ raise KeyError("Worksheet {0} does not exist.".format(key))
+
+ def __delitem__(self, key):
+ sheet = self[key]
+ self.remove(sheet)
+
+ def __iter__(self):
+ return iter(self.worksheets)
+
+
+ @deprecated("Use wb.sheetnames")
+ def get_sheet_names(self):
+ return self.sheetnames
+
+ @property
+ def worksheets(self):
+ """A list of sheets in this workbook
+
+ :type: list of :class:`openpyxl.worksheet.worksheet.Worksheet`
+ """
+ return [s for s in self._sheets if isinstance(s, (Worksheet, ReadOnlyWorksheet, WriteOnlyWorksheet))]
+
+ @property
+ def chartsheets(self):
+ """A list of Chartsheets in this workbook
+
+ :type: list of :class:`openpyxl.chartsheet.chartsheet.Chartsheet`
+ """
+ return [s for s in self._sheets if isinstance(s, Chartsheet)]
+
+ @property
+ def sheetnames(self):
+ """Returns the list of the names of worksheets in this workbook.
+
+ Names are returned in the worksheets order.
+
+ :type: list of strings
+
+ """
+ return [s.title for s in self._sheets]
+
+
+ @deprecated("Assign scoped named ranges directly to worksheets or global ones to the workbook. Deprecated in 3.1")
+ def create_named_range(self, name, worksheet=None, value=None, scope=None):
+ """Create a new named_range on a worksheet
+
+ """
+ defn = DefinedName(name=name)
+ if worksheet is not None:
+ defn.value = "{0}!{1}".format(quote_sheetname(worksheet.title), value)
+ else:
+ defn.value = value
+
+ self.defined_names[name] = defn
+
+
+ def add_named_style(self, style):
+ """
+ Add a named style
+ """
+ self._named_styles.append(style)
+ style.bind(self)
+
+
+ @property
+ def named_styles(self):
+ """
+ List available named styles
+ """
+ return self._named_styles.names
+
+
+ @property
+ def mime_type(self):
+ """
+ The mime type is determined by whether a workbook is a template or
+ not and whether it contains macros or not. Excel requires the file
+ extension to match but openpyxl does not enforce this.
+
+ """
+ ct = self.template and XLTX or XLSX
+ if self.vba_archive:
+ ct = self.template and XLTM or XLSM
+ return ct
+
+
+ def save(self, filename):
+ """Save the current workbook under the given `filename`.
+ Use this function instead of using an `ExcelWriter`.
+
+ .. warning::
+ When creating your workbook using `write_only` set to True,
+ you will only be able to call this function once. Subsequent attempts to
+ modify or save the file will raise an :class:`openpyxl.shared.exc.WorkbookAlreadySaved` exception.
+ """
+ if self.read_only:
+ raise TypeError("""Workbook is read-only""")
+ if self.write_only and not self.worksheets:
+ self.create_sheet()
+ save_workbook(self, filename)
+
+
+ @property
+ def style_names(self):
+ """
+ List of named styles
+ """
+ return [s.name for s in self._named_styles]
+
+
+ def copy_worksheet(self, from_worksheet):
+ """Copy an existing worksheet in the current workbook
+
+ .. warning::
+ This function cannot copy worksheets between workbooks.
+ worksheets can only be copied within the workbook that they belong
+
+ :param from_worksheet: the worksheet to be copied from
+ :return: copy of the initial worksheet
+ """
+ if self.__write_only or self._read_only:
+ raise ValueError("Cannot copy worksheets in read-only or write-only mode")
+
+ new_title = u"{0} Copy".format(from_worksheet.title)
+ to_worksheet = self.create_sheet(title=new_title)
+ cp = WorksheetCopy(source_worksheet=from_worksheet, target_worksheet=to_worksheet)
+ cp.copy_worksheet()
+ return to_worksheet
+
+
+ def close(self):
+ """
+ Close workbook file if open. Only affects read-only and write-only modes.
+ """
+ if hasattr(self, '_archive'):
+ self._archive.close()
+
+
+ def _duplicate_name(self, name):
+ """
+ Check for duplicate name in defined name list and table list of each worksheet.
+ Names are not case sensitive.
+ """
+ name = name.lower()
+ for sheet in self.worksheets:
+ for t in sheet.tables:
+ if name == t.lower():
+ return True
+
+ if name in self.defined_names:
+ return True
+