about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/openpyxl/packaging
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/openpyxl/packaging')
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/packaging/__init__.py3
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/packaging/core.py115
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/packaging/custom.py289
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/packaging/extended.py137
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/packaging/interface.py56
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/packaging/manifest.py194
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/packaging/relationship.py158
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/packaging/workbook.py185
8 files changed, 1137 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/packaging/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/packaging/__init__.py
new file mode 100644
index 00000000..c3085ee5
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/packaging/__init__.py
@@ -0,0 +1,3 @@
+"""
+Stuff related to Office OpenXML packaging: relationships, archive, content types.
+"""
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/packaging/core.py b/.venv/lib/python3.12/site-packages/openpyxl/packaging/core.py
new file mode 100644
index 00000000..45153732
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/packaging/core.py
@@ -0,0 +1,115 @@
+# Copyright (c) 2010-2024 openpyxl
+
+import datetime
+
+from openpyxl.descriptors import (
+    DateTime,
+    Alias,
+)
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors.nested import NestedText
+from openpyxl.xml.functions import (
+    Element,
+    QName,
+)
+from openpyxl.xml.constants import (
+    COREPROPS_NS,
+    DCORE_NS,
+    XSI_NS,
+    DCTERMS_NS,
+)
+
+
+class NestedDateTime(DateTime, NestedText):
+
+    expected_type = datetime.datetime
+
+    def to_tree(self, tagname=None, value=None, namespace=None):
+        namespace = getattr(self, "namespace", namespace)
+        if namespace is not None:
+            tagname = "{%s}%s" % (namespace, tagname)
+        el = Element(tagname)
+        if value is not None:
+            value = value.replace(tzinfo=None)
+            el.text = value.isoformat(timespec="seconds") + 'Z'
+            return el
+
+
+class QualifiedDateTime(NestedDateTime):
+
+    """In certain situations Excel will complain if the additional type
+    attribute isn't set"""
+
+    def to_tree(self, tagname=None, value=None, namespace=None):
+        el = super().to_tree(tagname, value, namespace)
+        el.set("{%s}type" % XSI_NS, QName(DCTERMS_NS, "W3CDTF"))
+        return el
+
+
+class DocumentProperties(Serialisable):
+    """High-level properties of the document.
+    Defined in ECMA-376 Par2 Annex D
+    """
+
+    tagname = "coreProperties"
+    namespace = COREPROPS_NS
+
+    category = NestedText(expected_type=str, allow_none=True)
+    contentStatus = NestedText(expected_type=str, allow_none=True)
+    keywords = NestedText(expected_type=str, allow_none=True)
+    lastModifiedBy = NestedText(expected_type=str, allow_none=True)
+    lastPrinted = NestedDateTime(allow_none=True)
+    revision = NestedText(expected_type=str, allow_none=True)
+    version = NestedText(expected_type=str, allow_none=True)
+    last_modified_by = Alias("lastModifiedBy")
+
+    # Dublin Core Properties
+    subject = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS)
+    title = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS)
+    creator = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS)
+    description = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS)
+    identifier = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS)
+    language = NestedText(expected_type=str, allow_none=True, namespace=DCORE_NS)
+    # Dublin Core Terms
+    created = QualifiedDateTime(allow_none=True, namespace=DCTERMS_NS) # assumed to be UTC
+    modified = QualifiedDateTime(allow_none=True, namespace=DCTERMS_NS) # assumed to be UTC
+
+    __elements__ = ("creator", "title", "description", "subject","identifier",
+                    "language", "created", "modified", "lastModifiedBy", "category",
+                    "contentStatus", "version", "revision", "keywords", "lastPrinted",
+                    )
+
+
+    def __init__(self,
+                 category=None,
+                 contentStatus=None,
+                 keywords=None,
+                 lastModifiedBy=None,
+                 lastPrinted=None,
+                 revision=None,
+                 version=None,
+                 created=None,
+                 creator="openpyxl",
+                 description=None,
+                 identifier=None,
+                 language=None,
+                 modified=None,
+                 subject=None,
+                 title=None,
+                 ):
+        now = datetime.datetime.now(tz=datetime.timezone.utc).replace(tzinfo=None)
+        self.contentStatus = contentStatus
+        self.lastPrinted = lastPrinted
+        self.revision = revision
+        self.version = version
+        self.creator = creator
+        self.lastModifiedBy = lastModifiedBy
+        self.modified = modified or now
+        self.created = created or now
+        self.title = title
+        self.subject = subject
+        self.description = description
+        self.identifier = identifier
+        self.language = language
+        self.keywords = keywords
+        self.category = category
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/packaging/custom.py b/.venv/lib/python3.12/site-packages/openpyxl/packaging/custom.py
new file mode 100644
index 00000000..7e253d78
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/packaging/custom.py
@@ -0,0 +1,289 @@
+# Copyright (c) 2010-2024 openpyxl
+
+"""Implementation of custom properties see ยง 22.3 in the specification"""
+
+
+from warnings import warn
+
+from openpyxl.descriptors import Strict
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors.sequence import Sequence
+from openpyxl.descriptors import (
+    Alias,
+    String,
+    Integer,
+    Float,
+    DateTime,
+    Bool,
+)
+from openpyxl.descriptors.nested import (
+    NestedText,
+)
+
+from openpyxl.xml.constants import (
+    CUSTPROPS_NS,
+    VTYPES_NS,
+    CPROPS_FMTID,
+)
+
+from .core import NestedDateTime
+
+
+class NestedBoolText(Bool, NestedText):
+    """
+    Descriptor for handling nested elements with the value stored in the text part
+    """
+
+    pass
+
+
+class _CustomDocumentProperty(Serialisable):
+
+    """
+    Low-level representation of a Custom Document Property.
+    Not used directly
+    Must always contain a child element, even if this is empty
+    """
+
+    tagname = "property"
+    _typ = None
+
+    name = String(allow_none=True)
+    lpwstr = NestedText(expected_type=str, allow_none=True, namespace=VTYPES_NS)
+    i4 = NestedText(expected_type=int, allow_none=True, namespace=VTYPES_NS)
+    r8 = NestedText(expected_type=float, allow_none=True, namespace=VTYPES_NS)
+    filetime = NestedDateTime(allow_none=True, namespace=VTYPES_NS)
+    bool = NestedBoolText(expected_type=bool, allow_none=True, namespace=VTYPES_NS)
+    linkTarget = String(expected_type=str, allow_none=True)
+    fmtid = String()
+    pid = Integer()
+
+    def __init__(self,
+                 name=None,
+                 pid=0,
+                 fmtid=CPROPS_FMTID,
+                 linkTarget=None,
+                 **kw):
+        self.fmtid = fmtid
+        self.pid = pid
+        self.name = name
+        self._typ = None
+        self.linkTarget = linkTarget
+
+        for k, v in kw.items():
+            setattr(self, k, v)
+            setattr(self, "_typ", k) # ugh!
+        for e in self.__elements__:
+            if e not in kw:
+                setattr(self, e, None)
+
+
+    @property
+    def type(self):
+        if self._typ is not None:
+            return self._typ
+        for a in self.__elements__:
+            if getattr(self, a) is not None:
+                return a
+        if self.linkTarget is not None:
+            return "linkTarget"
+
+
+    def to_tree(self, tagname=None, idx=None, namespace=None):
+        child = getattr(self, self._typ, None)
+        if child is None:
+            setattr(self, self._typ, "")
+
+        return super().to_tree(tagname=None, idx=None, namespace=None)
+
+
+class _CustomDocumentPropertyList(Serialisable):
+
+    """
+    Parses and seriliases property lists but is not used directly
+    """
+
+    tagname = "Properties"
+
+    property = Sequence(expected_type=_CustomDocumentProperty, namespace=CUSTPROPS_NS)
+    customProps = Alias("property")
+
+
+    def __init__(self, property=()):
+        self.property = property
+
+
+    def __len__(self):
+        return len(self.property)
+
+
+    def to_tree(self, tagname=None, idx=None, namespace=None):
+        for idx, p in enumerate(self.property, 2):
+            p.pid = idx
+        tree = super().to_tree(tagname, idx, namespace)
+        tree.set("xmlns", CUSTPROPS_NS)
+
+        return tree
+
+
+class _TypedProperty(Strict):
+
+    name = String()
+
+    def __init__(self,
+                 name,
+                 value):
+        self.name = name
+        self.value = value
+
+
+    def __eq__(self, other):
+        return self.name == other.name and self.value == other.value
+
+
+    def __repr__(self):
+        return f"{self.__class__.__name__}, name={self.name}, value={self.value}"
+
+
+class IntProperty(_TypedProperty):
+
+    value = Integer()
+
+
+class FloatProperty(_TypedProperty):
+
+    value = Float()
+
+
+class StringProperty(_TypedProperty):
+
+    value = String(allow_none=True)
+
+
+class DateTimeProperty(_TypedProperty):
+
+    value = DateTime()
+
+
+class BoolProperty(_TypedProperty):
+
+    value = Bool()
+
+
+class LinkProperty(_TypedProperty):
+
+    value = String()
+
+
+# from Python
+CLASS_MAPPING = {
+    StringProperty: "lpwstr",
+    IntProperty: "i4",
+    FloatProperty: "r8",
+    DateTimeProperty: "filetime",
+    BoolProperty: "bool",
+    LinkProperty: "linkTarget"
+}
+
+XML_MAPPING = {v:k for k,v in CLASS_MAPPING.items()}
+
+
+class CustomPropertyList(Strict):
+
+
+    props = Sequence(expected_type=_TypedProperty)
+
+    def __init__(self):
+        self.props = []
+
+
+    @classmethod
+    def from_tree(cls, tree):
+        """
+        Create list from OOXML element
+        """
+        prop_list = _CustomDocumentPropertyList.from_tree(tree)
+        props = []
+
+        for prop in prop_list.property:
+            attr = prop.type
+
+            typ = XML_MAPPING.get(attr, None)
+            if not typ:
+                warn(f"Unknown type for {prop.name}")
+                continue
+            value = getattr(prop, attr)
+            link = prop.linkTarget
+            if link is not None:
+                typ = LinkProperty
+                value = prop.linkTarget
+
+            new_prop = typ(name=prop.name, value=value)
+            props.append(new_prop)
+
+        new_prop_list = cls()
+        new_prop_list.props = props
+        return new_prop_list
+
+
+    def append(self, prop):
+        if prop.name in self.names:
+            raise ValueError(f"Property with name {prop.name} already exists")
+
+        self.props.append(prop)
+
+
+    def to_tree(self):
+        props = []
+
+        for p in self.props:
+            attr = CLASS_MAPPING.get(p.__class__, None)
+            if not attr:
+                raise TypeError("Unknown adapter for {p}")
+            np = _CustomDocumentProperty(name=p.name, **{attr:p.value})
+            if isinstance(p, LinkProperty):
+                np._typ = "lpwstr"
+                #np.lpwstr = ""
+            props.append(np)
+
+        prop_list = _CustomDocumentPropertyList(property=props)
+        return prop_list.to_tree()
+
+
+    def __len__(self):
+        return len(self.props)
+
+
+    @property
+    def names(self):
+        """List of property names"""
+        return [p.name for p in self.props]
+
+
+    def __getitem__(self, name):
+        """
+        Get property by name
+        """
+        for p in self.props:
+            if p.name == name:
+                return p
+        raise KeyError(f"Property with name {name} not found")
+
+
+    def __delitem__(self, name):
+        """
+        Delete a propery by name
+        """
+        for idx, p in enumerate(self.props):
+            if p.name == name:
+                self.props.pop(idx)
+                return
+        raise KeyError(f"Property with name {name} not found")
+
+
+    def __repr__(self):
+        return f"{self.__class__.__name__} containing {self.props}"
+
+
+    def __iter__(self):
+        return iter(self.props)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/packaging/extended.py b/.venv/lib/python3.12/site-packages/openpyxl/packaging/extended.py
new file mode 100644
index 00000000..fbd794af
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/packaging/extended.py
@@ -0,0 +1,137 @@
+# Copyright (c) 2010-2024 openpyxl
+
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+)
+from openpyxl.descriptors.nested import (
+    NestedText,
+)
+
+from openpyxl.xml.constants import XPROPS_NS
+from openpyxl import __version__
+
+
+class DigSigBlob(Serialisable):
+
+    __elements__ = __attrs__ = ()
+
+
+class VectorLpstr(Serialisable):
+
+    __elements__ = __attrs__ = ()
+
+
+class VectorVariant(Serialisable):
+
+    __elements__ = __attrs__ = ()
+
+
+class ExtendedProperties(Serialisable):
+
+    """
+    See 22.2
+
+    Most of this is irrelevant but Excel is very picky about the version number
+
+    It uses XX.YYYY (Version.Build) and expects everyone else to
+
+    We provide Major.Minor and the full version in the application name
+    """
+
+    tagname = "Properties"
+
+    Template = NestedText(expected_type=str, allow_none=True)
+    Manager = NestedText(expected_type=str, allow_none=True)
+    Company = NestedText(expected_type=str, allow_none=True)
+    Pages = NestedText(expected_type=int, allow_none=True)
+    Words = NestedText(expected_type=int,allow_none=True)
+    Characters = NestedText(expected_type=int, allow_none=True)
+    PresentationFormat = NestedText(expected_type=str, allow_none=True)
+    Lines = NestedText(expected_type=int, allow_none=True)
+    Paragraphs = NestedText(expected_type=int, allow_none=True)
+    Slides = NestedText(expected_type=int, allow_none=True)
+    Notes = NestedText(expected_type=int, allow_none=True)
+    TotalTime = NestedText(expected_type=int, allow_none=True)
+    HiddenSlides = NestedText(expected_type=int, allow_none=True)
+    MMClips = NestedText(expected_type=int, allow_none=True)
+    ScaleCrop = NestedText(expected_type=bool, allow_none=True)
+    HeadingPairs = Typed(expected_type=VectorVariant, allow_none=True)
+    TitlesOfParts = Typed(expected_type=VectorLpstr, allow_none=True)
+    LinksUpToDate = NestedText(expected_type=bool, allow_none=True)
+    CharactersWithSpaces = NestedText(expected_type=int, allow_none=True)
+    SharedDoc = NestedText(expected_type=bool, allow_none=True)
+    HyperlinkBase = NestedText(expected_type=str, allow_none=True)
+    HLinks = Typed(expected_type=VectorVariant, allow_none=True)
+    HyperlinksChanged = NestedText(expected_type=bool, allow_none=True)
+    DigSig = Typed(expected_type=DigSigBlob, allow_none=True)
+    Application = NestedText(expected_type=str, allow_none=True)
+    AppVersion = NestedText(expected_type=str, allow_none=True)
+    DocSecurity = NestedText(expected_type=int, allow_none=True)
+
+    __elements__ = ('Application', 'AppVersion', 'DocSecurity', 'ScaleCrop',
+                    'LinksUpToDate', 'SharedDoc', 'HyperlinksChanged')
+
+    def __init__(self,
+                 Template=None,
+                 Manager=None,
+                 Company=None,
+                 Pages=None,
+                 Words=None,
+                 Characters=None,
+                 PresentationFormat=None,
+                 Lines=None,
+                 Paragraphs=None,
+                 Slides=None,
+                 Notes=None,
+                 TotalTime=None,
+                 HiddenSlides=None,
+                 MMClips=None,
+                 ScaleCrop=None,
+                 HeadingPairs=None,
+                 TitlesOfParts=None,
+                 LinksUpToDate=None,
+                 CharactersWithSpaces=None,
+                 SharedDoc=None,
+                 HyperlinkBase=None,
+                 HLinks=None,
+                 HyperlinksChanged=None,
+                 DigSig=None,
+                 Application=None,
+                 AppVersion=None,
+                 DocSecurity=None,
+                ):
+        self.Template = Template
+        self.Manager = Manager
+        self.Company = Company
+        self.Pages = Pages
+        self.Words = Words
+        self.Characters = Characters
+        self.PresentationFormat = PresentationFormat
+        self.Lines = Lines
+        self.Paragraphs = Paragraphs
+        self.Slides = Slides
+        self.Notes = Notes
+        self.TotalTime = TotalTime
+        self.HiddenSlides = HiddenSlides
+        self.MMClips = MMClips
+        self.ScaleCrop = ScaleCrop
+        self.HeadingPairs = None
+        self.TitlesOfParts = None
+        self.LinksUpToDate = LinksUpToDate
+        self.CharactersWithSpaces = CharactersWithSpaces
+        self.SharedDoc = SharedDoc
+        self.HyperlinkBase = HyperlinkBase
+        self.HLinks = None
+        self.HyperlinksChanged = HyperlinksChanged
+        self.DigSig = None
+        self.Application = f"Microsoft Excel Compatible / Openpyxl {__version__}"
+        self.AppVersion = ".".join(__version__.split(".")[:-1])
+        self.DocSecurity = DocSecurity
+
+
+    def to_tree(self):
+        tree = super().to_tree()
+        tree.set("xmlns", XPROPS_NS)
+        return tree
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/packaging/interface.py b/.venv/lib/python3.12/site-packages/openpyxl/packaging/interface.py
new file mode 100644
index 00000000..cacc0462
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/packaging/interface.py
@@ -0,0 +1,56 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from abc import abstractproperty
+from openpyxl.compat.abc import ABC
+
+
+class ISerialisableFile(ABC):
+
+    """
+    Interface for Serialisable classes that represent files in the archive
+    """
+
+
+    @abstractproperty
+    def id(self):
+        """
+        Object id making it unique
+        """
+        pass
+
+
+    @abstractproperty
+    def _path(self):
+        """
+        File path in the archive
+        """
+        pass
+
+
+    @abstractproperty
+    def _namespace(self):
+        """
+        Qualified namespace when serialised
+        """
+        pass
+
+
+    @abstractproperty
+    def _type(self):
+        """
+        The content type for the manifest
+        """
+
+
+    @abstractproperty
+    def _rel_type(self):
+        """
+        The content type for relationships
+        """
+
+
+    @abstractproperty
+    def _rel_id(self):
+        """
+        Links object with parent
+        """
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/packaging/manifest.py b/.venv/lib/python3.12/site-packages/openpyxl/packaging/manifest.py
new file mode 100644
index 00000000..41da07f4
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/packaging/manifest.py
@@ -0,0 +1,194 @@
+# Copyright (c) 2010-2024 openpyxl
+
+"""
+File manifest
+"""
+from mimetypes import MimeTypes
+import os.path
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import String, Sequence
+from openpyxl.xml.functions import fromstring
+from openpyxl.xml.constants import (
+    ARC_CONTENT_TYPES,
+    ARC_THEME,
+    ARC_STYLE,
+    THEME_TYPE,
+    STYLES_TYPE,
+    CONTYPES_NS,
+    ACTIVEX,
+    CTRL,
+    VBA,
+)
+from openpyxl.xml.functions import tostring
+
+# initialise mime-types
+mimetypes = MimeTypes()
+mimetypes.add_type('application/xml', ".xml")
+mimetypes.add_type('application/vnd.openxmlformats-package.relationships+xml', ".rels")
+mimetypes.add_type("application/vnd.ms-office.vbaProject", ".bin")
+mimetypes.add_type("application/vnd.openxmlformats-officedocument.vmlDrawing", ".vml")
+mimetypes.add_type("image/x-emf", ".emf")
+
+
+class FileExtension(Serialisable):
+
+    tagname = "Default"
+
+    Extension = String()
+    ContentType = String()
+
+    def __init__(self, Extension, ContentType):
+        self.Extension = Extension
+        self.ContentType = ContentType
+
+
+class Override(Serialisable):
+
+    tagname = "Override"
+
+    PartName = String()
+    ContentType = String()
+
+    def __init__(self, PartName, ContentType):
+        self.PartName = PartName
+        self.ContentType = ContentType
+
+
+DEFAULT_TYPES = [
+    FileExtension("rels", "application/vnd.openxmlformats-package.relationships+xml"),
+    FileExtension("xml", "application/xml"),
+]
+
+DEFAULT_OVERRIDE = [
+    Override("/" + ARC_STYLE, STYLES_TYPE), # Styles
+    Override("/" + ARC_THEME, THEME_TYPE), # Theme
+    Override("/docProps/core.xml", "application/vnd.openxmlformats-package.core-properties+xml"),
+    Override("/docProps/app.xml", "application/vnd.openxmlformats-officedocument.extended-properties+xml")
+]
+
+
+class Manifest(Serialisable):
+
+    tagname = "Types"
+
+    Default = Sequence(expected_type=FileExtension, unique=True)
+    Override = Sequence(expected_type=Override, unique=True)
+    path = "[Content_Types].xml"
+
+    __elements__ = ("Default", "Override")
+
+    def __init__(self,
+                 Default=(),
+                 Override=(),
+                 ):
+        if not Default:
+            Default = DEFAULT_TYPES
+        self.Default = Default
+        if not Override:
+            Override = DEFAULT_OVERRIDE
+        self.Override = Override
+
+
+    @property
+    def filenames(self):
+        return [part.PartName for part in self.Override]
+
+
+    @property
+    def extensions(self):
+        """
+        Map content types to file extensions
+        Skip parts without extensions
+        """
+        exts = {os.path.splitext(part.PartName)[-1] for part in self.Override}
+        return [(ext[1:], mimetypes.types_map[True][ext]) for ext in sorted(exts) if ext]
+
+
+    def to_tree(self):
+        """
+        Custom serialisation method to allow setting a default namespace
+        """
+        defaults = [t.Extension for t in self.Default]
+        for ext, mime in self.extensions:
+            if ext not in defaults:
+                mime = FileExtension(ext, mime)
+                self.Default.append(mime)
+        tree = super().to_tree()
+        tree.set("xmlns", CONTYPES_NS)
+        return tree
+
+
+    def __contains__(self, content_type):
+        """
+        Check whether a particular content type is contained
+        """
+        for t in self.Override:
+            if t.ContentType == content_type:
+                return True
+
+
+    def find(self, content_type):
+        """
+        Find specific content-type
+        """
+        try:
+            return next(self.findall(content_type))
+        except StopIteration:
+            return
+
+
+    def findall(self, content_type):
+        """
+        Find all elements of a specific content-type
+        """
+        for t in self.Override:
+            if t.ContentType == content_type:
+                yield t
+
+
+    def append(self, obj):
+        """
+        Add content object to the package manifest
+        # needs a contract...
+        """
+        ct = Override(PartName=obj.path, ContentType=obj.mime_type)
+        self.Override.append(ct)
+
+
+    def _write(self, archive, workbook):
+        """
+        Write manifest to the archive
+        """
+        self.append(workbook)
+        self._write_vba(workbook)
+        self._register_mimetypes(filenames=archive.namelist())
+        archive.writestr(self.path, tostring(self.to_tree()))
+
+
+    def _register_mimetypes(self, filenames):
+        """
+        Make sure that the mime type for all file extensions is registered
+        """
+        for fn in filenames:
+            ext = os.path.splitext(fn)[-1]
+            if not ext:
+                continue
+            mime = mimetypes.types_map[True][ext]
+            fe = FileExtension(ext[1:], mime)
+            self.Default.append(fe)
+
+
+    def _write_vba(self, workbook):
+        """
+        Add content types from cached workbook when keeping VBA
+        """
+        if workbook.vba_archive:
+            node = fromstring(workbook.vba_archive.read(ARC_CONTENT_TYPES))
+            mf = Manifest.from_tree(node)
+            filenames = self.filenames
+            for override in mf.Override:
+                if override.PartName not in (ACTIVEX, CTRL, VBA):
+                    continue
+                if override.PartName not in filenames:
+                    self.Override.append(override)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/packaging/relationship.py b/.venv/lib/python3.12/site-packages/openpyxl/packaging/relationship.py
new file mode 100644
index 00000000..4318282d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/packaging/relationship.py
@@ -0,0 +1,158 @@
+# Copyright (c) 2010-2024 openpyxl
+
+import posixpath
+from warnings import warn
+
+from openpyxl.descriptors import (
+    String,
+    Alias,
+    Sequence,
+)
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors.container import ElementList
+
+from openpyxl.xml.constants import REL_NS, PKG_REL_NS
+from openpyxl.xml.functions import (
+    Element,
+    fromstring,
+)
+
+
+class Relationship(Serialisable):
+    """Represents many kinds of relationships."""
+
+    tagname = "Relationship"
+
+    Type = String()
+    Target = String()
+    target = Alias("Target")
+    TargetMode = String(allow_none=True)
+    Id = String(allow_none=True)
+    id = Alias("Id")
+
+
+    def __init__(self,
+                 Id=None,
+                 Type=None,
+                 type=None,
+                 Target=None,
+                 TargetMode=None
+                 ):
+        """
+        `type` can be used as a shorthand with the default relationships namespace
+        otherwise the `Type` must be a fully qualified URL
+        """
+        if type is not None:
+            Type = "{0}/{1}".format(REL_NS, type)
+        self.Type = Type
+        self.Target = Target
+        self.TargetMode = TargetMode
+        self.Id = Id
+
+
+class RelationshipList(ElementList):
+
+    tagname = "Relationships"
+    expected_type = Relationship
+
+
+    def append(self, value):
+        super().append(value)
+        if not value.Id:
+            value.Id = f"rId{len(self)}"
+
+
+    def find(self, content_type):
+        """
+        Find relationships by content-type
+        NB. these content-types namespaced objects and different to the MIME-types
+        in the package manifest :-(
+        """
+        for r in self:
+            if r.Type == content_type:
+                yield r
+
+
+    def get(self, key):
+        for r in self:
+            if r.Id == key:
+                return r
+        raise KeyError("Unknown relationship: {0}".format(key))
+
+
+    def to_dict(self):
+        """Return a dictionary of relations keyed by id"""
+        return {r.id:r for r in self}
+
+
+    def to_tree(self):
+        tree = super().to_tree()
+        tree.set("xmlns", PKG_REL_NS)
+        return tree
+
+
+def get_rels_path(path):
+    """
+    Convert relative path to absolutes that can be loaded from a zip
+    archive.
+    The path to be passed in is that of containing object (workbook,
+    worksheet, etc.)
+    """
+    folder, obj = posixpath.split(path)
+    filename = posixpath.join(folder, '_rels', '{0}.rels'.format(obj))
+    return filename
+
+
+def get_dependents(archive, filename):
+    """
+    Normalise dependency file paths to absolute ones
+
+    Relative paths are relative to parent object
+    """
+    src = archive.read(filename)
+    node = fromstring(src)
+    try:
+        rels = RelationshipList.from_tree(node)
+    except TypeError:
+        msg = "{0} contains invalid dependency definitions".format(filename)
+        warn(msg)
+        rels = RelationshipList()
+    folder = posixpath.dirname(filename)
+    parent = posixpath.split(folder)[0]
+    for r in rels:
+        if r.TargetMode == "External":
+            continue
+        elif r.target.startswith("/"):
+            r.target = r.target[1:]
+        else:
+            pth = posixpath.join(parent, r.target)
+            r.target = posixpath.normpath(pth)
+    return rels
+
+
+def get_rel(archive, deps, id=None, cls=None):
+    """
+    Get related object based on id or rel_type
+    """
+    if not any([id, cls]):
+        raise ValueError("Either the id or the content type are required")
+    if id is not None:
+        rel = deps.get(id)
+    else:
+        try:
+            rel = next(deps.find(cls.rel_type))
+        except StopIteration: # no known dependency
+            return
+
+    path = rel.target
+    src = archive.read(path)
+    tree = fromstring(src)
+    obj = cls.from_tree(tree)
+
+    rels_path = get_rels_path(path)
+    try:
+        obj.deps = get_dependents(archive, rels_path)
+    except KeyError:
+        obj.deps = []
+
+    return obj
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/packaging/workbook.py b/.venv/lib/python3.12/site-packages/openpyxl/packaging/workbook.py
new file mode 100644
index 00000000..a6413cdc
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/packaging/workbook.py
@@ -0,0 +1,185 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Alias,
+    Typed,
+    String,
+    Integer,
+    Bool,
+    NoneSet,
+)
+from openpyxl.descriptors.excel import ExtensionList, Relation
+from openpyxl.descriptors.sequence import NestedSequence
+from openpyxl.descriptors.nested import NestedString
+
+from openpyxl.xml.constants import SHEET_MAIN_NS
+
+from openpyxl.workbook.defined_name import DefinedNameList
+from openpyxl.workbook.external_reference import ExternalReference
+from openpyxl.workbook.function_group import FunctionGroupList
+from openpyxl.workbook.properties import WorkbookProperties, CalcProperties, FileVersion
+from openpyxl.workbook.protection import WorkbookProtection, FileSharing
+from openpyxl.workbook.smart_tags import SmartTagList, SmartTagProperties
+from openpyxl.workbook.views import CustomWorkbookView, BookView
+from openpyxl.workbook.web import WebPublishing, WebPublishObjectList
+
+
+class FileRecoveryProperties(Serialisable):
+
+    tagname = "fileRecoveryPr"
+
+    autoRecover = Bool(allow_none=True)
+    crashSave = Bool(allow_none=True)
+    dataExtractLoad = Bool(allow_none=True)
+    repairLoad = Bool(allow_none=True)
+
+    def __init__(self,
+                 autoRecover=None,
+                 crashSave=None,
+                 dataExtractLoad=None,
+                 repairLoad=None,
+                ):
+        self.autoRecover = autoRecover
+        self.crashSave = crashSave
+        self.dataExtractLoad = dataExtractLoad
+        self.repairLoad = repairLoad
+
+
+class ChildSheet(Serialisable):
+    """
+    Represents a reference to a worksheet or chartsheet in workbook.xml
+
+    It contains the title, order and state but only an indirect reference to
+    the objects themselves.
+    """
+
+    tagname = "sheet"
+
+    name = String()
+    sheetId = Integer()
+    state = NoneSet(values=(['visible', 'hidden', 'veryHidden']))
+    id = Relation()
+
+    def __init__(self,
+                 name=None,
+                 sheetId=None,
+                 state="visible",
+                 id=None,
+                ):
+        self.name = name
+        self.sheetId = sheetId
+        self.state = state
+        self.id = id
+
+
+class PivotCache(Serialisable):
+
+    tagname = "pivotCache"
+
+    cacheId = Integer()
+    id = Relation()
+
+    def __init__(self,
+                 cacheId=None,
+                 id=None
+                ):
+        self.cacheId = cacheId
+        self.id = id
+
+
+class WorkbookPackage(Serialisable):
+
+    """
+    Represent the workbook file in the archive
+    """
+
+    tagname = "workbook"
+
+    conformance = NoneSet(values=['strict', 'transitional'])
+    fileVersion = Typed(expected_type=FileVersion, allow_none=True)
+    fileSharing = Typed(expected_type=FileSharing, allow_none=True)
+    workbookPr = Typed(expected_type=WorkbookProperties, allow_none=True)
+    properties = Alias("workbookPr")
+    workbookProtection = Typed(expected_type=WorkbookProtection, allow_none=True)
+    bookViews = NestedSequence(expected_type=BookView)
+    sheets = NestedSequence(expected_type=ChildSheet)
+    functionGroups = Typed(expected_type=FunctionGroupList, allow_none=True)
+    externalReferences = NestedSequence(expected_type=ExternalReference)
+    definedNames = Typed(expected_type=DefinedNameList, allow_none=True)
+    calcPr = Typed(expected_type=CalcProperties, allow_none=True)
+    oleSize = NestedString(allow_none=True, attribute="ref")
+    customWorkbookViews = NestedSequence(expected_type=CustomWorkbookView)
+    pivotCaches = NestedSequence(expected_type=PivotCache, allow_none=True)
+    smartTagPr = Typed(expected_type=SmartTagProperties, allow_none=True)
+    smartTagTypes = Typed(expected_type=SmartTagList, allow_none=True)
+    webPublishing = Typed(expected_type=WebPublishing, allow_none=True)
+    fileRecoveryPr = Typed(expected_type=FileRecoveryProperties, allow_none=True)
+    webPublishObjects = Typed(expected_type=WebPublishObjectList, allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+    Ignorable = NestedString(namespace="http://schemas.openxmlformats.org/markup-compatibility/2006", allow_none=True)
+
+    __elements__ = ('fileVersion', 'fileSharing', 'workbookPr',
+                    'workbookProtection', 'bookViews', 'sheets', 'functionGroups',
+                    'externalReferences', 'definedNames', 'calcPr', 'oleSize',
+                    'customWorkbookViews', 'pivotCaches', 'smartTagPr', 'smartTagTypes',
+                    'webPublishing', 'fileRecoveryPr', 'webPublishObjects')
+
+    def __init__(self,
+                 conformance=None,
+                 fileVersion=None,
+                 fileSharing=None,
+                 workbookPr=None,
+                 workbookProtection=None,
+                 bookViews=(),
+                 sheets=(),
+                 functionGroups=None,
+                 externalReferences=(),
+                 definedNames=None,
+                 calcPr=None,
+                 oleSize=None,
+                 customWorkbookViews=(),
+                 pivotCaches=(),
+                 smartTagPr=None,
+                 smartTagTypes=None,
+                 webPublishing=None,
+                 fileRecoveryPr=None,
+                 webPublishObjects=None,
+                 extLst=None,
+                 Ignorable=None,
+                ):
+        self.conformance = conformance
+        self.fileVersion = fileVersion
+        self.fileSharing = fileSharing
+        if workbookPr is None:
+            workbookPr = WorkbookProperties()
+        self.workbookPr = workbookPr
+        self.workbookProtection = workbookProtection
+        self.bookViews = bookViews
+        self.sheets = sheets
+        self.functionGroups = functionGroups
+        self.externalReferences = externalReferences
+        self.definedNames = definedNames
+        self.calcPr = calcPr
+        self.oleSize = oleSize
+        self.customWorkbookViews = customWorkbookViews
+        self.pivotCaches = pivotCaches
+        self.smartTagPr = smartTagPr
+        self.smartTagTypes = smartTagTypes
+        self.webPublishing = webPublishing
+        self.fileRecoveryPr = fileRecoveryPr
+        self.webPublishObjects = webPublishObjects
+
+
+    def to_tree(self):
+        tree = super().to_tree()
+        tree.set("xmlns", SHEET_MAIN_NS)
+        return tree
+
+
+    @property
+    def active(self):
+        for view in self.bookViews:
+            if view.activeTab is not None:
+                return view.activeTab
+        return 0