File: pkgwriter.py

package info (click to toggle)
python-docx 0.8.11%2Bdfsg1-5
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 6,640 kB
  • sloc: xml: 25,311; python: 21,911; makefile: 168
file content (125 lines) | stat: -rw-r--r-- 4,524 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# encoding: utf-8

"""
Provides a low-level, write-only API to a serialized Open Packaging
Convention (OPC) package, essentially an implementation of OpcPackage.save()
"""

from __future__ import absolute_import

from .constants import CONTENT_TYPE as CT
from .oxml import CT_Types, serialize_part_xml
from .packuri import CONTENT_TYPES_URI, PACKAGE_URI
from .phys_pkg import PhysPkgWriter
from .shared import CaseInsensitiveDict
from .spec import default_content_types


class PackageWriter(object):
    """
    Writes a zip-format OPC package to *pkg_file*, where *pkg_file* can be
    either a path to a zip file (a string) or a file-like object. Its single
    API method, :meth:`write`, is static, so this class is not intended to
    be instantiated.
    """
    @staticmethod
    def write(pkg_file, pkg_rels, parts):
        """
        Write a physical package (.pptx file) to *pkg_file* containing
        *pkg_rels* and *parts* and a content types stream based on the
        content types of the parts.
        """
        phys_writer = PhysPkgWriter(pkg_file)
        PackageWriter._write_content_types_stream(phys_writer, parts)
        PackageWriter._write_pkg_rels(phys_writer, pkg_rels)
        PackageWriter._write_parts(phys_writer, parts)
        phys_writer.close()

    @staticmethod
    def _write_content_types_stream(phys_writer, parts):
        """
        Write ``[Content_Types].xml`` part to the physical package with an
        appropriate content type lookup target for each part in *parts*.
        """
        cti = _ContentTypesItem.from_parts(parts)
        phys_writer.write(CONTENT_TYPES_URI, cti.blob)

    @staticmethod
    def _write_parts(phys_writer, parts):
        """
        Write the blob of each part in *parts* to the package, along with a
        rels item for its relationships if and only if it has any.
        """
        for part in parts:
            phys_writer.write(part.partname, part.blob)
            if len(part._rels):
                phys_writer.write(part.partname.rels_uri, part._rels.xml)

    @staticmethod
    def _write_pkg_rels(phys_writer, pkg_rels):
        """
        Write the XML rels item for *pkg_rels* ('/_rels/.rels') to the
        package.
        """
        phys_writer.write(PACKAGE_URI.rels_uri, pkg_rels.xml)


class _ContentTypesItem(object):
    """
    Service class that composes a content types item ([Content_Types].xml)
    based on a list of parts. Not meant to be instantiated directly, its
    single interface method is xml_for(), e.g.
    ``_ContentTypesItem.xml_for(parts)``.
    """
    def __init__(self):
        self._defaults = CaseInsensitiveDict()
        self._overrides = dict()

    @property
    def blob(self):
        """
        Return XML form of this content types item, suitable for storage as
        ``[Content_Types].xml`` in an OPC package.
        """
        return serialize_part_xml(self._element)

    @classmethod
    def from_parts(cls, parts):
        """
        Return content types XML mapping each part in *parts* to the
        appropriate content type and suitable for storage as
        ``[Content_Types].xml`` in an OPC package.
        """
        cti = cls()
        cti._defaults['rels'] = CT.OPC_RELATIONSHIPS
        cti._defaults['xml'] = CT.XML
        for part in parts:
            cti._add_content_type(part.partname, part.content_type)
        return cti

    def _add_content_type(self, partname, content_type):
        """
        Add a content type for the part with *partname* and *content_type*,
        using a default or override as appropriate.
        """
        ext = partname.ext
        if (ext.lower(), content_type) in default_content_types:
            self._defaults[ext] = content_type
        else:
            self._overrides[partname] = content_type

    @property
    def _element(self):
        """
        Return XML form of this content types item, suitable for storage as
        ``[Content_Types].xml`` in 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.
        """
        _types_elm = CT_Types.new()
        for ext in sorted(self._defaults.keys()):
            _types_elm.add_default(ext, self._defaults[ext])
        for partname in sorted(self._overrides.keys()):
            _types_elm.add_override(partname, self._overrides[partname])
        return _types_elm