Skip to content

Commit 1682b2b

Browse files
author
Steve Canny
committed
opc: add OpcPackage.iter_rels()
1 parent 11afa86 commit 1682b2b

File tree

2 files changed

+84
-26
lines changed

2 files changed

+84
-26
lines changed

pptx/opc/package.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,22 @@ def iter_rels(self):
6161
Generate exactly one reference to each relationship in the package by
6262
performing a depth-first traversal of the rels graph.
6363
"""
64-
raise NotImplementedError
64+
def walk_rels(source, visited=None):
65+
visited = [] if visited is None else visited
66+
for rel in source.rels.values():
67+
yield rel
68+
if rel.is_external:
69+
continue
70+
part = rel.target_part
71+
if part in visited:
72+
continue
73+
visited.append(part)
74+
new_source = part
75+
for rel in walk_rels(new_source, visited):
76+
yield rel
77+
78+
for rel in walk_rels(self):
79+
yield rel
6580

6681
def load_rel(self, reltype, target, rId, is_external=False):
6782
"""

tests/opc/test_package.py

Lines changed: 68 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -74,31 +74,15 @@ def it_can_provide_a_list_of_the_parts_it_contains(self):
7474
with patch.object(OpcPackage, 'iter_parts', return_value=parts):
7575
assert pkg.parts == [parts[0], parts[1]]
7676

77-
def it_can_iterate_over_parts_by_walking_rels_graph(self):
78-
# +----------+ +--------+
79-
# | pkg_rels |-----> | part_1 |
80-
# +----------+ +--------+
81-
# | | ^
82-
# v v |
83-
# external +--------+
84-
# | part_2 |
85-
# +--------+
86-
part1, part2 = (Mock(name='part1'), Mock(name='part2'))
87-
part1.rels = {
88-
1: Mock(name='rel1', is_external=False, target_part=part2)
89-
}
90-
part2.rels = {
91-
1: Mock(name='rel2', is_external=False, target_part=part1)
92-
}
93-
pkg = OpcPackage()
94-
pkg._rels = {
95-
1: Mock(name='rel3', is_external=False, target_part=part1),
96-
2: Mock(name='rel4', is_external=True),
97-
}
98-
# verify -----------------------
99-
assert part1 in pkg.iter_parts()
100-
assert part2 in pkg.iter_parts()
101-
assert len([p for p in pkg.iter_parts()]) == 2
77+
def it_can_iterate_over_its_parts(self, iter_parts_fixture):
78+
package, expected_parts = iter_parts_fixture
79+
parts = list(package.iter_parts())
80+
assert parts == expected_parts
81+
82+
def it_can_iterate_over_its_relationships(self, iter_rels_fixture):
83+
package, expected_rels = iter_rels_fixture
84+
rels = list(package.iter_rels())
85+
assert rels == expected_rels
10286

10387
def it_can_find_a_part_related_by_reltype(self, related_part_fixture_):
10488
pkg, reltype, related_part_ = related_part_fixture_
@@ -128,6 +112,18 @@ def it_can_be_notified_after_unmarshalling_is_complete(self, pkg):
128112

129113
# fixtures ---------------------------------------------
130114

115+
@pytest.fixture
116+
def iter_parts_fixture(self, request, rels_fixture):
117+
package, parts, rels = rels_fixture
118+
expected_parts = list(parts)
119+
return package, expected_parts
120+
121+
@pytest.fixture
122+
def iter_rels_fixture(self, request, rels_fixture):
123+
package, parts, rels = rels_fixture
124+
expected_rels = list(rels)
125+
return package, expected_rels
126+
131127
@pytest.fixture(params=[
132128
((), 1), ((1,), 2), ((1, 2), 3), ((2, 3), 1), ((1, 3), 2)
133129
])
@@ -165,6 +161,34 @@ def related_part_fixture_(self, request, rels_, reltype):
165161
pkg._rels = rels_
166162
return pkg, reltype, related_part_
167163

164+
@pytest.fixture
165+
def rels_fixture(self, request, part_1_, part_2_):
166+
"""
167+
+----------+ +--------+
168+
| pkg_rels |-- r1 --> | part_1 |
169+
+----------+ +--------+
170+
| | | ^
171+
r5 | | r4 r2 | | r3
172+
| | | |
173+
v | v |
174+
external | +--------+
175+
+--------> | part_2 |
176+
+--------+
177+
"""
178+
r1 = self.rel(request, False, part_1_, 'r1')
179+
r2 = self.rel(request, False, part_2_, 'r2')
180+
r3 = self.rel(request, False, part_1_, 'r3')
181+
r4 = self.rel(request, False, part_2_, 'r4')
182+
r5 = self.rel(request, True, None, 'r5')
183+
184+
package = OpcPackage()
185+
186+
package._rels = self.rels(request, (r1, r4, r5))
187+
part_1_.rels = self.rels(request, (r2,))
188+
part_2_.rels = self.rels(request, (r3,))
189+
190+
return package, (part_1_, part_2_), (r1, r2, r3, r4, r5)
191+
168192
# fixture components -----------------------------------
169193

170194
@pytest.fixture
@@ -183,6 +207,14 @@ def PackageWriter_(self, request):
183207
def PartFactory_(self, request):
184208
return class_mock(request, 'pptx.opc.package.PartFactory')
185209

210+
@pytest.fixture
211+
def part_1_(self, request):
212+
return instance_mock(request, Part)
213+
214+
@pytest.fixture
215+
def part_2_(self, request):
216+
return instance_mock(request, Part)
217+
186218
@pytest.fixture
187219
def parts(self, request, parts_):
188220
"""
@@ -227,6 +259,17 @@ def rel_attrs_(self, request):
227259
rId = 'rId99'
228260
return reltype, target_, rId
229261

262+
def rel(self, request, is_external, target_part, name):
263+
return instance_mock(
264+
request, _Relationship, is_external=is_external,
265+
target_part=target_part, name=name
266+
)
267+
268+
def rels(self, request, values):
269+
rels = instance_mock(request, RelationshipCollection)
270+
rels.values.return_value = values
271+
return rels
272+
230273
@pytest.fixture
231274
def rels_(self, request):
232275
return instance_mock(request, RelationshipCollection)

0 commit comments

Comments
 (0)