Skip to content

Commit 1d94982

Browse files
author
Steve Canny
committed
rfctr: extract opc.rel module from opc.package
1 parent da0587c commit 1d94982

5 files changed

Lines changed: 358 additions & 452 deletions

File tree

docx/opc/package.py

Lines changed: 2 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@
99

1010
from .compat import cls_method_fn
1111
from .constants import RELATIONSHIP_TYPE as RT
12-
from .oxml import CT_Relationships, serialize_part_xml
12+
from .oxml import serialize_part_xml
1313
from ..oxml import parse_xml
1414
from .packuri import PACKAGE_URI, PackURI
1515
from .pkgreader import PackageReader
1616
from .pkgwriter import PackageWriter
17+
from .rel import Relationships
1718
from .shared import lazyproperty
1819

1920

@@ -386,126 +387,6 @@ def _part_cls_for(cls, content_type):
386387
return cls.default_part_type
387388

388389

389-
class Relationships(dict):
390-
"""
391-
Collection object for |_Relationship| instances, having list semantics.
392-
"""
393-
def __init__(self, baseURI):
394-
super(Relationships, self).__init__()
395-
self._baseURI = baseURI
396-
self._target_parts_by_rId = {}
397-
398-
def add_relationship(self, reltype, target, rId, is_external=False):
399-
"""
400-
Return a newly added |_Relationship| instance.
401-
"""
402-
rel = _Relationship(rId, reltype, target, self._baseURI, is_external)
403-
self[rId] = rel
404-
if not is_external:
405-
self._target_parts_by_rId[rId] = target
406-
return rel
407-
408-
def get_or_add(self, reltype, target_part):
409-
"""
410-
Return relationship of *reltype* to *target_part*, newly added if not
411-
already present in collection.
412-
"""
413-
rel = self._get_matching(reltype, target_part)
414-
if rel is None:
415-
rId = self._next_rId
416-
rel = self.add_relationship(reltype, target_part, rId)
417-
return rel
418-
419-
def get_or_add_ext_rel(self, reltype, target_ref):
420-
"""
421-
Return rId of external relationship of *reltype* to *target_ref*,
422-
newly added if not already present in collection.
423-
"""
424-
rel = self._get_matching(reltype, target_ref, is_external=True)
425-
if rel is None:
426-
rId = self._next_rId
427-
rel = self.add_relationship(
428-
reltype, target_ref, rId, is_external=True
429-
)
430-
return rel.rId
431-
432-
def part_with_reltype(self, reltype):
433-
"""
434-
Return target part of rel with matching *reltype*, raising |KeyError|
435-
if not found and |ValueError| if more than one matching relationship
436-
is found.
437-
"""
438-
rel = self._get_rel_of_type(reltype)
439-
return rel.target_part
440-
441-
@property
442-
def related_parts(self):
443-
"""
444-
dict mapping rIds to target parts for all the internal relationships
445-
in the collection.
446-
"""
447-
return self._target_parts_by_rId
448-
449-
@property
450-
def xml(self):
451-
"""
452-
Serialize this relationship collection into XML suitable for storage
453-
as a .rels file in an OPC package.
454-
"""
455-
rels_elm = CT_Relationships.new()
456-
for rel in self.values():
457-
rels_elm.add_rel(
458-
rel.rId, rel.reltype, rel.target_ref, rel.is_external
459-
)
460-
return rels_elm.xml
461-
462-
def _get_matching(self, reltype, target, is_external=False):
463-
"""
464-
Return relationship of matching *reltype*, *target*, and
465-
*is_external* from collection, or None if not found.
466-
"""
467-
def matches(rel, reltype, target, is_external):
468-
if rel.reltype != reltype:
469-
return False
470-
if rel.is_external != is_external:
471-
return False
472-
rel_target = rel.target_ref if rel.is_external else rel.target_part
473-
if rel_target != target:
474-
return False
475-
return True
476-
477-
for rel in self.values():
478-
if matches(rel, reltype, target, is_external):
479-
return rel
480-
return None
481-
482-
def _get_rel_of_type(self, reltype):
483-
"""
484-
Return single relationship of type *reltype* from the collection.
485-
Raises |KeyError| if no matching relationship is found. Raises
486-
|ValueError| if more than one matching relationship is found.
487-
"""
488-
matching = [rel for rel in self.values() if rel.reltype == reltype]
489-
if len(matching) == 0:
490-
tmpl = "no relationship of type '%s' in collection"
491-
raise KeyError(tmpl % reltype)
492-
if len(matching) > 1:
493-
tmpl = "multiple relationships of type '%s' in collection"
494-
raise ValueError(tmpl % reltype)
495-
return matching[0]
496-
497-
@property
498-
def _next_rId(self):
499-
"""
500-
Next available rId in collection, starting from 'rId1' and making use
501-
of any gaps in numbering, e.g. 'rId2' for rIds ['rId1', 'rId3'].
502-
"""
503-
for n in range(1, len(self)+2):
504-
rId_candidate = 'rId%d' % n # like 'rId19'
505-
if rId_candidate not in self:
506-
return rId_candidate
507-
508-
509390
class Unmarshaller(object):
510391
"""
511392
Hosts static methods for unmarshalling a package from a |PackageReader|
@@ -552,42 +433,3 @@ def _unmarshal_relationships(pkg_reader, package, parts):
552433
target = (srel.target_ref if srel.is_external
553434
else parts[srel.target_partname])
554435
source.load_rel(srel.reltype, target, srel.rId, srel.is_external)
555-
556-
557-
class _Relationship(object):
558-
"""
559-
Value object for relationship to part.
560-
"""
561-
def __init__(self, rId, reltype, target, baseURI, external=False):
562-
super(_Relationship, self).__init__()
563-
self._rId = rId
564-
self._reltype = reltype
565-
self._target = target
566-
self._baseURI = baseURI
567-
self._is_external = bool(external)
568-
569-
@property
570-
def is_external(self):
571-
return self._is_external
572-
573-
@property
574-
def reltype(self):
575-
return self._reltype
576-
577-
@property
578-
def rId(self):
579-
return self._rId
580-
581-
@property
582-
def target_part(self):
583-
if self._is_external:
584-
raise ValueError("target_part property on _Relationship is undef"
585-
"ined when target mode is External")
586-
return self._target
587-
588-
@property
589-
def target_ref(self):
590-
if self._is_external:
591-
return self._target
592-
else:
593-
return self._target.partname.relative_ref(self._baseURI)

docx/opc/rel.py

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# encoding: utf-8
2+
3+
"""
4+
Relationship-related objects.
5+
"""
6+
7+
from __future__ import (
8+
absolute_import, division, print_function, unicode_literals
9+
)
10+
11+
from .oxml import CT_Relationships
12+
13+
14+
class Relationships(dict):
15+
"""
16+
Collection object for |_Relationship| instances, having list semantics.
17+
"""
18+
def __init__(self, baseURI):
19+
super(Relationships, self).__init__()
20+
self._baseURI = baseURI
21+
self._target_parts_by_rId = {}
22+
23+
def add_relationship(self, reltype, target, rId, is_external=False):
24+
"""
25+
Return a newly added |_Relationship| instance.
26+
"""
27+
rel = _Relationship(rId, reltype, target, self._baseURI, is_external)
28+
self[rId] = rel
29+
if not is_external:
30+
self._target_parts_by_rId[rId] = target
31+
return rel
32+
33+
def get_or_add(self, reltype, target_part):
34+
"""
35+
Return relationship of *reltype* to *target_part*, newly added if not
36+
already present in collection.
37+
"""
38+
rel = self._get_matching(reltype, target_part)
39+
if rel is None:
40+
rId = self._next_rId
41+
rel = self.add_relationship(reltype, target_part, rId)
42+
return rel
43+
44+
def get_or_add_ext_rel(self, reltype, target_ref):
45+
"""
46+
Return rId of external relationship of *reltype* to *target_ref*,
47+
newly added if not already present in collection.
48+
"""
49+
rel = self._get_matching(reltype, target_ref, is_external=True)
50+
if rel is None:
51+
rId = self._next_rId
52+
rel = self.add_relationship(
53+
reltype, target_ref, rId, is_external=True
54+
)
55+
return rel.rId
56+
57+
def part_with_reltype(self, reltype):
58+
"""
59+
Return target part of rel with matching *reltype*, raising |KeyError|
60+
if not found and |ValueError| if more than one matching relationship
61+
is found.
62+
"""
63+
rel = self._get_rel_of_type(reltype)
64+
return rel.target_part
65+
66+
@property
67+
def related_parts(self):
68+
"""
69+
dict mapping rIds to target parts for all the internal relationships
70+
in the collection.
71+
"""
72+
return self._target_parts_by_rId
73+
74+
@property
75+
def xml(self):
76+
"""
77+
Serialize this relationship collection into XML suitable for storage
78+
as a .rels file in an OPC package.
79+
"""
80+
rels_elm = CT_Relationships.new()
81+
for rel in self.values():
82+
rels_elm.add_rel(
83+
rel.rId, rel.reltype, rel.target_ref, rel.is_external
84+
)
85+
return rels_elm.xml
86+
87+
def _get_matching(self, reltype, target, is_external=False):
88+
"""
89+
Return relationship of matching *reltype*, *target*, and
90+
*is_external* from collection, or None if not found.
91+
"""
92+
def matches(rel, reltype, target, is_external):
93+
if rel.reltype != reltype:
94+
return False
95+
if rel.is_external != is_external:
96+
return False
97+
rel_target = rel.target_ref if rel.is_external else rel.target_part
98+
if rel_target != target:
99+
return False
100+
return True
101+
102+
for rel in self.values():
103+
if matches(rel, reltype, target, is_external):
104+
return rel
105+
return None
106+
107+
def _get_rel_of_type(self, reltype):
108+
"""
109+
Return single relationship of type *reltype* from the collection.
110+
Raises |KeyError| if no matching relationship is found. Raises
111+
|ValueError| if more than one matching relationship is found.
112+
"""
113+
matching = [rel for rel in self.values() if rel.reltype == reltype]
114+
if len(matching) == 0:
115+
tmpl = "no relationship of type '%s' in collection"
116+
raise KeyError(tmpl % reltype)
117+
if len(matching) > 1:
118+
tmpl = "multiple relationships of type '%s' in collection"
119+
raise ValueError(tmpl % reltype)
120+
return matching[0]
121+
122+
@property
123+
def _next_rId(self):
124+
"""
125+
Next available rId in collection, starting from 'rId1' and making use
126+
of any gaps in numbering, e.g. 'rId2' for rIds ['rId1', 'rId3'].
127+
"""
128+
for n in range(1, len(self)+2):
129+
rId_candidate = 'rId%d' % n # like 'rId19'
130+
if rId_candidate not in self:
131+
return rId_candidate
132+
133+
134+
class _Relationship(object):
135+
"""
136+
Value object for relationship to part.
137+
"""
138+
def __init__(self, rId, reltype, target, baseURI, external=False):
139+
super(_Relationship, self).__init__()
140+
self._rId = rId
141+
self._reltype = reltype
142+
self._target = target
143+
self._baseURI = baseURI
144+
self._is_external = bool(external)
145+
146+
@property
147+
def is_external(self):
148+
return self._is_external
149+
150+
@property
151+
def reltype(self):
152+
return self._reltype
153+
154+
@property
155+
def rId(self):
156+
return self._rId
157+
158+
@property
159+
def target_part(self):
160+
if self._is_external:
161+
raise ValueError("target_part property on _Relationship is undef"
162+
"ined when target mode is External")
163+
return self._target
164+
165+
@property
166+
def target_ref(self):
167+
if self._is_external:
168+
return self._target
169+
else:
170+
return self._target.partname.relative_ref(self._baseURI)

0 commit comments

Comments
 (0)