Skip to content

Commit 1a77bf3

Browse files
committed
Implemented bookmarks
1 parent e05d002 commit 1a77bf3

File tree

6 files changed

+119
-97
lines changed

6 files changed

+119
-97
lines changed

docx/blkcntnr.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,20 @@
88

99
from __future__ import absolute_import, print_function
1010

11-
from .oxml.table import CT_Tbl
12-
from .shared import Parented
13-
from .text.paragraph import Paragraph
11+
from docx.oxml.table import CT_Tbl
12+
from docx.shared import Parented
13+
from docx.text.bookmarks import BookmarkParent
14+
from docx.text.paragraph import Paragraph
1415

1516

16-
class BlockItemContainer(Parented):
17+
class BlockItemContainer(Parented, BookmarkParent):
1718
"""
1819
Base class for proxy objects that can contain block items, such as _Body,
1920
_Cell, header, footer, footnote, endnote, comment, and text box objects.
2021
Provides the shared functionality to add a block item like a paragraph or
2122
table.
2223
"""
24+
2325
def __init__(self, element, parent):
2426
super(BlockItemContainer, self).__init__(parent)
2527
self._element = element

docx/document.py

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,18 @@
44
|Document| and closely related objects
55
"""
66

7-
from __future__ import (
8-
absolute_import, division, print_function, unicode_literals
9-
)
10-
11-
from .blkcntnr import BlockItemContainer
12-
from .enum.section import WD_SECTION
13-
from .enum.text import WD_BREAK
14-
from .section import Section, Sections
7+
from __future__ import (absolute_import, division, print_function,
8+
unicode_literals)
9+
10+
from docx.blkcntnr import BlockItemContainer
11+
from docx.enum.section import WD_SECTION
12+
from docx.enum.text import WD_BREAK
13+
from docx.section import Section, Sections
1514
from docx.shared import ElementProxy, Emu
16-
from docx.text.bookmarks import Bookmark, Bookmarks, BookmarkParent
17-
from docx.oxml.xmlchemy import ZeroOrOne, ZeroOrMore
15+
from docx.text.bookmarks import Bookmarks
1816

1917

20-
class Document(ElementProxy, BookmarkParent):
18+
class Document(ElementProxy):
2119
"""
2220
WordprocessingML (WML) document. Not intended to be constructed directly.
2321
Use :func:`docx.Document` to open or create a document.
@@ -33,6 +31,12 @@ def __init__(self, element, part):
3331
def bookmarks(self):
3432
return Bookmarks(self._element.body)
3533

34+
def start_bookmark(self, name):
35+
return self._body.start_bookmark(name)
36+
37+
def end_bookmark(self, bookmark=None):
38+
return self._body.end_bookmark(bookmark)
39+
3640
def add_heading(self, text='', level=1):
3741
"""
3842
Return a heading paragraph newly added to the end of the document,
@@ -219,18 +223,3 @@ def clear_content(self):
219223
"""
220224
self._body.clear_content()
221225
return self
222-
223-
def add_bookmarkStart(self, name):
224-
bookmarkStart = self._body._add_bookmarkStart()
225-
bookmarkStart.name = name
226-
bookmarkStart.bmrk_id = bookmarkStart._next_id
227-
return bookmarkStart
228-
229-
def add_bookmarkEnd(self, bookmark=None):
230-
bookmarkEnd = self._body._add_bookmarkEnd()
231-
if bookmark is None:
232-
bookmarkEnd.bmrk_id = bookmarkEnd._next_id
233-
else:
234-
bookmarkEnd.bmrk_id = bookmark._id
235-
return bookmarkEnd
236-

docx/oxml/bookmark.py

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,37 +11,59 @@
1111

1212

1313
class CT_BookmarkStart(BaseOxmlElement):
14-
"""
15-
``<w:bookmarkStart>`` element
16-
"""
14+
"""The ``<w:bookmarkStart>`` element"""
1715
name = RequiredAttribute('w:name', ST_String)
1816
bmrk_id = RequiredAttribute('w:id', ST_RelationshipId)
19-
17+
2018
@property
2119
def _next_id(self):
20+
"""
21+
The `_next_id` property is used to get the next index based on
22+
the total amount of bookmarkStart elements already in the document
23+
"""
2224
root = self.getroottree().getroot()
2325
return str(len(root.xpath('.//w:bookmarkStart')))
2426

27+
@property
28+
def is_closed(self):
29+
"""
30+
The `is_closed` property of the :class:`CT_BookmarkStart` object is
31+
used to determine whether there is already a bookmarkEnd element in
32+
the document containing the same bookmark id. If this is the case, the
33+
bookmark is closed if not, the bookmark is open.
34+
"""
35+
root_element = self.getroottree().getroot()
36+
matching_bookmarkEnds = root_element.xpath(
37+
'.//w:bookmarkEnd[@w:id=\'%s\']' % self.bmrk_id
38+
)
39+
if not matching_bookmarkEnds:
40+
return False
41+
return True
2542

2643

2744
class CT_BookmarkEnd(BaseOxmlElement):
28-
"""
29-
The ``<w:bookmarkEnd>`` element
30-
"""
45+
"""The ``<w:bookmarkEnd>`` element."""
3146
bmrk_id = RequiredAttribute('w:id', ST_RelationshipId)
3247

3348
@property
3449
def _next_id(self):
50+
"""
51+
The `_next_id` property is used to get the next index based on
52+
the total amount of bookmarkStart elements already in the document
53+
"""
3554
root = self.getroottree().getroot()
3655
return str(len(root.xpath('.//w:bookmarkStart')))
3756

38-
# def match_bookmarkstart(self, name):
39-
# """Returns `w:bookmarkStart` element having matching name, None if not present."""
40-
# root_element = self.getroottree().getroot()
41-
# matching_bookmarkStarts = root_element.xpath(
42-
# './/w:bookmarkStart[@w:name=\'%s\']' % name
43-
# )
44-
# if not matching_bookmarkStarts:
45-
# raise ValueError('Bookmark name not found')
46-
# return None
47-
# return matching_bookmarkStarts[0].bmrk_id
57+
@property
58+
def is_closed(self):
59+
"""
60+
The `is_closed` property is used to determine whether there is allready
61+
a bookmarkEnd element in the document containing the same bookmark id.
62+
"""
63+
root_element = self.getroottree().getroot()
64+
matching_bookmarkEnds = root_element.xpath(
65+
'.//w:bookmarkEnd[@w:id=\'%s\']' % self.bmrk_id
66+
)
67+
if len(matching_bookmarkEnds) == 1:
68+
return True
69+
return False

docx/oxml/text/paragraph.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from docx.oxml.ns import qn
88
from docx.oxml.xmlchemy import (BaseOxmlElement, OxmlElement, ZeroOrMore,
99
ZeroOrOne)
10-
from docx.text.bookmarks import Bookmark
1110

1211

1312
class CT_P(BaseOxmlElement):
@@ -31,20 +30,6 @@ def add_p_before(self):
3130
self.addprevious(new_p)
3231
return new_p
3332

34-
def add_bookmarkStart(self, name):
35-
bookmarkStart = self._add_bookmarkStart()
36-
bookmarkStart.name = name
37-
bookmarkStart.bmrk_id = bookmarkStart._next_id
38-
return bookmarkStart
39-
40-
def add_bookmarkEnd(self, bookmark=None):
41-
bookmarkEnd = self._add_bookmarkEnd()
42-
if bookmark is None:
43-
bookmarkEnd.bmrk_id = bookmarkEnd._next_id
44-
else:
45-
bookmarkEnd.bmrk_id = bookmark._id
46-
return bookmarkEnd
47-
4833
@property
4934
def alignment(self):
5035
"""

docx/oxml/text/run.py

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from docx.oxml.simpletypes import ST_BrClear, ST_BrType
99
from docx.oxml.xmlchemy import (BaseOxmlElement, OptionalAttribute, ZeroOrMore,
1010
ZeroOrOne)
11-
from docx.text.bookmarks import Bookmark
1211

1312

1413
class CT_Br(BaseOxmlElement):
@@ -29,27 +28,13 @@ class CT_R(BaseOxmlElement):
2928
cr = ZeroOrMore('w:cr')
3029
tab = ZeroOrMore('w:tab')
3130
drawing = ZeroOrMore('w:drawing')
32-
bookmarkStart = ZeroOrMore('w:bookmarkStart', successors=('w:sectPr',))
3331
bookmarkEnd = ZeroOrMore('w:bookmarkEnd', successors=('w:sectPr',))
32+
bookmarkStart = ZeroOrMore('w:bookmarkStart', successors=('w:sectPr',))
3433

3534
def _insert_rPr(self, rPr):
3635
self.insert(0, rPr)
3736
return rPr
3837

39-
def add_bookmarkStart(self, name):
40-
bookmarkStart = self._add_bookmarkStart()
41-
bookmarkStart.name = name
42-
bookmarkStart.bmrk_id = bookmarkStart._next_id
43-
return bookmarkStart
44-
45-
def add_bookmarkEnd(self, bookmark=None):
46-
bookmarkEnd = self._add_bookmarkEnd()
47-
if bookmark is None:
48-
bookmarkEnd.bmrk_id = bookmarkEnd._next_id
49-
else:
50-
bookmarkEnd.bmrk_id = bookmark._id
51-
return bookmarkEnd
52-
5338
def add_t(self, text):
5439
"""
5540
Return a newly added ``<w:t>`` element containing *text*.

docx/text/bookmarks.py

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@
44
Bookmarks-related proxy types.
55
"""
66

7-
from __future__ import (
8-
absolute_import, division, print_function, unicode_literals
9-
)
7+
from __future__ import (absolute_import, division, print_function,
8+
unicode_literals)
109

1110
from collections import Sequence
12-
from docx.oxml.bookmark import CT_BookmarkEnd, CT_BookmarkStart
11+
1312
from docx.shared import ElementProxy
1413

1514

@@ -19,60 +18,100 @@ def __init__(self, document_elm):
1918
self._document = self._element = document_elm
2019

2120
def __iter__(self):
21+
"""Enables list like iteration of the bookmark starts. """
2222
for bookmarkStart in self._bookmarkStarts:
2323
yield Bookmark(bookmarkStart)
2424

2525
def __getitem__(self, idx):
26+
"""Provides list like access to the bookmarks """
2627
bookmarkStart = self._bookmarkStarts[idx]
2728
return Bookmark(bookmarkStart)
2829

2930
def __len__(self):
31+
"""
32+
Returns the total count of ``<w:bookmarkStart>`` elements in the
33+
document
34+
"""
3035
return len(self._bookmarkStarts)
3136

3237
def get(self, name, default=None):
38+
"""
39+
Get method which returns the bookmark corresponding to the name
40+
provided.
41+
"""
3342
for bookmarkStart in self._bookmarkStarts:
3443
if bookmarkStart.name == name:
3544
return Bookmark(bookmarkStart)
3645
return default
3746

3847
@property
3948
def _bookmarkStarts(self):
49+
"""Returns a list of ``<w:bookmarkStart>`` elements """
4050
return self._document.xpath('.//w:bookmarkStart')
4151

4252
@property
4353
def _bookmarkEnds(self):
54+
"""Returns a list of ``<w:bookmarkEnd>`` elements """
4455
return self._document.xpath('.//w:bookmarkEnd')
4556

46-
@property
47-
def _next_id(self):
48-
return str(len(self._bookmarkStarts))
4957

5058
class Bookmark(ElementProxy):
59+
"""
60+
The :class:`Bookmark` object is an proxy element which is used to wrap
61+
around the xml elements ``<w:bookmarkStart>`` and ``<w:bookmarkEnd>``
62+
"""
5163
def __init__(self, doc_element):
5264
super(Bookmark, self).__init__(doc_element)
5365
self._element = doc_element
54-
66+
5567
@property
56-
def _id(self):
68+
def id(self):
69+
""" Returns the element's unique identifier."""
5770
return self._element.bmrk_id
5871

5972
@property
60-
def _name(self):
73+
def name(self):
74+
""" Returns the element's name."""
6175
return self._element.name
6276

77+
@property
78+
def is_closed(self):
79+
""" If True, the bookmark is closed. """
80+
return self._element.is_closed
81+
82+
6383
class BookmarkParent(object):
84+
"""
85+
The :class:`BookmarkParent` object is used as mixin object for the
86+
different parts of the document. It contains the methods which can be used
87+
to start and end a Bookmark.
88+
"""
6489
def start_bookmark(self, name):
65-
if hasattr(self, '_body'):
66-
self._element = self._body
67-
bookmarkstart = self._element.add_bookmarkStart(name)
90+
"""
91+
The :func:`start_bookmark` method is used to place the start of a
92+
bookmark. It requires a name as input.
93+
94+
:param str name: Bookmark name
95+
96+
"""
97+
bookmarkstart = self._element._add_bookmarkStart()
98+
bookmarkstart.bmrk_id = bookmarkstart._next_id
99+
bookmarkstart.name = name
68100
return Bookmark(bookmarkstart)
69101

70102
def end_bookmark(self, bookmark=None):
71-
if hasattr(self, '_body'):
72-
self._element = self._body
73-
bookmarkend = self._element.add_bookmarkEnd(bookmark)
103+
"""
104+
The :func:`end_bookmark` method is used to end a bookmark. It takes a
105+
:any:`Bookmark<docx.text.bookmarks.Bookmark>` as optional input.
106+
107+
"""
108+
bookmarkend = self._element._add_bookmarkEnd()
74109
if bookmark is None:
75-
bookmarkend._next_id
110+
bookmarkend.bmrk_id = bookmarkend._next_id
111+
if bookmarkend.is_closed:
112+
raise ValueError('Cannot end closed bookmark.')
76113
else:
77-
bookmarkend._id = bookmark._id
78-
return Bookmark(bookmarkend)
114+
if bookmark.is_closed:
115+
raise ValueError('Cannot end closed bookmark.')
116+
bookmarkend.bmrk_id = bookmark.id
117+
return Bookmark(bookmarkend)

0 commit comments

Comments
 (0)