about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/pptx/opc
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/pptx/opc')
-rw-r--r--.venv/lib/python3.12/site-packages/pptx/opc/__init__.py0
-rw-r--r--.venv/lib/python3.12/site-packages/pptx/opc/constants.py331
-rw-r--r--.venv/lib/python3.12/site-packages/pptx/opc/oxml.py188
-rw-r--r--.venv/lib/python3.12/site-packages/pptx/opc/package.py762
-rw-r--r--.venv/lib/python3.12/site-packages/pptx/opc/packuri.py109
-rw-r--r--.venv/lib/python3.12/site-packages/pptx/opc/serialized.py296
-rw-r--r--.venv/lib/python3.12/site-packages/pptx/opc/shared.py20
-rw-r--r--.venv/lib/python3.12/site-packages/pptx/opc/spec.py44
8 files changed, 1750 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/pptx/opc/__init__.py b/.venv/lib/python3.12/site-packages/pptx/opc/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pptx/opc/__init__.py
diff --git a/.venv/lib/python3.12/site-packages/pptx/opc/constants.py b/.venv/lib/python3.12/site-packages/pptx/opc/constants.py
new file mode 100644
index 00000000..e1b08a93
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pptx/opc/constants.py
@@ -0,0 +1,331 @@
+"""Constant values related to the Open Packaging Convention.
+
+In particular, this includes content (MIME) types and relationship types.
+"""
+
+from __future__ import annotations
+
+
+class CONTENT_TYPE:
+    """Content type URIs (like MIME-types) that specify a part's format."""
+
+    ASF = "video/x-ms-asf"
+    AVI = "video/avi"
+    BMP = "image/bmp"
+    DML_CHART = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"
+    DML_CHARTSHAPES = "application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml"
+    DML_DIAGRAM_COLORS = "application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml"
+    DML_DIAGRAM_DATA = "application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml"
+    DML_DIAGRAM_DRAWING = "application/vnd.ms-office.drawingml.diagramDrawing+xml"
+    DML_DIAGRAM_LAYOUT = "application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml"
+    DML_DIAGRAM_STYLE = "application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml"
+    GIF = "image/gif"
+    INK = "application/inkml+xml"
+    JPEG = "image/jpeg"
+    MOV = "video/quicktime"
+    MP4 = "video/mp4"
+    MPG = "video/mpeg"
+    MS_PHOTO = "image/vnd.ms-photo"
+    MS_VIDEO = "video/msvideo"
+    OFC_CHART_COLORS = "application/vnd.ms-office.chartcolorstyle+xml"
+    OFC_CHART_EX = "application/vnd.ms-office.chartex+xml"
+    OFC_CHART_STYLE = "application/vnd.ms-office.chartstyle+xml"
+    OFC_CUSTOM_PROPERTIES = "application/vnd.openxmlformats-officedocument.custom-properties+xml"
+    OFC_CUSTOM_XML_PROPERTIES = (
+        "application/vnd.openxmlformats-officedocument.customXmlProperties+xml"
+    )
+    OFC_DRAWING = "application/vnd.openxmlformats-officedocument.drawing+xml"
+    OFC_EXTENDED_PROPERTIES = (
+        "application/vnd.openxmlformats-officedocument.extended-properties+xml"
+    )
+    OFC_OLE_OBJECT = "application/vnd.openxmlformats-officedocument.oleObject"
+    OFC_PACKAGE = "application/vnd.openxmlformats-officedocument.package"
+    OFC_THEME = "application/vnd.openxmlformats-officedocument.theme+xml"
+    OFC_THEME_OVERRIDE = "application/vnd.openxmlformats-officedocument.themeOverride+xml"
+    OFC_VML_DRAWING = "application/vnd.openxmlformats-officedocument.vmlDrawing"
+    OPC_CORE_PROPERTIES = "application/vnd.openxmlformats-package.core-properties+xml"
+    OPC_DIGITAL_SIGNATURE_CERTIFICATE = (
+        "application/vnd.openxmlformats-package.digital-signature-certificate"
+    )
+    OPC_DIGITAL_SIGNATURE_ORIGIN = "application/vnd.openxmlformats-package.digital-signature-origin"
+    OPC_DIGITAL_SIGNATURE_XMLSIGNATURE = (
+        "application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml"
+    )
+    OPC_RELATIONSHIPS = "application/vnd.openxmlformats-package.relationships+xml"
+    PML_COMMENTS = "application/vnd.openxmlformats-officedocument.presentationml.comments+xml"
+    PML_COMMENT_AUTHORS = (
+        "application/vnd.openxmlformats-officedocument.presentationml.commentAuthors+xml"
+    )
+    PML_HANDOUT_MASTER = (
+        "application/vnd.openxmlformats-officedocument.presentationml.handoutMaster+xml"
+    )
+    PML_NOTES_MASTER = (
+        "application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml"
+    )
+    PML_NOTES_SLIDE = "application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml"
+    PML_PRESENTATION = "application/vnd.openxmlformats-officedocument.presentationml.presentation"
+    PML_PRESENTATION_MAIN = (
+        "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"
+    )
+    PML_PRES_MACRO_MAIN = "application/vnd.ms-powerpoint.presentation.macroEnabled.main+xml"
+    PML_PRES_PROPS = "application/vnd.openxmlformats-officedocument.presentationml.presProps+xml"
+    PML_PRINTER_SETTINGS = (
+        "application/vnd.openxmlformats-officedocument.presentationml.printerSettings"
+    )
+    PML_SLIDE = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml"
+    PML_SLIDESHOW_MAIN = (
+        "application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml"
+    )
+    PML_SLIDE_LAYOUT = (
+        "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"
+    )
+    PML_SLIDE_MASTER = (
+        "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"
+    )
+    PML_SLIDE_UPDATE_INFO = (
+        "application/vnd.openxmlformats-officedocument.presentationml.slideUpdateInfo+xml"
+    )
+    PML_TABLE_STYLES = (
+        "application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml"
+    )
+    PML_TAGS = "application/vnd.openxmlformats-officedocument.presentationml.tags+xml"
+    PML_TEMPLATE_MAIN = (
+        "application/vnd.openxmlformats-officedocument.presentationml.template.main+xml"
+    )
+    PML_VIEW_PROPS = "application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml"
+    PNG = "image/png"
+    SML_CALC_CHAIN = "application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml"
+    SML_CHARTSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml"
+    SML_COMMENTS = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"
+    SML_CONNECTIONS = "application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml"
+    SML_CUSTOM_PROPERTY = (
+        "application/vnd.openxmlformats-officedocument.spreadsheetml.customProperty"
+    )
+    SML_DIALOGSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml"
+    SML_EXTERNAL_LINK = (
+        "application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml"
+    )
+    SML_PIVOT_CACHE_DEFINITION = (
+        "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml"
+    )
+    SML_PIVOT_CACHE_RECORDS = (
+        "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml"
+    )
+    SML_PIVOT_TABLE = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"
+    SML_PRINTER_SETTINGS = (
+        "application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings"
+    )
+    SML_QUERY_TABLE = "application/vnd.openxmlformats-officedocument.spreadsheetml.queryTable+xml"
+    SML_REVISION_HEADERS = (
+        "application/vnd.openxmlformats-officedocument.spreadsheetml.revisionHeaders+xml"
+    )
+    SML_REVISION_LOG = "application/vnd.openxmlformats-officedocument.spreadsheetml.revisionLog+xml"
+    SML_SHARED_STRINGS = (
+        "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"
+    )
+    SML_SHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
+    SML_SHEET_MAIN = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"
+    SML_SHEET_METADATA = (
+        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml"
+    )
+    SML_STYLES = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"
+    SML_TABLE = "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"
+    SML_TABLE_SINGLE_CELLS = (
+        "application/vnd.openxmlformats-officedocument.spreadsheetml.tableSingleCells+xml"
+    )
+    SML_TEMPLATE_MAIN = (
+        "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml"
+    )
+    SML_USER_NAMES = "application/vnd.openxmlformats-officedocument.spreadsheetml.userNames+xml"
+    SML_VOLATILE_DEPENDENCIES = (
+        "application/vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml"
+    )
+    SML_WORKSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"
+    SWF = "application/x-shockwave-flash"
+    TIFF = "image/tiff"
+    VIDEO = "video/unknown"
+    WML_COMMENTS = "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml"
+    WML_DOCUMENT = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
+    WML_DOCUMENT_GLOSSARY = (
+        "application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml"
+    )
+    WML_DOCUMENT_MAIN = (
+        "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"
+    )
+    WML_ENDNOTES = "application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml"
+    WML_FONT_TABLE = "application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml"
+    WML_FOOTER = "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"
+    WML_FOOTNOTES = "application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml"
+    WML_HEADER = "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml"
+    WML_NUMBERING = "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"
+    WML_PRINTER_SETTINGS = (
+        "application/vnd.openxmlformats-officedocument.wordprocessingml.printerSettings"
+    )
+    WML_SETTINGS = "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml"
+    WML_STYLES = "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"
+    WML_WEB_SETTINGS = (
+        "application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml"
+    )
+    WMV = "video/x-ms-wmv"
+    XML = "application/xml"
+    X_EMF = "image/x-emf"
+    X_FONTDATA = "application/x-fontdata"
+    X_FONT_TTF = "application/x-font-ttf"
+    X_MS_VIDEO = "video/x-msvideo"
+    X_WMF = "image/x-wmf"
+
+
+class NAMESPACE:
+    """Constant values for OPC XML namespaces"""
+
+    DML_WORDPROCESSING_DRAWING = (
+        "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
+    )
+    OFC_RELATIONSHIPS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
+    OPC_RELATIONSHIPS = "http://schemas.openxmlformats.org/package/2006/relationships"
+    OPC_CONTENT_TYPES = "http://schemas.openxmlformats.org/package/2006/content-types"
+    WML_MAIN = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
+
+
+class RELATIONSHIP_TARGET_MODE:
+    """Open XML relationship target modes"""
+
+    EXTERNAL = "External"
+    INTERNAL = "Internal"
+
+
+class RELATIONSHIP_TYPE:
+    AUDIO = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/audio"
+    A_F_CHUNK = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/aFChunk"
+    CALC_CHAIN = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/calcChain"
+    CERTIFICATE = (
+        "http://schemas.openxmlformats.org/package/2006/relationships/digital-signatu"
+        "re/certificate"
+    )
+    CHART = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart"
+    CHARTSHEET = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet"
+    CHART_COLOR_STYLE = "http://schemas.microsoft.com/office/2011/relationships/chartColorStyle"
+    CHART_USER_SHAPES = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartUserShapes"
+    )
+    COMMENTS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"
+    COMMENT_AUTHORS = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/commentAuthors"
+    )
+    CONNECTIONS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/connections"
+    CONTROL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/control"
+    CORE_PROPERTIES = (
+        "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"
+    )
+    CUSTOM_PROPERTIES = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties"
+    )
+    CUSTOM_PROPERTY = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/customProperty"
+    )
+    CUSTOM_XML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXml"
+    CUSTOM_XML_PROPS = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXmlProps"
+    )
+    DIAGRAM_COLORS = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramColors"
+    )
+    DIAGRAM_DATA = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramData"
+    DIAGRAM_LAYOUT = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramLayout"
+    )
+    DIAGRAM_QUICK_STYLE = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramQuickStyle"
+    )
+    DIALOGSHEET = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/dialogsheet"
+    DRAWING = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"
+    ENDNOTES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes"
+    EXTENDED_PROPERTIES = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties"
+    )
+    EXTERNAL_LINK = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/externalLink"
+    )
+    FONT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/font"
+    FONT_TABLE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable"
+    FOOTER = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer"
+    FOOTNOTES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes"
+    GLOSSARY_DOCUMENT = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/glossaryDocument"
+    )
+    HANDOUT_MASTER = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/handoutMaster"
+    )
+    HEADER = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header"
+    HYPERLINK = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
+    IMAGE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
+    MEDIA = "http://schemas.microsoft.com/office/2007/relationships/media"
+    NOTES_MASTER = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesMaster"
+    NOTES_SLIDE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide"
+    NUMBERING = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering"
+    OFFICE_DOCUMENT = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
+    )
+    OLE_OBJECT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject"
+    ORIGIN = "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/origin"
+    PACKAGE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/package"
+    PIVOT_CACHE_DEFINITION = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCac"
+        "heDefinition"
+    )
+    PIVOT_CACHE_RECORDS = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/spreadsh"
+        "eetml/pivotCacheRecords"
+    )
+    PIVOT_TABLE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable"
+    PRES_PROPS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/presProps"
+    PRINTER_SETTINGS = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings"
+    )
+    QUERY_TABLE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/queryTable"
+    REVISION_HEADERS = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/revisionHeaders"
+    )
+    REVISION_LOG = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/revisionLog"
+    SETTINGS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings"
+    SHARED_STRINGS = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"
+    )
+    SHEET_METADATA = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sheetMetadata"
+    )
+    SIGNATURE = (
+        "http://schemas.openxmlformats.org/package/2006/relationships/digital-signatu"
+        "re/signature"
+    )
+    SLIDE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"
+    SLIDE_LAYOUT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout"
+    SLIDE_MASTER = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster"
+    SLIDE_UPDATE_INFO = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideUpdateInfo"
+    )
+    STYLES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"
+    TABLE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table"
+    TABLE_SINGLE_CELLS = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableSingleCells"
+    )
+    TABLE_STYLES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableStyles"
+    TAGS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/tags"
+    THEME = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme"
+    THEME_OVERRIDE = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/themeOverride"
+    )
+    THUMBNAIL = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail"
+    USERNAMES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/usernames"
+    VIDEO = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/video"
+    VIEW_PROPS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/viewProps"
+    VML_DRAWING = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing"
+    VOLATILE_DEPENDENCIES = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/volatile"
+        "Dependencies"
+    )
+    WEB_SETTINGS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings"
+    WORKSHEET_SOURCE = (
+        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheetSource"
+    )
+    XML_MAPS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/xmlMaps"
diff --git a/.venv/lib/python3.12/site-packages/pptx/opc/oxml.py b/.venv/lib/python3.12/site-packages/pptx/opc/oxml.py
new file mode 100644
index 00000000..5dd902a5
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pptx/opc/oxml.py
@@ -0,0 +1,188 @@
+"""OPC-local oxml module to handle OPC-local concerns like relationship parsing."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Callable, cast
+
+from lxml import etree
+
+from pptx.opc.constants import NAMESPACE as NS
+from pptx.opc.constants import RELATIONSHIP_TARGET_MODE as RTM
+from pptx.oxml import parse_xml, register_element_cls
+from pptx.oxml.simpletypes import (
+    ST_ContentType,
+    ST_Extension,
+    ST_TargetMode,
+    XsdAnyUri,
+    XsdId,
+)
+from pptx.oxml.xmlchemy import (
+    BaseOxmlElement,
+    OptionalAttribute,
+    RequiredAttribute,
+    ZeroOrMore,
+)
+
+if TYPE_CHECKING:
+    from pptx.opc.packuri import PackURI
+
+nsmap = {
+    "ct": NS.OPC_CONTENT_TYPES,
+    "pr": NS.OPC_RELATIONSHIPS,
+    "r": NS.OFC_RELATIONSHIPS,
+}
+
+
+def oxml_to_encoded_bytes(
+    element: BaseOxmlElement,
+    encoding: str = "utf-8",
+    pretty_print: bool = False,
+    standalone: bool | None = None,
+) -> bytes:
+    return etree.tostring(
+        element, encoding=encoding, pretty_print=pretty_print, standalone=standalone
+    )
+
+
+def oxml_tostring(
+    elm: BaseOxmlElement,
+    encoding: str | None = None,
+    pretty_print: bool = False,
+    standalone: bool | None = None,
+):
+    return etree.tostring(elm, encoding=encoding, pretty_print=pretty_print, standalone=standalone)
+
+
+def serialize_part_xml(part_elm: BaseOxmlElement) -> bytes:
+    """Produce XML-file bytes for `part_elm`, suitable for writing directly to a `.xml` file.
+
+    Includes XML-declaration header.
+    """
+    return etree.tostring(part_elm, encoding="UTF-8", standalone=True)
+
+
+class CT_Default(BaseOxmlElement):
+    """`<Default>` element.
+
+    Specifies the default content type to be applied to a part with the specified extension.
+    """
+
+    extension: str = RequiredAttribute(  # pyright: ignore[reportAssignmentType]
+        "Extension", ST_Extension
+    )
+    contentType: str = RequiredAttribute(  # pyright: ignore[reportAssignmentType]
+        "ContentType", ST_ContentType
+    )
+
+
+class CT_Override(BaseOxmlElement):
+    """`<Override>` element.
+
+    Specifies the content type to be applied for a part with the specified partname.
+    """
+
+    partName: str = RequiredAttribute(  # pyright: ignore[reportAssignmentType]
+        "PartName", XsdAnyUri
+    )
+    contentType: str = RequiredAttribute(  # pyright: ignore[reportAssignmentType]
+        "ContentType", ST_ContentType
+    )
+
+
+class CT_Relationship(BaseOxmlElement):
+    """`<Relationship>` element.
+
+    Represents a single relationship from a source to a target part.
+    """
+
+    rId: str = RequiredAttribute("Id", XsdId)  # pyright: ignore[reportAssignmentType]
+    reltype: str = RequiredAttribute("Type", XsdAnyUri)  # pyright: ignore[reportAssignmentType]
+    target_ref: str = RequiredAttribute(  # pyright: ignore[reportAssignmentType]
+        "Target", XsdAnyUri
+    )
+    targetMode: str = OptionalAttribute(  # pyright: ignore[reportAssignmentType]
+        "TargetMode", ST_TargetMode, default=RTM.INTERNAL
+    )
+
+    @classmethod
+    def new(
+        cls, rId: str, reltype: str, target_ref: str, target_mode: str = RTM.INTERNAL
+    ) -> CT_Relationship:
+        """Return a new `<Relationship>` element.
+
+        `target_ref` is either a partname or a URI.
+        """
+        relationship = cast(CT_Relationship, parse_xml(f'<Relationship xmlns="{nsmap["pr"]}"/>'))
+        relationship.rId = rId
+        relationship.reltype = reltype
+        relationship.target_ref = target_ref
+        relationship.targetMode = target_mode
+        return relationship
+
+
+class CT_Relationships(BaseOxmlElement):
+    """`<Relationships>` element, the root element in a .rels file."""
+
+    relationship_lst: list[CT_Relationship]
+    _insert_relationship: Callable[[CT_Relationship], CT_Relationship]
+
+    relationship = ZeroOrMore("pr:Relationship")
+
+    def add_rel(
+        self, rId: str, reltype: str, target: str, is_external: bool = False
+    ) -> CT_Relationship:
+        """Add a child `<Relationship>` element with attributes set as specified."""
+        target_mode = RTM.EXTERNAL if is_external else RTM.INTERNAL
+        relationship = CT_Relationship.new(rId, reltype, target, target_mode)
+        return self._insert_relationship(relationship)
+
+    @classmethod
+    def new(cls) -> CT_Relationships:
+        """Return a new `<Relationships>` element."""
+        return cast(CT_Relationships, parse_xml(f'<Relationships xmlns="{nsmap["pr"]}"/>'))
+
+    @property
+    def xml_file_bytes(self) -> bytes:
+        """Return XML bytes, with XML-declaration, for this `<Relationships>` element.
+
+        Suitable for saving in a .rels stream, not pretty printed and with an XML declaration at
+        the top.
+        """
+        return oxml_to_encoded_bytes(self, encoding="UTF-8", standalone=True)
+
+
+class CT_Types(BaseOxmlElement):
+    """`<Types>` element.
+
+    The container element for Default and Override elements in [Content_Types].xml.
+    """
+
+    default_lst: list[CT_Default]
+    override_lst: list[CT_Override]
+
+    _add_default: Callable[..., CT_Default]
+    _add_override: Callable[..., CT_Override]
+
+    default = ZeroOrMore("ct:Default")
+    override = ZeroOrMore("ct:Override")
+
+    def add_default(self, ext: str, content_type: str) -> CT_Default:
+        """Add a child `<Default>` element with attributes set to parameter values."""
+        return self._add_default(extension=ext, contentType=content_type)
+
+    def add_override(self, partname: PackURI, content_type: str) -> CT_Override:
+        """Add a child `<Override>` element with attributes set to parameter values."""
+        return self._add_override(partName=partname, contentType=content_type)
+
+    @classmethod
+    def new(cls) -> CT_Types:
+        """Return a new `<Types>` element."""
+        return cast(CT_Types, parse_xml(f'<Types xmlns="{nsmap["ct"]}"/>'))
+
+
+register_element_cls("ct:Default", CT_Default)
+register_element_cls("ct:Override", CT_Override)
+register_element_cls("ct:Types", CT_Types)
+
+register_element_cls("pr:Relationship", CT_Relationship)
+register_element_cls("pr:Relationships", CT_Relationships)
diff --git a/.venv/lib/python3.12/site-packages/pptx/opc/package.py b/.venv/lib/python3.12/site-packages/pptx/opc/package.py
new file mode 100644
index 00000000..713759c5
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pptx/opc/package.py
@@ -0,0 +1,762 @@
+"""Fundamental Open Packaging Convention (OPC) objects.
+
+The :mod:`pptx.packaging` module coheres around the concerns of reading and writing
+presentations to and from a .pptx file.
+"""
+
+from __future__ import annotations
+
+import collections
+from typing import IO, TYPE_CHECKING, DefaultDict, Iterator, Mapping, Set, cast
+
+from pptx.opc.constants import RELATIONSHIP_TARGET_MODE as RTM
+from pptx.opc.constants import RELATIONSHIP_TYPE as RT
+from pptx.opc.oxml import CT_Relationships, serialize_part_xml
+from pptx.opc.packuri import CONTENT_TYPES_URI, PACKAGE_URI, PackURI
+from pptx.opc.serialized import PackageReader, PackageWriter
+from pptx.opc.shared import CaseInsensitiveDict
+from pptx.oxml import parse_xml
+from pptx.util import lazyproperty
+
+if TYPE_CHECKING:
+    from typing_extensions import Self
+
+    from pptx.opc.oxml import CT_Relationship, CT_Types
+    from pptx.oxml.xmlchemy import BaseOxmlElement
+    from pptx.package import Package
+    from pptx.parts.presentation import PresentationPart
+
+
+class _RelatableMixin:
+    """Provide relationship methods required by both the package and each part."""
+
+    def part_related_by(self, reltype: str) -> Part:
+        """Return (single) part having relationship to this package of `reltype`.
+
+        Raises |KeyError| if no such relationship is found and |ValueError| if more than one such
+        relationship is found.
+        """
+        return self._rels.part_with_reltype(reltype)
+
+    def relate_to(self, target: Part | str, reltype: str, is_external: bool = False) -> str:
+        """Return rId key of relationship of `reltype` to `target`.
+
+        If such a relationship already exists, its rId is returned. Otherwise the relationship is
+        added and its new rId returned.
+        """
+        if isinstance(target, str):
+            assert is_external
+            return self._rels.get_or_add_ext_rel(reltype, target)
+
+        return self._rels.get_or_add(reltype, target)
+
+    def related_part(self, rId: str) -> Part:
+        """Return related |Part| subtype identified by `rId`."""
+        return self._rels[rId].target_part
+
+    def target_ref(self, rId: str) -> str:
+        """Return URL contained in target ref of relationship identified by `rId`."""
+        return self._rels[rId].target_ref
+
+    @lazyproperty
+    def _rels(self) -> _Relationships:
+        """|_Relationships| object containing relationships from this part to others."""
+        raise NotImplementedError(  # pragma: no cover
+            "`%s` must implement `.rels`" % type(self).__name__
+        )
+
+
+class OpcPackage(_RelatableMixin):
+    """Main API class for |python-opc|.
+
+    A new instance is constructed by calling the :meth:`open` classmethod with a path to a package
+    file or file-like object containing a package (.pptx file).
+    """
+
+    def __init__(self, pkg_file: str | IO[bytes]):
+        self._pkg_file = pkg_file
+
+    @classmethod
+    def open(cls, pkg_file: str | IO[bytes]) -> Self:
+        """Return an |OpcPackage| instance loaded with the contents of `pkg_file`."""
+        return cls(pkg_file)._load()
+
+    def drop_rel(self, rId: str) -> None:
+        """Remove relationship identified by `rId`."""
+        self._rels.pop(rId)
+
+    def iter_parts(self) -> Iterator[Part]:
+        """Generate exactly one reference to each part in the package."""
+        visited: Set[Part] = set()
+        for rel in self.iter_rels():
+            if rel.is_external:
+                continue
+            part = rel.target_part
+            if part in visited:
+                continue
+            yield part
+            visited.add(part)
+
+    def iter_rels(self) -> Iterator[_Relationship]:
+        """Generate exactly one reference to each relationship in package.
+
+        Performs a depth-first traversal of the rels graph.
+        """
+        visited: Set[Part] = set()
+
+        def walk_rels(rels: _Relationships) -> Iterator[_Relationship]:
+            for rel in rels.values():
+                yield rel
+                # --- external items can have no relationships ---
+                if rel.is_external:
+                    continue
+                # -- all relationships other than those for the package belong to a part. Once
+                # -- that part has been processed, processing it again would lead to the same
+                # -- relationships appearing more than once.
+                part = rel.target_part
+                if part in visited:
+                    continue
+                visited.add(part)
+                # --- recurse into relationships of each unvisited target-part ---
+                yield from walk_rels(part.rels)
+
+        yield from walk_rels(self._rels)
+
+    @property
+    def main_document_part(self) -> PresentationPart:
+        """Return |Part| subtype serving as the main document part for this package.
+
+        In this case it will be a |Presentation| part.
+        """
+        return cast("PresentationPart", self.part_related_by(RT.OFFICE_DOCUMENT))
+
+    def next_partname(self, tmpl: str) -> PackURI:
+        """Return |PackURI| next available partname matching `tmpl`.
+
+        `tmpl` is a printf (%)-style template string containing a single replacement item, a '%d'
+        to be used to insert the integer portion of the partname. Example:
+        '/ppt/slides/slide%d.xml'
+        """
+        # --- expected next partname is tmpl % n where n is one greater than the number
+        # --- of existing partnames that match tmpl. Speed up finding the next one
+        # --- (maybe) by searching from the end downward rather than from 1 upward.
+        prefix = tmpl[: (tmpl % 42).find("42")]
+        partnames = {p.partname for p in self.iter_parts() if p.partname.startswith(prefix)}
+        for n in range(len(partnames) + 1, 0, -1):
+            candidate_partname = tmpl % n
+            if candidate_partname not in partnames:
+                return PackURI(candidate_partname)
+        raise Exception("ProgrammingError: ran out of candidate_partnames")  # pragma: no cover
+
+    def save(self, pkg_file: str | IO[bytes]) -> None:
+        """Save this package to `pkg_file`.
+
+        `file` can be either a path to a file (a string) or a file-like object.
+        """
+        PackageWriter.write(pkg_file, self._rels, tuple(self.iter_parts()))
+
+    def _load(self) -> Self:
+        """Return the package after loading all parts and relationships."""
+        pkg_xml_rels, parts = _PackageLoader.load(self._pkg_file, cast("Package", self))
+        self._rels.load_from_xml(PACKAGE_URI, pkg_xml_rels, parts)
+        return self
+
+    @lazyproperty
+    def _rels(self) -> _Relationships:
+        """|Relationships| object containing relationships of this package."""
+        return _Relationships(PACKAGE_URI.baseURI)
+
+
+class _PackageLoader:
+    """Function-object that loads a package from disk (or other store)."""
+
+    def __init__(self, pkg_file: str | IO[bytes], package: Package):
+        self._pkg_file = pkg_file
+        self._package = package
+
+    @classmethod
+    def load(
+        cls, pkg_file: str | IO[bytes], package: Package
+    ) -> tuple[CT_Relationships, dict[PackURI, Part]]:
+        """Return (pkg_xml_rels, parts) pair resulting from loading `pkg_file`.
+
+        The returned `parts` value is a {partname: part} mapping with each part in the package
+        included and constructed complete with its relationships to other parts in the package.
+
+        The returned `pkg_xml_rels` value is a `CT_Relationships` object containing the parsed
+        package relationships. It is the caller's responsibility (the package object) to load
+        those relationships into its |_Relationships| object.
+        """
+        return cls(pkg_file, package)._load()
+
+    def _load(self) -> tuple[CT_Relationships, dict[PackURI, Part]]:
+        """Return (pkg_xml_rels, parts) pair resulting from loading pkg_file."""
+        parts, xml_rels = self._parts, self._xml_rels
+
+        for partname, part in parts.items():
+            part.load_rels_from_xml(xml_rels[partname], parts)
+
+        return xml_rels[PACKAGE_URI], parts
+
+    @lazyproperty
+    def _content_types(self) -> _ContentTypeMap:
+        """|_ContentTypeMap| object providing content-types for items of this package.
+
+        Provides a content-type (MIME-type) for any given partname.
+        """
+        return _ContentTypeMap.from_xml(self._package_reader[CONTENT_TYPES_URI])
+
+    @lazyproperty
+    def _package_reader(self) -> PackageReader:
+        """|PackageReader| object providing access to package-items in pkg_file."""
+        return PackageReader(self._pkg_file)
+
+    @lazyproperty
+    def _parts(self) -> dict[PackURI, Part]:
+        """dict {partname: Part} populated with parts loading from package.
+
+        Among other duties, this collection is passed to each relationships collection so each
+        relationship can resolve a reference to its target part when required. This reference can
+        only be reliably carried out once the all parts have been loaded.
+        """
+        content_types = self._content_types
+        package = self._package
+        package_reader = self._package_reader
+
+        return {
+            partname: PartFactory(
+                partname,
+                content_types[partname],
+                package,
+                blob=package_reader[partname],
+            )
+            for partname in (p for p in self._xml_rels if p != "/")
+            # -- invalid partnames can arise in some packages; ignore those rather than raise an
+            # -- exception.
+            if partname in package_reader
+        }
+
+    @lazyproperty
+    def _xml_rels(self) -> dict[PackURI, CT_Relationships]:
+        """dict {partname: xml_rels} for package and all package parts.
+
+        This is used as the basis for other loading operations such as loading parts and
+        populating their relationships.
+        """
+        xml_rels: dict[PackURI, CT_Relationships] = {}
+        visited_partnames: Set[PackURI] = set()
+
+        def load_rels(source_partname: PackURI, rels: CT_Relationships):
+            """Populate `xml_rels` dict by traversing relationships depth-first."""
+            xml_rels[source_partname] = rels
+            visited_partnames.add(source_partname)
+            base_uri = source_partname.baseURI
+
+            # --- recursion stops when there are no unvisited partnames in rels ---
+            for rel in rels.relationship_lst:
+                if rel.targetMode == RTM.EXTERNAL:
+                    continue
+                target_partname = PackURI.from_rel_ref(base_uri, rel.target_ref)
+                if target_partname in visited_partnames:
+                    continue
+                load_rels(target_partname, self._xml_rels_for(target_partname))
+
+        load_rels(PACKAGE_URI, self._xml_rels_for(PACKAGE_URI))
+        return xml_rels
+
+    def _xml_rels_for(self, partname: PackURI) -> CT_Relationships:
+        """Return CT_Relationships object formed by parsing rels XML for `partname`.
+
+        A CT_Relationships object is returned in all cases. A part that has no relationships
+        receives an "empty" CT_Relationships object, i.e. containing no `CT_Relationship` objects.
+        """
+        rels_xml = self._package_reader.rels_xml_for(partname)
+        return (
+            CT_Relationships.new()
+            if rels_xml is None
+            else cast(CT_Relationships, parse_xml(rels_xml))
+        )
+
+
+class Part(_RelatableMixin):
+    """Base class for package parts.
+
+    Provides common properties and methods, but intended to be subclassed in client code to
+    implement specific part behaviors. Also serves as the default class for parts that are not yet
+    given specific behaviors.
+    """
+
+    def __init__(
+        self, partname: PackURI, content_type: str, package: Package, blob: bytes | None = None
+    ):
+        # --- XmlPart subtypes, don't store a blob (the original XML) ---
+        self._partname = partname
+        self._content_type = content_type
+        self._package = package
+        self._blob = blob
+
+    @classmethod
+    def load(cls, partname: PackURI, content_type: str, package: Package, blob: bytes) -> Self:
+        """Return `cls` instance loaded from arguments.
+
+        This one is a straight pass-through, but subtypes may do some pre-processing, see XmlPart
+        for an example.
+        """
+        return cls(partname, content_type, package, blob)
+
+    @property
+    def blob(self) -> bytes:
+        """Contents of this package part as a sequence of bytes.
+
+        Intended to be overridden by subclasses. Default behavior is to return the blob initial
+        loaded during `Package.open()` operation.
+        """
+        return self._blob or b""
+
+    @blob.setter
+    def blob(self, blob: bytes):
+        """Note that not all subclasses use the part blob as their blob source.
+
+        In particular, the |XmlPart| subclass uses its `self._element` to serialize a blob on
+        demand. This works fine for binary parts though.
+        """
+        self._blob = blob
+
+    @lazyproperty
+    def content_type(self) -> str:
+        """Content-type (MIME-type) of this part."""
+        return self._content_type
+
+    def load_rels_from_xml(self, xml_rels: CT_Relationships, parts: dict[PackURI, Part]) -> None:
+        """load _Relationships for this part from `xml_rels`.
+
+        Part references are resolved using the `parts` dict that maps each partname to the loaded
+        part with that partname. These relationships are loaded from a serialized package and so
+        already have assigned rIds. This method is only used during package loading.
+        """
+        self._rels.load_from_xml(self._partname.baseURI, xml_rels, parts)
+
+    @lazyproperty
+    def package(self) -> Package:
+        """Package this part belongs to."""
+        return self._package
+
+    @property
+    def partname(self) -> PackURI:
+        """|PackURI| partname for this part, e.g. "/ppt/slides/slide1.xml"."""
+        return self._partname
+
+    @partname.setter
+    def partname(self, partname: PackURI):
+        if not isinstance(partname, PackURI):  # pyright: ignore[reportUnnecessaryIsInstance]
+            raise TypeError(  # pragma: no cover
+                "partname must be instance of PackURI, got '%s'" % type(partname).__name__
+            )
+        self._partname = partname
+
+    @lazyproperty
+    def rels(self) -> _Relationships:
+        """Collection of relationships from this part to other parts."""
+        # --- this must be public to allow the part graph to be traversed ---
+        return self._rels
+
+    def _blob_from_file(self, file: str | IO[bytes]) -> bytes:
+        """Return bytes of `file`, which is either a str path or a file-like object."""
+        # --- a str `file` is assumed to be a path ---
+        if isinstance(file, str):
+            with open(file, "rb") as f:
+                return f.read()
+
+        # --- otherwise, assume `file` is a file-like object
+        # --- reposition file cursor if it has one
+        if callable(getattr(file, "seek")):
+            file.seek(0)
+        return file.read()
+
+    @lazyproperty
+    def _rels(self) -> _Relationships:
+        """Relationships from this part to others."""
+        return _Relationships(self._partname.baseURI)
+
+
+class XmlPart(Part):
+    """Base class for package parts containing an XML payload, which is most of them.
+
+    Provides additional methods to the |Part| base class that take care of parsing and
+    reserializing the XML payload and managing relationships to other parts.
+    """
+
+    def __init__(
+        self, partname: PackURI, content_type: str, package: Package, element: BaseOxmlElement
+    ):
+        super(XmlPart, self).__init__(partname, content_type, package)
+        self._element = element
+
+    @classmethod
+    def load(cls, partname: PackURI, content_type: str, package: Package, blob: bytes):
+        """Return instance of `cls` loaded with parsed XML from `blob`."""
+        return cls(
+            partname, content_type, package, element=cast("BaseOxmlElement", parse_xml(blob))
+        )
+
+    @property
+    def blob(self) -> bytes:  # pyright: ignore[reportIncompatibleMethodOverride]
+        """bytes XML serialization of this part."""
+        return serialize_part_xml(self._element)
+
+    # -- XmlPart cannot set its blob, which is why pyright complains --
+
+    def drop_rel(self, rId: str) -> None:
+        """Remove relationship identified by `rId` if its reference count is under 2.
+
+        Relationships with a reference count of 0 are implicit relationships. Note that only XML
+        parts can drop relationships.
+        """
+        if self._rel_ref_count(rId) < 2:
+            self._rels.pop(rId)
+
+    @property
+    def part(self):
+        """This part.
+
+        This is part of the parent protocol, "children" of the document will not know the part
+        that contains them so must ask their parent object. That chain of delegation ends here for
+        child objects.
+        """
+        return self
+
+    def _rel_ref_count(self, rId: str) -> int:
+        """Return int count of references in this part's XML to `rId`."""
+        return len([r for r in cast("list[str]", self._element.xpath("//@r:id")) if r == rId])
+
+
+class PartFactory:
+    """Constructs a registered subtype of |Part|.
+
+    Client code can register a subclass of |Part| to be used for a package blob based on its
+    content type.
+    """
+
+    part_type_for: dict[str, type[Part]] = {}
+
+    def __new__(cls, partname: PackURI, content_type: str, package: Package, blob: bytes) -> Part:
+        PartClass = cls._part_cls_for(content_type)
+        return PartClass.load(partname, content_type, package, blob)
+
+    @classmethod
+    def _part_cls_for(cls, content_type: str) -> type[Part]:
+        """Return the custom part class registered for `content_type`.
+
+        Returns |Part| if no custom class is registered for `content_type`.
+        """
+        if content_type in cls.part_type_for:
+            return cls.part_type_for[content_type]
+        return Part
+
+
+class _ContentTypeMap:
+    """Value type providing dict semantics for looking up content type by partname."""
+
+    def __init__(self, overrides: dict[str, str], defaults: dict[str, str]):
+        self._overrides = overrides
+        self._defaults = defaults
+
+    def __getitem__(self, partname: PackURI) -> str:
+        """Return content-type (MIME-type) for part identified by *partname*."""
+        if not isinstance(partname, PackURI):  # pyright: ignore[reportUnnecessaryIsInstance]
+            raise TypeError(
+                "_ContentTypeMap key must be <type 'PackURI'>, got %s" % type(partname).__name__
+            )
+
+        if partname in self._overrides:
+            return self._overrides[partname]
+
+        if partname.ext in self._defaults:
+            return self._defaults[partname.ext]
+
+        raise KeyError("no content-type for partname '%s' in [Content_Types].xml" % partname)
+
+    @classmethod
+    def from_xml(cls, content_types_xml: bytes) -> _ContentTypeMap:
+        """Return |_ContentTypeMap| instance populated from `content_types_xml`."""
+        types_elm = cast("CT_Types", parse_xml(content_types_xml))
+        # -- note all partnames in [Content_Types].xml are absolute --
+        overrides = CaseInsensitiveDict(
+            (o.partName.lower(), o.contentType) for o in types_elm.override_lst
+        )
+        defaults = CaseInsensitiveDict(
+            (d.extension.lower(), d.contentType) for d in types_elm.default_lst
+        )
+        return cls(overrides, defaults)
+
+
+class _Relationships(Mapping[str, "_Relationship"]):
+    """Collection of |_Relationship| instances having `dict` semantics.
+
+    Relationships are keyed by their rId, but may also be found in other ways, such as by their
+    relationship type. |Relationship| objects are keyed by their rId.
+
+    Iterating this collection has normal mapping semantics, generating the keys (rIds) of the
+    mapping. `rels.keys()`, `rels.values()`, and `rels.items() can be used as they would be for a
+    `dict`.
+    """
+
+    def __init__(self, base_uri: str):
+        self._base_uri = base_uri
+
+    def __contains__(self, rId: object) -> bool:
+        """Implement 'in' operation, like `"rId7" in relationships`."""
+        return rId in self._rels
+
+    def __getitem__(self, rId: str) -> _Relationship:
+        """Implement relationship lookup by rId using indexed access, like rels[rId]."""
+        try:
+            return self._rels[rId]
+        except KeyError:
+            raise KeyError("no relationship with key '%s'" % rId)
+
+    def __iter__(self) -> Iterator[str]:
+        """Implement iteration of rIds (iterating a mapping produces its keys)."""
+        return iter(self._rels)
+
+    def __len__(self) -> int:
+        """Return count of relationships in collection."""
+        return len(self._rels)
+
+    def get_or_add(self, reltype: str, target_part: Part) -> str:
+        """Return str rId of `reltype` to `target_part`.
+
+        The rId of an existing matching relationship is used if present. Otherwise, a new
+        relationship is added and that rId is returned.
+        """
+        existing_rId = self._get_matching(reltype, target_part)
+        return (
+            self._add_relationship(reltype, target_part) if existing_rId is None else existing_rId
+        )
+
+    def get_or_add_ext_rel(self, reltype: str, target_ref: str) -> str:
+        """Return str rId of external relationship of `reltype` to `target_ref`.
+
+        The rId of an existing matching relationship is used if present. Otherwise, a new
+        relationship is added and that rId is returned.
+        """
+        existing_rId = self._get_matching(reltype, target_ref, is_external=True)
+        return (
+            self._add_relationship(reltype, target_ref, is_external=True)
+            if existing_rId is None
+            else existing_rId
+        )
+
+    def load_from_xml(
+        self, base_uri: str, xml_rels: CT_Relationships, parts: dict[PackURI, Part]
+    ) -> None:
+        """Replace any relationships in this collection with those from `xml_rels`."""
+
+        def iter_valid_rels():
+            """Filter out broken relationships such as those pointing to NULL."""
+            for rel_elm in xml_rels.relationship_lst:
+                # --- Occasionally a PowerPoint plugin or other client will "remove"
+                # --- a relationship simply by "voiding" its Target value, like making
+                # --- it "/ppt/slides/NULL". Skip any relationships linking to a
+                # --- partname that is not present in the package.
+                if rel_elm.targetMode == RTM.INTERNAL:
+                    partname = PackURI.from_rel_ref(base_uri, rel_elm.target_ref)
+                    if partname not in parts:
+                        continue
+                yield _Relationship.from_xml(base_uri, rel_elm, parts)
+
+        self._rels.clear()
+        self._rels.update((rel.rId, rel) for rel in iter_valid_rels())
+
+    def part_with_reltype(self, reltype: str) -> Part:
+        """Return target part of relationship with matching `reltype`.
+
+        Raises |KeyError| if not found and |ValueError| if more than one matching relationship is
+        found.
+        """
+        rels_of_reltype = self._rels_by_reltype[reltype]
+
+        if len(rels_of_reltype) == 0:
+            raise KeyError("no relationship of type '%s' in collection" % reltype)
+
+        if len(rels_of_reltype) > 1:
+            raise ValueError("multiple relationships of type '%s' in collection" % reltype)
+
+        return rels_of_reltype[0].target_part
+
+    def pop(self, rId: str) -> _Relationship:
+        """Return |_Relationship| identified by `rId` after removing it from collection.
+
+        The caller is responsible for ensuring it is no longer required.
+        """
+        return self._rels.pop(rId)
+
+    @property
+    def xml(self):
+        """bytes XML serialization of this relationship collection.
+
+        This value is suitable for storage as a .rels file in an OPC package. Includes a `<?xml..`
+        declaration header with encoding as UTF-8.
+        """
+        rels_elm = CT_Relationships.new()
+
+        # -- Sequence <Relationship> elements deterministically (in numerical order) to
+        # -- simplify testing and manual inspection.
+        def iter_rels_in_numerical_order():
+            sorted_num_rId_pairs = sorted(
+                (
+                    int(rId[3:]) if rId.startswith("rId") and rId[3:].isdigit() else 0,
+                    rId,
+                )
+                for rId in self.keys()
+            )
+            return (self[rId] for _, rId in sorted_num_rId_pairs)
+
+        for rel in iter_rels_in_numerical_order():
+            rels_elm.add_rel(rel.rId, rel.reltype, rel.target_ref, rel.is_external)
+
+        return rels_elm.xml_file_bytes
+
+    def _add_relationship(self, reltype: str, target: Part | str, is_external: bool = False) -> str:
+        """Return str rId of |_Relationship| newly added to spec."""
+        rId = self._next_rId
+        self._rels[rId] = _Relationship(
+            self._base_uri,
+            rId,
+            reltype,
+            target_mode=RTM.EXTERNAL if is_external else RTM.INTERNAL,
+            target=target,
+        )
+        return rId
+
+    def _get_matching(
+        self, reltype: str, target: Part | str, is_external: bool = False
+    ) -> str | None:
+        """Return optional str rId of rel of `reltype`, `target`, and `is_external`.
+
+        Returns `None` on no matching relationship
+        """
+        for rel in self._rels_by_reltype[reltype]:
+            if rel.is_external != is_external:
+                continue
+            rel_target = rel.target_ref if rel.is_external else rel.target_part
+            if rel_target == target:
+                return rel.rId
+
+        return None
+
+    @property
+    def _next_rId(self) -> str:
+        """Next str rId available in collection.
+
+        The next rId is the first unused key starting from "rId1" and making use of any gaps in
+        numbering, e.g. 'rId2' for rIds ['rId1', 'rId3'].
+        """
+        # --- The common case is where all sequential numbers starting at "rId1" are
+        # --- used and the next available rId is "rId%d" % (len(rels)+1). So we start
+        # --- there and count down to produce the best performance.
+        for n in range(len(self) + 1, 0, -1):
+            rId_candidate = "rId%d" % n  # like 'rId19'
+            if rId_candidate not in self._rels:
+                return rId_candidate
+        raise Exception(
+            "ProgrammingError: Impossible to have more distinct rIds than relationships"
+        )
+
+    @lazyproperty
+    def _rels(self) -> dict[str, _Relationship]:
+        """dict {rId: _Relationship} containing relationships of this collection."""
+        return {}
+
+    @property
+    def _rels_by_reltype(self) -> dict[str, list[_Relationship]]:
+        """defaultdict {reltype: [rels]} for all relationships in collection."""
+        D: DefaultDict[str, list[_Relationship]] = collections.defaultdict(list)
+        for rel in self.values():
+            D[rel.reltype].append(rel)
+        return D
+
+
+class _Relationship:
+    """Value object describing link from a part or package to another part."""
+
+    def __init__(self, base_uri: str, rId: str, reltype: str, target_mode: str, target: Part | str):
+        self._base_uri = base_uri
+        self._rId = rId
+        self._reltype = reltype
+        self._target_mode = target_mode
+        self._target = target
+
+    @classmethod
+    def from_xml(
+        cls, base_uri: str, rel: CT_Relationship, parts: dict[PackURI, Part]
+    ) -> _Relationship:
+        """Return |_Relationship| object based on CT_Relationship element `rel`."""
+        target = (
+            rel.target_ref
+            if rel.targetMode == RTM.EXTERNAL
+            else parts[PackURI.from_rel_ref(base_uri, rel.target_ref)]
+        )
+        return cls(base_uri, rel.rId, rel.reltype, rel.targetMode, target)
+
+    @lazyproperty
+    def is_external(self) -> bool:
+        """True if target_mode is `RTM.EXTERNAL`.
+
+        An external relationship is a link to a resource outside the package, such as a
+        web-resource (URL).
+        """
+        return self._target_mode == RTM.EXTERNAL
+
+    @lazyproperty
+    def reltype(self) -> str:
+        """Member of RELATIONSHIP_TYPE describing relationship of target to source."""
+        return self._reltype
+
+    @lazyproperty
+    def rId(self) -> str:
+        """str relationship-id, like 'rId9'.
+
+        Corresponds to the `Id` attribute on the `CT_Relationship` element and uniquely identifies
+        this relationship within its peers for the source-part or package.
+        """
+        return self._rId
+
+    @lazyproperty
+    def target_part(self) -> Part:
+        """|Part| or subtype referred to by this relationship."""
+        if self.is_external:
+            raise ValueError(
+                "`.target_part` property on _Relationship is undefined when "
+                "target-mode is external"
+            )
+        assert isinstance(self._target, Part)
+        return self._target
+
+    @lazyproperty
+    def target_partname(self) -> PackURI:
+        """|PackURI| instance containing partname targeted by this relationship.
+
+        Raises `ValueError` on reference if target_mode is external. Use :attr:`target_mode` to
+        check before referencing.
+        """
+        if self.is_external:
+            raise ValueError(
+                "`.target_partname` property on _Relationship is undefined when "
+                "target-mode is external"
+            )
+        assert isinstance(self._target, Part)
+        return self._target.partname
+
+    @lazyproperty
+    def target_ref(self) -> str:
+        """str reference to relationship target.
+
+        For internal relationships this is the relative partname, suitable for serialization
+        purposes. For an external relationship it is typically a URL.
+        """
+        if self.is_external:
+            assert isinstance(self._target, str)
+            return self._target
+
+        return self.target_partname.relative_ref(self._base_uri)
diff --git a/.venv/lib/python3.12/site-packages/pptx/opc/packuri.py b/.venv/lib/python3.12/site-packages/pptx/opc/packuri.py
new file mode 100644
index 00000000..74ddd333
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pptx/opc/packuri.py
@@ -0,0 +1,109 @@
+"""Provides the PackURI value type and known pack-URI strings such as PACKAGE_URI."""
+
+from __future__ import annotations
+
+import posixpath
+import re
+
+
+class PackURI(str):
+    """Proxy for a pack URI (partname).
+
+    Provides utility properties the baseURI and the filename slice. Behaves as |str| otherwise.
+    """
+
+    _filename_re = re.compile("([a-zA-Z]+)([0-9][0-9]*)?")
+
+    def __new__(cls, pack_uri_str: str):
+        if not pack_uri_str[0] == "/":
+            raise ValueError(f"PackURI must begin with slash, got {repr(pack_uri_str)}")
+        return str.__new__(cls, pack_uri_str)
+
+    @staticmethod
+    def from_rel_ref(baseURI: str, relative_ref: str) -> PackURI:
+        """Construct an absolute pack URI formed by translating `relative_ref` onto `baseURI`."""
+        joined_uri = posixpath.join(baseURI, relative_ref)
+        abs_uri = posixpath.abspath(joined_uri)
+        return PackURI(abs_uri)
+
+    @property
+    def baseURI(self) -> str:
+        """The base URI of this pack URI; the directory portion, roughly speaking.
+
+        E.g. `"/ppt/slides"` for `"/ppt/slides/slide1.xml"`.
+
+        For the package pseudo-partname "/", the baseURI is "/".
+        """
+        return posixpath.split(self)[0]
+
+    @property
+    def ext(self) -> str:
+        """The extension portion of this pack URI.
+
+        E.g. `"xml"` for `"/ppt/slides/slide1.xml"`. Note the leading period is not included.
+        """
+        # -- raw_ext is either empty string or starts with period, e.g. ".xml" --
+        raw_ext = posixpath.splitext(self)[1]
+        return raw_ext[1:] if raw_ext.startswith(".") else raw_ext
+
+    @property
+    def filename(self) -> str:
+        """The "filename" portion of this pack URI.
+
+        E.g. `"slide1.xml"` for `"/ppt/slides/slide1.xml"`.
+
+        For the package pseudo-partname "/", `filename` is ''.
+        """
+        return posixpath.split(self)[1]
+
+    @property
+    def idx(self) -> int | None:
+        """Optional int partname index.
+
+        Value is an integer for an "array" partname or None for singleton partname, e.g. `21` for
+        `"/ppt/slides/slide21.xml"` and |None| for `"/ppt/presentation.xml"`.
+        """
+        filename = self.filename
+        if not filename:
+            return None
+        name_part = posixpath.splitext(filename)[0]  # filename w/ext removed
+        match = self._filename_re.match(name_part)
+        if match is None:
+            return None
+        if match.group(2):
+            return int(match.group(2))
+        return None
+
+    @property
+    def membername(self) -> str:
+        """The pack URI with the leading slash stripped off.
+
+        This is the form used as the Zip file membername for the package item. Returns "" for the
+        package pseudo-partname "/".
+        """
+        return self[1:]
+
+    def relative_ref(self, baseURI: str) -> str:
+        """Return string containing relative reference to package item from `baseURI`.
+
+        E.g. PackURI("/ppt/slideLayouts/slideLayout1.xml") would return
+        "../slideLayouts/slideLayout1.xml" for baseURI "/ppt/slides".
+        """
+        # workaround for posixpath bug in 2.6, doesn't generate correct
+        # relative path when `start` (second) parameter is root ("/")
+        return self[1:] if baseURI == "/" else posixpath.relpath(self, baseURI)
+
+    @property
+    def rels_uri(self) -> PackURI:
+        """The pack URI of the .rels part corresponding to the current pack URI.
+
+        Only produces sensible output if the pack URI is a partname or the package pseudo-partname
+        "/".
+        """
+        rels_filename = "%s.rels" % self.filename
+        rels_uri_str = posixpath.join(self.baseURI, "_rels", rels_filename)
+        return PackURI(rels_uri_str)
+
+
+PACKAGE_URI = PackURI("/")
+CONTENT_TYPES_URI = PackURI("/[Content_Types].xml")
diff --git a/.venv/lib/python3.12/site-packages/pptx/opc/serialized.py b/.venv/lib/python3.12/site-packages/pptx/opc/serialized.py
new file mode 100644
index 00000000..92366708
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pptx/opc/serialized.py
@@ -0,0 +1,296 @@
+"""API for reading/writing serialized Open Packaging Convention (OPC) package."""
+
+from __future__ import annotations
+
+import os
+import posixpath
+import zipfile
+from typing import IO, TYPE_CHECKING, Any, Container, Sequence
+
+from pptx.exc import PackageNotFoundError
+from pptx.opc.constants import CONTENT_TYPE as CT
+from pptx.opc.oxml import CT_Types, serialize_part_xml
+from pptx.opc.packuri import CONTENT_TYPES_URI, PACKAGE_URI, PackURI
+from pptx.opc.shared import CaseInsensitiveDict
+from pptx.opc.spec import default_content_types
+from pptx.util import lazyproperty
+
+if TYPE_CHECKING:
+    from pptx.opc.package import Part, _Relationships  # pyright: ignore[reportPrivateUsage]
+
+
+class PackageReader(Container[bytes]):
+    """Provides access to package-parts of an OPC package with dict semantics.
+
+    The package may be in zip-format (a .pptx file) or expanded into a directory structure,
+    perhaps by unzipping a .pptx file.
+    """
+
+    def __init__(self, pkg_file: str | IO[bytes]):
+        self._pkg_file = pkg_file
+
+    def __contains__(self, pack_uri: object) -> bool:
+        """Return True when part identified by `pack_uri` is present in package."""
+        return pack_uri in self._blob_reader
+
+    def __getitem__(self, pack_uri: PackURI) -> bytes:
+        """Return bytes for part corresponding to `pack_uri`."""
+        return self._blob_reader[pack_uri]
+
+    def rels_xml_for(self, partname: PackURI) -> bytes | None:
+        """Return optional rels item XML for `partname`.
+
+        Returns `None` if no rels item is present for `partname`. `partname` is a |PackURI|
+        instance.
+        """
+        blob_reader, uri = self._blob_reader, partname.rels_uri
+        return blob_reader[uri] if uri in blob_reader else None
+
+    @lazyproperty
+    def _blob_reader(self) -> _PhysPkgReader:
+        """|_PhysPkgReader| subtype providing read access to the package file."""
+        return _PhysPkgReader.factory(self._pkg_file)
+
+
+class PackageWriter:
+    """Writes a zip-format OPC package to `pkg_file`.
+
+    `pkg_file` can be either a path to a zip file (a string) or a file-like object. `pkg_rels` is
+    the |_Relationships| object containing relationships for the package. `parts` is a sequence of
+    |Part| subtype instance to be written to the package.
+
+    Its single API classmethod is :meth:`write`. This class is not intended to be instantiated.
+    """
+
+    def __init__(self, pkg_file: str | IO[bytes], pkg_rels: _Relationships, parts: Sequence[Part]):
+        self._pkg_file = pkg_file
+        self._pkg_rels = pkg_rels
+        self._parts = parts
+
+    @classmethod
+    def write(
+        cls, pkg_file: str | IO[bytes], pkg_rels: _Relationships, parts: Sequence[Part]
+    ) -> None:
+        """Write a physical package (.pptx file) to `pkg_file`.
+
+        The serialized package contains `pkg_rels` and `parts`, a content-types stream based on
+        the content type of each part, and a .rels file for each part that has relationships.
+        """
+        cls(pkg_file, pkg_rels, parts)._write()
+
+    def _write(self) -> None:
+        """Write physical package (.pptx file)."""
+        with _PhysPkgWriter.factory(self._pkg_file) as phys_writer:
+            self._write_content_types_stream(phys_writer)
+            self._write_pkg_rels(phys_writer)
+            self._write_parts(phys_writer)
+
+    def _write_content_types_stream(self, phys_writer: _PhysPkgWriter) -> None:
+        """Write `[Content_Types].xml` part to the physical package.
+
+        This part must contain an appropriate content type lookup target for each part in the
+        package.
+        """
+        phys_writer.write(
+            CONTENT_TYPES_URI,
+            serialize_part_xml(_ContentTypesItem.xml_for(self._parts)),
+        )
+
+    def _write_parts(self, phys_writer: _PhysPkgWriter) -> None:
+        """Write blob of each part in `parts` to the package.
+
+        A rels item for each part is also written when the part has relationships.
+        """
+        for part in self._parts:
+            phys_writer.write(part.partname, part.blob)
+            if part._rels:  # pyright: ignore[reportPrivateUsage]
+                phys_writer.write(part.partname.rels_uri, part.rels.xml)
+
+    def _write_pkg_rels(self, phys_writer: _PhysPkgWriter) -> None:
+        """Write the XML rels item for `pkg_rels` ('/_rels/.rels') to the package."""
+        phys_writer.write(PACKAGE_URI.rels_uri, self._pkg_rels.xml)
+
+
+class _PhysPkgReader(Container[PackURI]):
+    """Base class for physical package reader objects."""
+
+    def __contains__(self, item: object) -> bool:
+        """Must be implemented by each subclass."""
+        raise NotImplementedError(  # pragma: no cover
+            "`%s` must implement `.__contains__()`" % type(self).__name__
+        )
+
+    def __getitem__(self, pack_uri: PackURI) -> bytes:
+        """Blob for part corresponding to `pack_uri`."""
+        raise NotImplementedError(  # pragma: no cover
+            f"`{type(self).__name__}` must implement `.__contains__()`"
+        )
+
+    @classmethod
+    def factory(cls, pkg_file: str | IO[bytes]) -> _PhysPkgReader:
+        """Return |_PhysPkgReader| subtype instance appropriage for `pkg_file`."""
+        # --- for pkg_file other than str, assume it's a stream and pass it to Zip
+        # --- reader to sort out
+        if not isinstance(pkg_file, str):
+            return _ZipPkgReader(pkg_file)
+
+        # --- otherwise we treat `pkg_file` as a path ---
+        if os.path.isdir(pkg_file):
+            return _DirPkgReader(pkg_file)
+
+        if zipfile.is_zipfile(pkg_file):
+            return _ZipPkgReader(pkg_file)
+
+        raise PackageNotFoundError("Package not found at '%s'" % pkg_file)
+
+
+class _DirPkgReader(_PhysPkgReader):
+    """Implements |PhysPkgReader| interface for OPC package extracted into directory.
+
+    `path` is the path to a directory containing an expanded package.
+    """
+
+    def __init__(self, path: str):
+        self._path = os.path.abspath(path)
+
+    def __contains__(self, pack_uri: object) -> bool:
+        """Return True when part identified by `pack_uri` is present in zip archive."""
+        if not isinstance(pack_uri, PackURI):
+            return False
+        return os.path.exists(posixpath.join(self._path, pack_uri.membername))
+
+    def __getitem__(self, pack_uri: PackURI) -> bytes:
+        """Return bytes of file corresponding to `pack_uri` in package directory."""
+        path = os.path.join(self._path, pack_uri.membername)
+        try:
+            with open(path, "rb") as f:
+                return f.read()
+        except IOError:
+            raise KeyError("no member '%s' in package" % pack_uri)
+
+
+class _ZipPkgReader(_PhysPkgReader):
+    """Implements |PhysPkgReader| interface for a zip-file OPC package."""
+
+    def __init__(self, pkg_file: str | IO[bytes]):
+        self._pkg_file = pkg_file
+
+    def __contains__(self, pack_uri: object) -> bool:
+        """Return True when part identified by `pack_uri` is present in zip archive."""
+        return pack_uri in self._blobs
+
+    def __getitem__(self, pack_uri: PackURI) -> bytes:
+        """Return bytes for part corresponding to `pack_uri`.
+
+        Raises |KeyError| if no matching member is present in zip archive.
+        """
+        if pack_uri not in self._blobs:
+            raise KeyError("no member '%s' in package" % pack_uri)
+        return self._blobs[pack_uri]
+
+    @lazyproperty
+    def _blobs(self) -> dict[PackURI, bytes]:
+        """dict mapping partname to package part binaries."""
+        with zipfile.ZipFile(self._pkg_file, "r") as z:
+            return {PackURI("/%s" % name): z.read(name) for name in z.namelist()}
+
+
+class _PhysPkgWriter:
+    """Base class for physical package writer objects."""
+
+    @classmethod
+    def factory(cls, pkg_file: str | IO[bytes]) -> _ZipPkgWriter:
+        """Return |_PhysPkgWriter| subtype instance appropriage for `pkg_file`.
+
+        Currently the only subtype is `_ZipPkgWriter`, but a `_DirPkgWriter` could be implemented
+        or even a `_StreamPkgWriter`.
+        """
+        return _ZipPkgWriter(pkg_file)
+
+    def write(self, pack_uri: PackURI, blob: bytes) -> None:
+        """Write `blob` to package with membername corresponding to `pack_uri`."""
+        raise NotImplementedError(  # pragma: no cover
+            f"`{type(self).__name__}` must implement `.write()`"
+        )
+
+
+class _ZipPkgWriter(_PhysPkgWriter):
+    """Implements |PhysPkgWriter| interface for a zip-file (.pptx file) OPC package."""
+
+    def __init__(self, pkg_file: str | IO[bytes]):
+        self._pkg_file = pkg_file
+
+    def __enter__(self) -> _ZipPkgWriter:
+        """Enable use as a context-manager. Opening zip for writing happens here."""
+        return self
+
+    def __exit__(self, *exc: list[Any]) -> None:
+        """Close the zip archive on exit from context.
+
+        Closing flushes any pending physical writes and releasing any resources it's using.
+        """
+        self._zipf.close()
+
+    def write(self, pack_uri: PackURI, blob: bytes) -> None:
+        """Write `blob` to zip package with membername corresponding to `pack_uri`."""
+        self._zipf.writestr(pack_uri.membername, blob)
+
+    @lazyproperty
+    def _zipf(self) -> zipfile.ZipFile:
+        """`ZipFile` instance open for writing."""
+        return zipfile.ZipFile(
+            self._pkg_file, "w", compression=zipfile.ZIP_DEFLATED, strict_timestamps=False
+        )
+
+
+class _ContentTypesItem:
+    """Composes content-types "part" ([Content_Types].xml) for a collection of parts."""
+
+    def __init__(self, parts: Sequence[Part]):
+        self._parts = parts
+
+    @classmethod
+    def xml_for(cls, parts: Sequence[Part]) -> CT_Types:
+        """Return content-types XML mapping each part in `parts` to a content-type.
+
+        The resulting XML is suitable for storage as `[Content_Types].xml` in an OPC package.
+        """
+        return cls(parts)._xml
+
+    @lazyproperty
+    def _xml(self) -> CT_Types:
+        """lxml.etree._Element containing the content-types item.
+
+        This XML object is suitable for serialization to the `[Content_Types].xml` item for an OPC
+        package. Although the sequence of elements is not strictly significant, as an aid to
+        testing and readability Default elements are sorted by extension and Override elements are
+        sorted by partname.
+        """
+        defaults, overrides = self._defaults_and_overrides
+        _types_elm = CT_Types.new()
+
+        for ext, content_type in sorted(defaults.items()):
+            _types_elm.add_default(ext, content_type)
+        for partname, content_type in sorted(overrides.items()):
+            _types_elm.add_override(partname, content_type)
+
+        return _types_elm
+
+    @lazyproperty
+    def _defaults_and_overrides(self) -> tuple[dict[str, str], dict[PackURI, str]]:
+        """pair of dict (defaults, overrides) accounting for all parts.
+
+        `defaults` is {ext: content_type} and overrides is {partname: content_type}.
+        """
+        defaults = CaseInsensitiveDict(rels=CT.OPC_RELATIONSHIPS, xml=CT.XML)
+        overrides: dict[PackURI, str] = {}
+
+        for part in self._parts:
+            partname, content_type = part.partname, part.content_type
+            ext = partname.ext
+            if (ext.lower(), content_type) in default_content_types:
+                defaults[ext] = content_type
+            else:
+                overrides[partname] = content_type
+
+        return defaults, overrides
diff --git a/.venv/lib/python3.12/site-packages/pptx/opc/shared.py b/.venv/lib/python3.12/site-packages/pptx/opc/shared.py
new file mode 100644
index 00000000..cc7fce8c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pptx/opc/shared.py
@@ -0,0 +1,20 @@
+"""Objects shared by modules in the pptx.opc sub-package."""
+
+from __future__ import annotations
+
+
+class CaseInsensitiveDict(dict):
+    """Mapping type like dict except it matches key without respect to case.
+
+    For example, D['A'] == D['a']. Note this is not general-purpose, just complete
+    enough to satisfy opc package needs. It assumes str keys for example.
+    """
+
+    def __contains__(self, key):
+        return super(CaseInsensitiveDict, self).__contains__(key.lower())
+
+    def __getitem__(self, key):
+        return super(CaseInsensitiveDict, self).__getitem__(key.lower())
+
+    def __setitem__(self, key, value):
+        return super(CaseInsensitiveDict, self).__setitem__(key.lower(), value)
diff --git a/.venv/lib/python3.12/site-packages/pptx/opc/spec.py b/.venv/lib/python3.12/site-packages/pptx/opc/spec.py
new file mode 100644
index 00000000..a83caf8b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pptx/opc/spec.py
@@ -0,0 +1,44 @@
+"""Provides mappings that embody aspects of the Open XML spec ISO/IEC 29500."""
+
+from pptx.opc.constants import CONTENT_TYPE as CT
+
+default_content_types = (
+    ("bin", CT.PML_PRINTER_SETTINGS),
+    ("bin", CT.SML_PRINTER_SETTINGS),
+    ("bin", CT.WML_PRINTER_SETTINGS),
+    ("bmp", CT.BMP),
+    ("emf", CT.X_EMF),
+    ("fntdata", CT.X_FONTDATA),
+    ("gif", CT.GIF),
+    ("jpe", CT.JPEG),
+    ("jpeg", CT.JPEG),
+    ("jpg", CT.JPEG),
+    ("mov", CT.MOV),
+    ("mp4", CT.MP4),
+    ("mpg", CT.MPG),
+    ("png", CT.PNG),
+    ("rels", CT.OPC_RELATIONSHIPS),
+    ("tif", CT.TIFF),
+    ("tiff", CT.TIFF),
+    ("vid", CT.VIDEO),
+    ("wdp", CT.MS_PHOTO),
+    ("wmf", CT.X_WMF),
+    ("wmv", CT.WMV),
+    ("xlsx", CT.SML_SHEET),
+    ("xml", CT.XML),
+)
+
+
+image_content_types = {
+    "bmp": CT.BMP,
+    "emf": CT.X_EMF,
+    "gif": CT.GIF,
+    "jpe": CT.JPEG,
+    "jpeg": CT.JPEG,
+    "jpg": CT.JPEG,
+    "png": CT.PNG,
+    "tif": CT.TIFF,
+    "tiff": CT.TIFF,
+    "wdp": CT.MS_PHOTO,
+    "wmf": CT.X_WMF,
+}