Skip to content

Commit 660492e

Browse files
committed
bmk: add _DocumentBookmarkFinder.bookmark_pairs
1 parent 1bbf613 commit 660492e

3 files changed

Lines changed: 78 additions & 1 deletion

File tree

docx/bookmark.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
absolute_import, division, print_function, unicode_literals
77
)
88

9+
from itertools import chain
10+
911
from docx.shared import lazyproperty
1012

1113

@@ -45,4 +47,18 @@ def bookmark_pairs(self):
4547
start), or duplicate (name same as prior bookmark) bookmarks are
4648
ignored.
4749
"""
50+
return list(
51+
chain(*(
52+
_PartBookmarkFinder.iter_start_end_pairs(part)
53+
for part in self._document_part.iter_story_parts()
54+
))
55+
)
56+
57+
58+
class _PartBookmarkFinder(object):
59+
"""Provides access to bookmark oxml elements in a story part."""
60+
61+
@classmethod
62+
def iter_start_end_pairs(cls, part):
63+
"""Generate each (bookmarkStart, bookmarkEnd) in *part*."""
4864
raise NotImplementedError

docx/parts/document.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ def inline_shapes(self):
8383
"""
8484
return InlineShapes(self._element.body, self)
8585

86+
def iter_story_parts(self):
87+
"""Generate all parts in document that contain a story.
88+
89+
A story is a sequence of block-level items (paragraphs and tables).
90+
Story parts include this main document part, headers, footers,
91+
footnotes, and endnotes.
92+
"""
93+
raise NotImplementedError
94+
8695
def new_pic_inline(self, image_descriptor, width, height):
8796
"""
8897
Return a newly-created `w:inline` element containing the image

tests/test_bookmark.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99
import pytest
1010

1111
from docx.bookmark import Bookmarks, _DocumentBookmarkFinder
12+
from docx.opc.part import Part
1213
from docx.parts.document import DocumentPart
1314

14-
from .unitutil.mock import class_mock, instance_mock, property_mock
15+
from .unitutil.mock import call, class_mock, instance_mock, property_mock
1516

1617

1718
class DescribeBookmarks(object):
@@ -53,3 +54,54 @@ def finder_(self, request):
5354
@pytest.fixture
5455
def _finder_prop_(self, request):
5556
return property_mock(request, Bookmarks, '_finder')
57+
58+
59+
class Describe_DocumentBookmarkFinder(object):
60+
61+
def it_finds_all_the_bookmark_pairs_in_the_document(
62+
self, pairs_fixture, _PartBookmarkFinder_):
63+
document_part_, calls, expected_value = pairs_fixture
64+
document_bookmark_finder = _DocumentBookmarkFinder(document_part_)
65+
66+
bookmark_pairs = document_bookmark_finder.bookmark_pairs
67+
68+
document_part_.iter_story_parts.assert_called_once_with()
69+
assert (
70+
_PartBookmarkFinder_.iter_start_end_pairs.call_args_list == calls
71+
)
72+
assert bookmark_pairs == expected_value
73+
74+
# fixtures -------------------------------------------------------
75+
76+
@pytest.fixture(params=[
77+
([[(1, 2)]],
78+
[(1, 2)]),
79+
([[(1, 2), (3, 4), (5, 6)]],
80+
[(1, 2), (3, 4), (5, 6)]),
81+
([[(1, 2)], [(3, 4)], [(5, 6)]],
82+
[(1, 2), (3, 4), (5, 6)]),
83+
([[(1, 2), (3, 4)], [(5, 6), (7, 8)], [(9, 10)]],
84+
[(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)]),
85+
])
86+
def pairs_fixture(self, request, document_part_, _PartBookmarkFinder_):
87+
parts_pairs, expected_value = request.param
88+
mock_parts = [
89+
instance_mock(request, Part, name='Part-%d' % idx)
90+
for idx, part_pairs in enumerate(parts_pairs)
91+
]
92+
calls = [call(part_) for part_ in mock_parts]
93+
94+
document_part_.iter_story_parts.return_value = (p for p in mock_parts)
95+
_PartBookmarkFinder_.iter_start_end_pairs.side_effect = parts_pairs
96+
97+
return document_part_, calls, expected_value
98+
99+
# fixture components ---------------------------------------------
100+
101+
@pytest.fixture
102+
def _PartBookmarkFinder_(self, request):
103+
return class_mock(request, 'docx.bookmark._PartBookmarkFinder')
104+
105+
@pytest.fixture
106+
def document_part_(self, request):
107+
return instance_mock(request, DocumentPart)

0 commit comments

Comments
 (0)