Skip to content

Commit b314f59

Browse files
author
Steve Canny
committed
opc: introduce XmlPart subclass
Takes care of all the common responsibilities of XML-content parts, like parsing and reserializing the XML.
1 parent 51c79c6 commit b314f59

8 files changed

Lines changed: 379 additions & 582 deletions

File tree

docx/opc/package.py

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from .compat import cls_method_fn
1111
from .constants import RELATIONSHIP_TYPE as RT
1212
from .oxml import CT_Relationships, serialize_part_xml
13+
from ..oxml import parse_xml
1314
from .packuri import PACKAGE_URI, PackURI
1415
from .pkgreader import PackageReader
1516
from .pkgwriter import PackageWriter
@@ -146,7 +147,6 @@ def save(self, pkg_file):
146147
Save this package to *pkg_file*, where *file* can be either a path to
147148
a file (a string) or a file-like object.
148149
"""
149-
# self._notify_before_marshal()
150150
for part in self.parts:
151151
part.before_marshal()
152152
PackageWriter.write(pkg_file, self.rels, self.parts)
@@ -158,18 +158,13 @@ class Part(object):
158158
intended to be subclassed in client code to implement specific part
159159
behaviors.
160160
"""
161-
def __init__(
162-
self, partname, content_type, blob=None, element=None,
163-
package=None):
161+
def __init__(self, partname, content_type, blob=None, package=None):
164162
super(Part, self).__init__()
165163
self._partname = partname
166164
self._content_type = content_type
167165
self._blob = blob
168-
self._element = element
169166
self._package = package
170167

171-
# load/save interface to OpcPackage ------------------------------
172-
173168
def after_unmarshal(self):
174169
"""
175170
Entry point for post-unmarshaling processing, for example to parse
@@ -197,8 +192,6 @@ def blob(self):
197192
binary. Intended to be overridden by subclasses. Default behavior is
198193
to return load blob.
199194
"""
200-
if self._element is not None:
201-
return serialize_part_xml(self._element)
202195
return self._blob
203196

204197
@property
@@ -208,18 +201,25 @@ def content_type(self):
208201
"""
209202
return self._content_type
210203

204+
def drop_rel(self, rId):
205+
"""
206+
Remove the relationship identified by *rId* if its reference count
207+
is less than 2. Relationships with a reference count of 0 are
208+
implicit relationships.
209+
"""
210+
if self._rel_ref_count(rId) < 2:
211+
del self.rels[rId]
212+
211213
@classmethod
212214
def load(cls, partname, content_type, blob, package):
213-
return cls(
214-
partname, content_type, blob=blob, element=None, package=package
215-
)
215+
return cls(partname, content_type, blob, package)
216216

217217
def load_rel(self, reltype, target, rId, is_external=False):
218218
"""
219219
Return newly added |_Relationship| instance of *reltype* between this
220220
part and *target* with key *rId*. Target mode is set to
221221
``RTM.EXTERNAL`` if *is_external* is |True|. Intended for use during
222-
load from a serialized package, where the rId is well known. Other
222+
load from a serialized package, where the rId is well-known. Other
223223
methods exist for adding a new relationship to a part when
224224
manipulating a part.
225225
"""
@@ -240,16 +240,12 @@ def partname(self, partname):
240240
raise TypeError(tmpl % type(partname).__name__)
241241
self._partname = partname
242242

243-
# relationship management interface for child objects ------------
244-
245-
def drop_rel(self, rId):
243+
@property
244+
def package(self):
246245
"""
247-
Remove the relationship identified by *rId* if its reference count
248-
is less than 2. Relationships with a reference count of 0 are
249-
implicit relationships.
246+
|OpcPackage| instance this part belongs to.
250247
"""
251-
if self._rel_ref_count(rId) < 2:
252-
del self.rels[rId]
248+
return self._package
253249

254250
def part_related_by(self, reltype):
255251
"""
@@ -300,18 +296,40 @@ def _rel_ref_count(self, rId):
300296
Return the count of references in this part's XML to the relationship
301297
identified by *rId*.
302298
"""
303-
assert self._element is not None
304299
rIds = self._element.xpath('//@r:id')
305300
return len([_rId for _rId in rIds if _rId == rId])
306301

307-
# ----------------------------------------------------------------
302+
303+
class XmlPart(Part):
304+
"""
305+
Base class for package parts containing an XML payload, which is most of
306+
them. Provides additional methods to the |Part| base class that take care
307+
of parsing and reserializing the XML payload and managing relationships
308+
to other parts.
309+
"""
310+
def __init__(self, partname, content_type, element, package):
311+
super(XmlPart, self).__init__(
312+
partname, content_type, package=package
313+
)
314+
self._element = element
308315

309316
@property
310-
def package(self):
317+
def blob(self):
318+
return serialize_part_xml(self._element)
319+
320+
@classmethod
321+
def load(cls, partname, content_type, blob, package):
322+
element = parse_xml(blob)
323+
return cls(partname, content_type, element, package)
324+
325+
@property
326+
def part(self):
311327
"""
312-
|OpcPackage| instance this part belongs to.
328+
Part of the parent protocol, "children" of the document will not know
329+
the part that contains them so must ask their parent object. That
330+
chain of delegation ends here for child objects.
313331
"""
314-
return self._package
332+
return self
315333

316334

317335
class PartFactory(object):

docx/parts/document.py

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,18 @@
1212

1313
from ..enum.section import WD_SECTION
1414
from ..opc.constants import RELATIONSHIP_TYPE as RT
15-
from ..opc.oxml import serialize_part_xml
16-
from ..opc.package import Part
17-
from ..oxml import parse_xml
15+
from ..opc.package import XmlPart
1816
from ..section import Section
1917
from ..shape import InlineShape
2018
from ..shared import lazyproperty, Parented
2119
from ..table import Table
2220
from ..text import Paragraph
2321

2422

25-
class DocumentPart(Part):
23+
class DocumentPart(XmlPart):
2624
"""
2725
Main document part of a WordprocessingML (WML) package, aka a .docx file.
2826
"""
29-
def __init__(self, partname, content_type, document_elm, package):
30-
super(DocumentPart, self).__init__(
31-
partname, content_type, package=package
32-
)
33-
self._element = document_elm
34-
3527
def add_paragraph(self):
3628
"""
3729
Return a paragraph newly added to the end of body content.
@@ -54,10 +46,6 @@ def add_table(self, rows, cols):
5446
"""
5547
return self.body.add_table(rows, cols)
5648

57-
@property
58-
def blob(self):
59-
return serialize_part_xml(self._element)
60-
6149
@lazyproperty
6250
def body(self):
6351
"""
@@ -86,12 +74,6 @@ def inline_shapes(self):
8674
"""
8775
return InlineShapes(self._element.body, self)
8876

89-
@classmethod
90-
def load(cls, partname, content_type, blob, package):
91-
document_elm = parse_xml(blob)
92-
document_part = cls(partname, content_type, document_elm, package)
93-
return document_part
94-
9577
@property
9678
def next_id(self):
9779
"""
@@ -114,15 +96,6 @@ def paragraphs(self):
11496
"""
11597
return self.body.paragraphs
11698

117-
@property
118-
def part(self):
119-
"""
120-
Part of the parent protocol, "children" of the document will not know
121-
the part that contains them so must ask their parent object. That
122-
chain of delegation ends here for document child objects.
123-
"""
124-
return self
125-
12699
@lazyproperty
127100
def sections(self):
128101
"""

docx/parts/numbering.py

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,15 @@
88
absolute_import, division, print_function, unicode_literals
99
)
1010

11-
from ..opc.package import Part
12-
from ..oxml import parse_xml
11+
from ..opc.package import XmlPart
1312
from ..shared import lazyproperty
1413

1514

16-
class NumberingPart(Part):
15+
class NumberingPart(XmlPart):
1716
"""
1817
Proxy for the numbering.xml part containing numbering definitions for
1918
a document or glossary.
2019
"""
21-
def __init__(self, partname, content_type, element, package):
22-
super(NumberingPart, self).__init__(
23-
partname, content_type, element=element, package=package
24-
)
25-
26-
@classmethod
27-
def load(cls, partname, content_type, blob, package):
28-
"""
29-
Provides PartFactory interface for loading a numbering part from
30-
a WML package.
31-
"""
32-
numbering_elm = parse_xml(blob)
33-
numbering_part = cls(partname, content_type, numbering_elm, package)
34-
return numbering_part
35-
3620
@classmethod
3721
def new(cls):
3822
"""

docx/parts/styles.py

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,15 @@
88
absolute_import, division, print_function, unicode_literals
99
)
1010

11-
from ..opc.package import Part
12-
from ..oxml import parse_xml
11+
from ..opc.package import XmlPart
1312
from ..shared import lazyproperty
1413

1514

16-
class StylesPart(Part):
15+
class StylesPart(XmlPart):
1716
"""
1817
Proxy for the styles.xml part containing style definitions for a document
1918
or glossary.
2019
"""
21-
def __init__(self, partname, content_type, element, package):
22-
super(StylesPart, self).__init__(
23-
partname, content_type, element=element, package=package
24-
)
25-
26-
@classmethod
27-
def load(cls, partname, content_type, blob, package):
28-
"""
29-
Provides PartFactory interface for loading a styles part from a WML
30-
package.
31-
"""
32-
styles_elm = parse_xml(blob)
33-
styles_part = cls(partname, content_type, styles_elm, package)
34-
return styles_part
35-
3620
@classmethod
3721
def new(cls):
3822
"""

0 commit comments

Comments
 (0)