Skip to content

Commit 05895d3

Browse files
committed
pkg: add OpcPackage.next_partname()
1 parent 4ed8545 commit 05895d3

File tree

2 files changed

+60
-19
lines changed

2 files changed

+60
-19
lines changed

docx/opc/package.py

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,24 @@
11
# encoding: utf-8
22

3-
"""
4-
The :mod:`pptx.packaging` module coheres around the concerns of reading and
5-
writing presentations to and from a .pptx file.
6-
"""
3+
"""Objects that implement reading and writing OPC packages."""
74

8-
from __future__ import absolute_import, print_function, unicode_literals
5+
from __future__ import absolute_import, division, print_function, unicode_literals
96

10-
from .constants import RELATIONSHIP_TYPE as RT
11-
from .packuri import PACKAGE_URI
12-
from .part import PartFactory
13-
from .parts.coreprops import CorePropertiesPart
14-
from .pkgreader import PackageReader
15-
from .pkgwriter import PackageWriter
16-
from .rel import Relationships
17-
from .shared import lazyproperty
7+
from docx.opc.constants import RELATIONSHIP_TYPE as RT
8+
from docx.opc.packuri import PACKAGE_URI, PackURI
9+
from docx.opc.part import PartFactory
10+
from docx.opc.parts.coreprops import CorePropertiesPart
11+
from docx.opc.pkgreader import PackageReader
12+
from docx.opc.pkgwriter import PackageWriter
13+
from docx.opc.rel import Relationships
14+
from docx.opc.shared import lazyproperty
1815

1916

2017
class OpcPackage(object):
21-
"""
22-
Main API class for |python-opc|. A new instance is constructed by calling
23-
the :meth:`open` class method with a path to a package file or file-like
24-
object containing one.
18+
"""Main API class for |python-opc|.
19+
20+
A new instance is constructed by calling the :meth:`open` class method with a path
21+
to a package file or file-like object containing one.
2522
"""
2623

2724
def __init__(self):
@@ -116,7 +113,11 @@ def next_partname(self, template):
116113
containing a single replacement item, a '%d' to be used to insert the integer
117114
portion of the partname. Example: "/word/header%d.xml"
118115
"""
119-
raise NotImplementedError
116+
partnames = {part.partname for part in self.iter_parts()}
117+
for n in range(1, len(partnames) + 2):
118+
candidate_partname = template % n
119+
if candidate_partname not in partnames:
120+
return PackURI(candidate_partname)
120121

121122
@classmethod
122123
def open(cls, pkg_file):

tests/opc/test_package.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from docx.opc.constants import RELATIONSHIP_TYPE as RT
1010
from docx.opc.coreprops import CoreProperties
1111
from docx.opc.package import OpcPackage, Unmarshaller
12-
from docx.opc.packuri import PACKAGE_URI
12+
from docx.opc.packuri import PACKAGE_URI, PackURI
1313
from docx.opc.part import Part
1414
from docx.opc.parts.coreprops import CorePropertiesPart
1515
from docx.opc.pkgreader import PackageReader
@@ -101,6 +101,20 @@ def it_can_iterate_over_parts_by_walking_rels_graph(self):
101101
assert part2 in pkg.iter_parts()
102102
assert len([p for p in pkg.iter_parts()]) == 2
103103

104+
def it_can_find_the_next_available_vector_partname(
105+
self, next_partname_fixture, iter_parts_, PackURI_, packuri_
106+
):
107+
"""A vector partname is one with a numeric suffix, like header42.xml."""
108+
parts_, expected_value = next_partname_fixture
109+
iter_parts_.return_value = iter(parts_)
110+
PackURI_.return_value = packuri_
111+
package = OpcPackage()
112+
113+
partname = package.next_partname(template="/foo/bar/baz%d.xml")
114+
115+
PackURI_.assert_called_once_with(expected_value)
116+
assert partname is packuri_
117+
104118
def it_can_find_a_part_related_by_reltype(self, related_part_fixture_):
105119
pkg, reltype, related_part_ = related_part_fixture_
106120
related_part = pkg.part_related_by(reltype)
@@ -161,6 +175,20 @@ def core_props_part_fixture(
161175
part_related_by_.return_value = core_properties_part_
162176
return opc_package, core_properties_part_
163177

178+
@pytest.fixture(
179+
params=[((), 1), ((1,), 2), ((1, 2), 3), ((2, 3), 1), ((1, 3), 2)]
180+
)
181+
def next_partname_fixture(self, request, iter_parts_):
182+
existing_partname_ns, next_partname_n = request.param
183+
parts_ = [
184+
instance_mock(
185+
request, Part, name="part[%d]" % idx, partname="/foo/bar/baz%d.xml" % n
186+
)
187+
for idx, n in enumerate(existing_partname_ns)
188+
]
189+
expected_value = "/foo/bar/baz%d.xml" % next_partname_n
190+
return parts_, expected_value
191+
164192
@pytest.fixture
165193
def relate_to_part_fixture_(self, request, pkg, rels_, reltype):
166194
rId = 'rId99'
@@ -196,10 +224,22 @@ def core_properties_part_(self, request):
196224
def _core_properties_part_prop_(self, request):
197225
return property_mock(request, OpcPackage, '_core_properties_part')
198226

227+
@pytest.fixture
228+
def iter_parts_(self, request):
229+
return method_mock(request, OpcPackage, "iter_parts")
230+
199231
@pytest.fixture
200232
def PackageReader_(self, request):
201233
return class_mock(request, 'docx.opc.package.PackageReader')
202234

235+
@pytest.fixture
236+
def PackURI_(self, request):
237+
return class_mock(request, "docx.opc.package.PackURI")
238+
239+
@pytest.fixture
240+
def packuri_(self, request):
241+
return instance_mock(request, PackURI)
242+
203243
@pytest.fixture
204244
def PackageWriter_(self, request):
205245
return class_mock(request, 'docx.opc.package.PackageWriter')

0 commit comments

Comments
 (0)