Skip to content

Commit 5f820f8

Browse files
author
Steve Canny
committed
rfctr: extract opc.part module from opc.package
1 parent 734ce6f commit 5f820f8

14 files changed

Lines changed: 769 additions & 736 deletions

File tree

docx/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# register custom Part classes with opc package reader
99

1010
from docx.opc.constants import CONTENT_TYPE as CT, RELATIONSHIP_TYPE as RT
11-
from docx.opc.package import PartFactory
11+
from docx.opc.part import PartFactory
1212

1313
from docx.parts.document import DocumentPart
1414
from docx.parts.image import ImagePart

docx/opc/package.py

Lines changed: 2 additions & 222 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@
77

88
from __future__ import absolute_import, print_function, unicode_literals
99

10-
from .compat import cls_method_fn
1110
from .constants import RELATIONSHIP_TYPE as RT
12-
from .oxml import serialize_part_xml
13-
from ..oxml import parse_xml
14-
from .packuri import PACKAGE_URI, PackURI
11+
from .packuri import PACKAGE_URI
12+
from .part import PartFactory
1513
from .pkgreader import PackageReader
1614
from .pkgwriter import PackageWriter
1715
from .rel import Relationships
@@ -169,224 +167,6 @@ def _core_properties_part(self):
169167
raise NotImplementedError
170168

171169

172-
class Part(object):
173-
"""
174-
Base class for package parts. Provides common properties and methods, but
175-
intended to be subclassed in client code to implement specific part
176-
behaviors.
177-
"""
178-
def __init__(self, partname, content_type, blob=None, package=None):
179-
super(Part, self).__init__()
180-
self._partname = partname
181-
self._content_type = content_type
182-
self._blob = blob
183-
self._package = package
184-
185-
def after_unmarshal(self):
186-
"""
187-
Entry point for post-unmarshaling processing, for example to parse
188-
the part XML. May be overridden by subclasses without forwarding call
189-
to super.
190-
"""
191-
# don't place any code here, just catch call if not overridden by
192-
# subclass
193-
pass
194-
195-
def before_marshal(self):
196-
"""
197-
Entry point for pre-serialization processing, for example to finalize
198-
part naming if necessary. May be overridden by subclasses without
199-
forwarding call to super.
200-
"""
201-
# don't place any code here, just catch call if not overridden by
202-
# subclass
203-
pass
204-
205-
@property
206-
def blob(self):
207-
"""
208-
Contents of this package part as a sequence of bytes. May be text or
209-
binary. Intended to be overridden by subclasses. Default behavior is
210-
to return load blob.
211-
"""
212-
return self._blob
213-
214-
@property
215-
def content_type(self):
216-
"""
217-
Content type of this part.
218-
"""
219-
return self._content_type
220-
221-
def drop_rel(self, rId):
222-
"""
223-
Remove the relationship identified by *rId* if its reference count
224-
is less than 2. Relationships with a reference count of 0 are
225-
implicit relationships.
226-
"""
227-
if self._rel_ref_count(rId) < 2:
228-
del self.rels[rId]
229-
230-
@classmethod
231-
def load(cls, partname, content_type, blob, package):
232-
return cls(partname, content_type, blob, package)
233-
234-
def load_rel(self, reltype, target, rId, is_external=False):
235-
"""
236-
Return newly added |_Relationship| instance of *reltype* between this
237-
part and *target* with key *rId*. Target mode is set to
238-
``RTM.EXTERNAL`` if *is_external* is |True|. Intended for use during
239-
load from a serialized package, where the rId is well-known. Other
240-
methods exist for adding a new relationship to a part when
241-
manipulating a part.
242-
"""
243-
return self.rels.add_relationship(reltype, target, rId, is_external)
244-
245-
@property
246-
def partname(self):
247-
"""
248-
|PackURI| instance holding partname of this part, e.g.
249-
'/ppt/slides/slide1.xml'
250-
"""
251-
return self._partname
252-
253-
@partname.setter
254-
def partname(self, partname):
255-
if not isinstance(partname, PackURI):
256-
tmpl = "partname must be instance of PackURI, got '%s'"
257-
raise TypeError(tmpl % type(partname).__name__)
258-
self._partname = partname
259-
260-
@property
261-
def package(self):
262-
"""
263-
|OpcPackage| instance this part belongs to.
264-
"""
265-
return self._package
266-
267-
def part_related_by(self, reltype):
268-
"""
269-
Return part to which this part has a relationship of *reltype*.
270-
Raises |KeyError| if no such relationship is found and |ValueError|
271-
if more than one such relationship is found. Provides ability to
272-
resolve implicitly related part, such as Slide -> SlideLayout.
273-
"""
274-
return self.rels.part_with_reltype(reltype)
275-
276-
def relate_to(self, target, reltype, is_external=False):
277-
"""
278-
Return rId key of relationship of *reltype* to *target*, from an
279-
existing relationship if there is one, otherwise a newly created one.
280-
"""
281-
if is_external:
282-
return self.rels.get_or_add_ext_rel(reltype, target)
283-
else:
284-
rel = self.rels.get_or_add(reltype, target)
285-
return rel.rId
286-
287-
@property
288-
def related_parts(self):
289-
"""
290-
Dictionary mapping related parts by rId, so child objects can resolve
291-
explicit relationships present in the part XML, e.g. sldIdLst to a
292-
specific |Slide| instance.
293-
"""
294-
return self.rels.related_parts
295-
296-
@lazyproperty
297-
def rels(self):
298-
"""
299-
|Relationships| instance holding the relationships for this part.
300-
"""
301-
return Relationships(self._partname.baseURI)
302-
303-
def target_ref(self, rId):
304-
"""
305-
Return URL contained in target ref of relationship identified by
306-
*rId*.
307-
"""
308-
rel = self.rels[rId]
309-
return rel.target_ref
310-
311-
def _rel_ref_count(self, rId):
312-
"""
313-
Return the count of references in this part's XML to the relationship
314-
identified by *rId*.
315-
"""
316-
rIds = self._element.xpath('//@r:id')
317-
return len([_rId for _rId in rIds if _rId == rId])
318-
319-
320-
class XmlPart(Part):
321-
"""
322-
Base class for package parts containing an XML payload, which is most of
323-
them. Provides additional methods to the |Part| base class that take care
324-
of parsing and reserializing the XML payload and managing relationships
325-
to other parts.
326-
"""
327-
def __init__(self, partname, content_type, element, package):
328-
super(XmlPart, self).__init__(
329-
partname, content_type, package=package
330-
)
331-
self._element = element
332-
333-
@property
334-
def blob(self):
335-
return serialize_part_xml(self._element)
336-
337-
@classmethod
338-
def load(cls, partname, content_type, blob, package):
339-
element = parse_xml(blob)
340-
return cls(partname, content_type, element, package)
341-
342-
@property
343-
def part(self):
344-
"""
345-
Part of the parent protocol, "children" of the document will not know
346-
the part that contains them so must ask their parent object. That
347-
chain of delegation ends here for child objects.
348-
"""
349-
return self
350-
351-
352-
class PartFactory(object):
353-
"""
354-
Provides a way for client code to specify a subclass of |Part| to be
355-
constructed by |Unmarshaller| based on its content type and/or a custom
356-
callable. Setting ``PartFactory.part_class_selector`` to a callable
357-
object will cause that object to be called with the parameters
358-
``content_type, reltype``, once for each part in the package. If the
359-
callable returns an object, it is used as the class for that part. If it
360-
returns |None|, part class selection falls back to the content type map
361-
defined in ``PartFactory.part_type_for``. If no class is returned from
362-
either of these, the class contained in ``PartFactory.default_part_type``
363-
is used to construct the part, which is by default ``opc.package.Part``.
364-
"""
365-
part_class_selector = None
366-
part_type_for = {}
367-
default_part_type = Part
368-
369-
def __new__(cls, partname, content_type, reltype, blob, package):
370-
PartClass = None
371-
if cls.part_class_selector is not None:
372-
part_class_selector = cls_method_fn(cls, 'part_class_selector')
373-
PartClass = part_class_selector(content_type, reltype)
374-
if PartClass is None:
375-
PartClass = cls._part_cls_for(content_type)
376-
return PartClass.load(partname, content_type, blob, package)
377-
378-
@classmethod
379-
def _part_cls_for(cls, content_type):
380-
"""
381-
Return the custom part class registered for *content_type*, or the
382-
default part class if no custom class is registered for
383-
*content_type*.
384-
"""
385-
if content_type in cls.part_type_for:
386-
return cls.part_type_for[content_type]
387-
return cls.default_part_type
388-
389-
390170
class Unmarshaller(object):
391171
"""
392172
Hosts static methods for unmarshalling a package from a |PackageReader|

0 commit comments

Comments
 (0)