Skip to content

Commit 4acb2e5

Browse files
committed
bmk: add Document.start_bookmark()
1 parent 4c93f5d commit 4acb2e5

3 files changed

Lines changed: 84 additions & 56 deletions

File tree

docx/blkcntnr.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def __init__(self, element, parent):
2525
super(BlockItemContainer, self).__init__(parent)
2626
self._element = element
2727

28-
def add_paragraph(self, text='', style=None):
28+
def add_paragraph(self, text="", style=None):
2929
"""
3030
Return a paragraph newly added to the end of the content in this
3131
container, having *text* in a single run if present, and having
@@ -46,6 +46,7 @@ def add_table(self, rows, cols, width):
4646
distributed between the table columns.
4747
"""
4848
from .table import Table
49+
4950
tbl = CT_Tbl.new_tbl(rows, cols, width)
5051
self._element._insert_tbl(tbl)
5152
return Table(tbl, self)
@@ -58,13 +59,21 @@ def paragraphs(self):
5859
"""
5960
return [Paragraph(p, self) for p in self._element.p_lst]
6061

62+
def start_bookmark(self, name):
63+
"""Return _Bookmark object identified by `name`.
64+
65+
The returned bookmark is anchored at the end of this block-item container.
66+
"""
67+
raise NotImplementedError
68+
6169
@property
6270
def tables(self):
6371
"""
6472
A list containing the tables in this container, in document order.
6573
Read-only.
6674
"""
6775
from .table import Table
76+
6877
return [Table(tbl, self) for tbl in self._element.tbl_lst]
6978

7079
def _add_paragraph(self):

docx/document.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,13 @@ def settings(self):
159159
"""
160160
return self._part.settings
161161

162+
def start_bookmark(self, name):
163+
"""Return _Bookmark object identified by `name`.
164+
165+
The returned bookmark is anchored at the end of this document.
166+
"""
167+
return self._body.start_bookmark(name)
168+
162169
@property
163170
def styles(self):
164171
"""

tests/test_document.py

Lines changed: 67 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import pytest
88

9-
from docx.bookmark import Bookmarks
9+
from docx.bookmark import _Bookmark, Bookmarks
1010
from docx.document import _Body, Document
1111
from docx.enum.section import WD_SECTION
1212
from docx.enum.text import WD_BREAK
@@ -26,6 +26,7 @@
2626

2727

2828
class DescribeDocument(object):
29+
"""Unit-test suite for `docx.document.Document` object."""
2930

3031
def it_can_add_a_heading(self, add_heading_fixture, add_paragraph_, paragraph_):
3132
level, style = add_heading_fixture
@@ -78,7 +79,7 @@ def it_can_add_a_section(
7879
section = document.add_section(start_type)
7980

8081
assert document.element.xml == expected_xml
81-
sectPr = document.element.xpath('w:body/w:sectPr')[0]
82+
sectPr = document.element.xpath("w:body/w:sectPr")[0]
8283
Section_.assert_called_once_with(sectPr, document_part_)
8384
assert section is section_
8485

@@ -95,7 +96,8 @@ def it_can_save_the_document_to_a_file(self, save_fixture):
9596
document._part.save.assert_called_once_with(file_)
9697

9798
def it_provides_access_to_its_bookmarks(
98-
self, document_part_, Bookmarks_, bookmarks_):
99+
self, document_part_, Bookmarks_, bookmarks_
100+
):
99101
Bookmarks_.return_value = bookmarks_
100102
document = Document(None, document_part_)
101103

@@ -119,7 +121,7 @@ def it_provides_access_to_its_paragraphs(self, paragraphs_fixture):
119121
assert paragraphs is paragraphs_
120122

121123
def it_provides_access_to_its_sections(self, document_part_, Sections_, sections_):
122-
document_elm = element('w:document')
124+
document_elm = element("w:document")
123125
Sections_.return_value = sections_
124126
document = Document(document_elm, document_part_)
125127

@@ -132,6 +134,16 @@ def it_provides_access_to_its_settings(self, settings_fixture):
132134
document, settings_ = settings_fixture
133135
assert document.settings is settings_
134136

137+
def it_can_start_a_bookmark(self, _body_prop_, body_, bookmark_):
138+
_body_prop_.return_value = body_
139+
body_.start_bookmark.return_value = bookmark_
140+
document = Document(None, None)
141+
142+
bookmark = document.start_bookmark("foobar")
143+
144+
body_.start_bookmark.assert_called_once_with("foobar")
145+
assert bookmark is bookmark_
146+
135147
def it_provides_access_to_its_styles(self, styles_fixture):
136148
document, styles_ = styles_fixture
137149
assert document.styles is styles_
@@ -159,57 +171,52 @@ def it_determines_block_width_to_help(self, block_width_fixture):
159171

160172
# fixtures -------------------------------------------------------
161173

162-
@pytest.fixture(params=[
163-
(0, 'Title'),
164-
(1, 'Heading 1'),
165-
(2, 'Heading 2'),
166-
(9, 'Heading 9'),
167-
])
174+
@pytest.fixture(
175+
params=[(0, "Title"), (1, "Heading 1"), (2, "Heading 2"), (9, "Heading 9")]
176+
)
168177
def add_heading_fixture(self, request):
169178
level, style = request.param
170179
return level, style
171180

172-
@pytest.fixture(params=[
173-
('', None),
174-
('', 'Heading 1'),
175-
('foo\rbar', 'Body Text'),
176-
])
177-
def add_paragraph_fixture(self, request, body_prop_, paragraph_):
181+
@pytest.fixture(params=[("", None), ("", "Heading 1"), ("foo\rbar", "Body Text")])
182+
def add_paragraph_fixture(self, request, _body_prop_, paragraph_):
178183
text, style = request.param
179184
document = Document(None, None)
180-
body_prop_.return_value.add_paragraph.return_value = paragraph_
185+
_body_prop_.return_value.add_paragraph.return_value = paragraph_
181186
return document, text, style, paragraph_
182187

183188
@pytest.fixture
184189
def add_picture_fixture(self, request, add_paragraph_, run_, picture_):
185190
document = Document(None, None)
186-
path, width, height = 'foobar.png', 100, 200
191+
path, width, height = "foobar.png", 100, 200
187192
add_paragraph_.return_value.add_run.return_value = run_
188193
run_.add_picture.return_value = picture_
189194
return document, path, width, height, run_, picture_
190195

191-
@pytest.fixture(params=[
192-
('w:sectPr', WD_SECTION.EVEN_PAGE,
193-
'w:sectPr/w:type{w:val=evenPage}'),
194-
('w:sectPr/w:type{w:val=evenPage}', WD_SECTION.ODD_PAGE,
195-
'w:sectPr/w:type{w:val=oddPage}'),
196-
('w:sectPr/w:type{w:val=oddPage}', WD_SECTION.NEW_PAGE,
197-
'w:sectPr'),
198-
])
196+
@pytest.fixture(
197+
params=[
198+
("w:sectPr", WD_SECTION.EVEN_PAGE, "w:sectPr/w:type{w:val=evenPage}"),
199+
(
200+
"w:sectPr/w:type{w:val=evenPage}",
201+
WD_SECTION.ODD_PAGE,
202+
"w:sectPr/w:type{w:val=oddPage}",
203+
),
204+
("w:sectPr/w:type{w:val=oddPage}", WD_SECTION.NEW_PAGE, "w:sectPr"),
205+
]
206+
)
199207
def add_section_fixture(self, request):
200208
sentinel, start_type, new_sentinel = request.param
201-
document_elm = element('w:document/w:body/(w:p,%s)' % sentinel)
209+
document_elm = element("w:document/w:body/(w:p,%s)" % sentinel)
202210
expected_xml = xml(
203-
'w:document/w:body/(w:p,w:p/w:pPr/%s,%s)' %
204-
(sentinel, new_sentinel)
211+
"w:document/w:body/(w:p,w:p/w:pPr/%s,%s)" % (sentinel, new_sentinel)
205212
)
206213
return document_elm, start_type, expected_xml
207214

208215
@pytest.fixture
209-
def add_table_fixture(self, _block_width_prop_, body_prop_, table_):
216+
def add_table_fixture(self, _block_width_prop_, _body_prop_, table_):
210217
document = Document(None, None)
211-
rows, cols, style = 4, 2, 'Light Shading Accent 1'
212-
body_prop_.return_value.add_table.return_value = table_
218+
rows, cols, style = 4, 2, "Light Shading Accent 1"
219+
_body_prop_.return_value.add_table.return_value = table_
213220
_block_width_prop_.return_value = width = 42
214221
return document, rows, cols, style, width, table_
215222

@@ -225,7 +232,7 @@ def block_width_fixture(self, sections_prop_, section_):
225232

226233
@pytest.fixture
227234
def body_fixture(self, _Body_, body_):
228-
document_elm = element('w:document/w:body')
235+
document_elm = element("w:document/w:body")
229236
body_elm = document_elm[0]
230237
document = Document(document_elm, None)
231238
return document, body_elm, _Body_, body_
@@ -243,9 +250,9 @@ def inline_shapes_fixture(self, document_part_, inline_shapes_):
243250
return document, inline_shapes_
244251

245252
@pytest.fixture
246-
def paragraphs_fixture(self, body_prop_, paragraphs_):
253+
def paragraphs_fixture(self, _body_prop_, paragraphs_):
247254
document = Document(None, None)
248-
body_prop_.return_value.paragraphs = paragraphs_
255+
_body_prop_.return_value.paragraphs = paragraphs_
249256
return document, paragraphs_
250257

251258
@pytest.fixture
@@ -256,7 +263,7 @@ def part_fixture(self, document_part_):
256263
@pytest.fixture
257264
def save_fixture(self, document_part_):
258265
document = Document(None, document_part_)
259-
file_ = 'foobar.docx'
266+
file_ = "foobar.docx"
260267
return document, file_
261268

262269
@pytest.fixture
@@ -272,36 +279,40 @@ def styles_fixture(self, document_part_, styles_):
272279
return document, styles_
273280

274281
@pytest.fixture
275-
def tables_fixture(self, body_prop_, tables_):
282+
def tables_fixture(self, _body_prop_, tables_):
276283
document = Document(None, None)
277-
body_prop_.return_value.tables = tables_
284+
_body_prop_.return_value.tables = tables_
278285
return document, tables_
279286

280287
# fixture components ---------------------------------------------
281288

282289
@pytest.fixture
283290
def add_paragraph_(self, request):
284-
return method_mock(request, Document, 'add_paragraph')
291+
return method_mock(request, Document, "add_paragraph")
285292

286293
@pytest.fixture
287294
def _block_width_prop_(self, request):
288-
return property_mock(request, Document, '_block_width')
295+
return property_mock(request, Document, "_block_width")
289296

290297
@pytest.fixture
291298
def _Body_(self, request, body_):
292-
return class_mock(request, 'docx.document._Body', return_value=body_)
299+
return class_mock(request, "docx.document._Body", return_value=body_)
293300

294301
@pytest.fixture
295302
def body_(self, request):
296303
return instance_mock(request, _Body)
297304

298305
@pytest.fixture
299-
def body_prop_(self, request, body_):
300-
return property_mock(request, Document, '_body')
306+
def _body_prop_(self, request, body_):
307+
return property_mock(request, Document, "_body")
308+
309+
@pytest.fixture
310+
def bookmark_(self, request):
311+
return instance_mock(request, _Bookmark)
301312

302313
@pytest.fixture
303314
def Bookmarks_(self, request):
304-
return class_mock(request, 'docx.document.Bookmarks')
315+
return class_mock(request, "docx.document.Bookmarks")
305316

306317
@pytest.fixture
307318
def bookmarks_(self, request):
@@ -337,23 +348,23 @@ def run_(self, request):
337348

338349
@pytest.fixture
339350
def Section_(self, request):
340-
return class_mock(request, 'docx.document.Section')
351+
return class_mock(request, "docx.document.Section")
341352

342353
@pytest.fixture
343354
def section_(self, request):
344355
return instance_mock(request, Section)
345356

346357
@pytest.fixture
347358
def Sections_(self, request):
348-
return class_mock(request, 'docx.document.Sections')
359+
return class_mock(request, "docx.document.Sections")
349360

350361
@pytest.fixture
351362
def sections_(self, request):
352363
return instance_mock(request, Sections)
353364

354365
@pytest.fixture
355366
def sections_prop_(self, request):
356-
return property_mock(request, Document, 'sections')
367+
return property_mock(request, Document, "sections")
357368

358369
@pytest.fixture
359370
def settings_(self, request):
@@ -365,15 +376,14 @@ def styles_(self, request):
365376

366377
@pytest.fixture
367378
def table_(self, request):
368-
return instance_mock(request, Table, style='UNASSIGNED')
379+
return instance_mock(request, Table, style="UNASSIGNED")
369380

370381
@pytest.fixture
371382
def tables_(self, request):
372383
return instance_mock(request, list)
373384

374385

375386
class Describe_Body(object):
376-
377387
def it_can_clear_itself_of_all_content_it_holds(self, clear_fixture):
378388
body, expected_xml = clear_fixture
379389
_body = body.clear_content()
@@ -382,12 +392,14 @@ def it_can_clear_itself_of_all_content_it_holds(self, clear_fixture):
382392

383393
# fixtures -------------------------------------------------------
384394

385-
@pytest.fixture(params=[
386-
('w:body', 'w:body'),
387-
('w:body/w:p', 'w:body'),
388-
('w:body/w:sectPr', 'w:body/w:sectPr'),
389-
('w:body/(w:p, w:sectPr)', 'w:body/w:sectPr'),
390-
])
395+
@pytest.fixture(
396+
params=[
397+
("w:body", "w:body"),
398+
("w:body/w:p", "w:body"),
399+
("w:body/w:sectPr", "w:body/w:sectPr"),
400+
("w:body/(w:p, w:sectPr)", "w:body/w:sectPr"),
401+
]
402+
)
391403
def clear_fixture(self, request):
392404
before_cxml, after_cxml = request.param
393405
body = _Body(element(before_cxml), None)

0 commit comments

Comments
 (0)