Skip to content

Commit 06439d0

Browse files
committed
First full working version
1 parent 7e8c9de commit 06439d0

File tree

10 files changed

+245
-38
lines changed

10 files changed

+245
-38
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: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
|Document| and closely related objects
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

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
15-
from .shared import ElementProxy, Emu
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
14+
from docx.shared import ElementProxy, Emu
15+
from docx.text.bookmarks import Bookmarks
1616

1717

1818
class Document(ElementProxy):
@@ -28,6 +28,15 @@ def __init__(self, element, part):
2828
self._part = part
2929
self.__body = None
3030

31+
def bookmarks(self):
32+
return Bookmarks(self._element.body)
33+
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+
3140
def add_heading(self, text='', level=1):
3241
"""
3342
Return a heading paragraph newly added to the end of the document,
@@ -201,6 +210,7 @@ class _Body(BlockItemContainer):
201210
Proxy for ``<w:body>`` element in this document, having primarily a
202211
container role.
203212
"""
213+
204214
def __init__(self, body_elm, parent):
205215
super(_Body, self).__init__(body_elm, parent)
206216
self._body = body_elm

docx/oxml/__init__.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@
88
from __future__ import absolute_import
99

1010
from lxml import etree
11-
12-
from .ns import NamespacePrefixedTag, nsmap
13-
11+
from docx.oxml.ns import NamespacePrefixedTag, nsmap
1412

1513
# configure XML parser
1614
element_class_lookup = etree.ElementNamespaceClassLookup()
@@ -200,3 +198,7 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None):
200198
register_element_cls('w:br', CT_Br)
201199
register_element_cls('w:r', CT_R)
202200
register_element_cls('w:t', CT_Text)
201+
202+
from .bookmark import CT_BookmarkStart, CT_BookmarkEnd
203+
register_element_cls('w:bookmarkEnd', CT_BookmarkEnd)
204+
register_element_cls('w:bookmarkStart', CT_BookmarkStart)

docx/oxml/bookmark.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Custom element classes for bookmarks
4+
"""
5+
6+
from __future__ import (absolute_import, division, print_function,
7+
unicode_literals)
8+
9+
from docx.oxml.simpletypes import ST_RelationshipId, ST_String
10+
from docx.oxml.xmlchemy import BaseOxmlElement, RequiredAttribute
11+
12+
13+
class CT_BookmarkStart(BaseOxmlElement):
14+
"""The ``<w:bookmarkStart>`` element"""
15+
name = RequiredAttribute('w:name', ST_String)
16+
bmrk_id = RequiredAttribute('w:id', ST_RelationshipId)
17+
18+
@property
19+
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+
"""
24+
root = self.getroottree().getroot()
25+
return str(len(root.xpath('.//w:bookmarkStart')))
26+
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
42+
43+
44+
class CT_BookmarkEnd(BaseOxmlElement):
45+
"""The ``<w:bookmarkEnd>`` element."""
46+
bmrk_id = RequiredAttribute('w:id', ST_RelationshipId)
47+
48+
@property
49+
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+
"""
54+
root = self.getroottree().getroot()
55+
return str(len(root.xpath('.//w:bookmarkStart')))
56+
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/document.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<w:document>.
66
"""
77

8-
from .xmlchemy import BaseOxmlElement, ZeroOrOne, ZeroOrMore
8+
from docx.oxml.xmlchemy import BaseOxmlElement, ZeroOrOne, ZeroOrMore
99

1010

1111
class CT_Document(BaseOxmlElement):
@@ -30,6 +30,8 @@ class CT_Body(BaseOxmlElement):
3030
"""
3131
p = ZeroOrMore('w:p', successors=('w:sectPr',))
3232
tbl = ZeroOrMore('w:tbl', successors=('w:sectPr',))
33+
bookmarkStart = ZeroOrMore('w:bookmarkStart', successors=('w:sectPr',))
34+
bookmarkEnd = ZeroOrMore('w:bookmarkEnd', successors=('w:sectPr',))
3335
sectPr = ZeroOrOne('w:sectPr', successors=())
3436

3537
def add_section_break(self):

docx/oxml/text/paragraph.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
Custom element classes related to paragraphs (CT_P).
55
"""
66

7-
from ..ns import qn
8-
from ..xmlchemy import BaseOxmlElement, OxmlElement, ZeroOrMore, ZeroOrOne
7+
from docx.oxml.ns import qn
8+
from docx.oxml.xmlchemy import (BaseOxmlElement, OxmlElement, ZeroOrMore,
9+
ZeroOrOne)
910

1011

1112
class CT_P(BaseOxmlElement):
@@ -14,6 +15,8 @@ class CT_P(BaseOxmlElement):
1415
"""
1516
pPr = ZeroOrOne('w:pPr')
1617
r = ZeroOrMore('w:r')
18+
bookmarkStart = ZeroOrMore('w:bookmarkStart', successors=('w:sectPr',))
19+
bookmarkEnd = ZeroOrMore('w:bookmarkEnd', successors=('w:sectPr',))
1720

1821
def _insert_pPr(self, pPr):
1922
self.insert(0, pPr)

docx/oxml/text/run.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@
44
Custom element classes related to text runs (CT_R).
55
"""
66

7-
from ..ns import qn
8-
from ..simpletypes import ST_BrClear, ST_BrType
9-
from ..xmlchemy import (
10-
BaseOxmlElement, OptionalAttribute, ZeroOrMore, ZeroOrOne
11-
)
7+
from docx.oxml.ns import qn
8+
from docx.oxml.simpletypes import ST_BrClear, ST_BrType
9+
from docx.oxml.xmlchemy import (BaseOxmlElement, OptionalAttribute, ZeroOrMore,
10+
ZeroOrOne)
1211

1312

1413
class CT_Br(BaseOxmlElement):
@@ -29,6 +28,8 @@ class CT_R(BaseOxmlElement):
2928
cr = ZeroOrMore('w:cr')
3029
tab = ZeroOrMore('w:tab')
3130
drawing = ZeroOrMore('w:drawing')
31+
bookmarkEnd = ZeroOrMore('w:bookmarkEnd', successors=('w:sectPr',))
32+
bookmarkStart = ZeroOrMore('w:bookmarkStart', successors=('w:sectPr',))
3233

3334
def _insert_rPr(self, rPr):
3435
self.insert(0, rPr)

docx/text/bookmarks.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# encoding: utf-8
2+
3+
"""
4+
Bookmarks-related proxy types.
5+
"""
6+
7+
from __future__ import (absolute_import, division, print_function,
8+
unicode_literals)
9+
10+
from collections import Sequence
11+
12+
from docx.shared import ElementProxy
13+
14+
15+
class Bookmarks(Sequence):
16+
def __init__(self, document_elm):
17+
super(Bookmarks, self).__init__()
18+
self._document = self._element = document_elm
19+
20+
def __iter__(self):
21+
"""Enables list like iteration of the bookmark starts. """
22+
for bookmarkStart in self._bookmarkStarts:
23+
yield Bookmark(bookmarkStart)
24+
25+
def __getitem__(self, idx):
26+
"""Provides list like access to the bookmarks """
27+
bookmarkStart = self._bookmarkStarts[idx]
28+
return Bookmark(bookmarkStart)
29+
30+
def __len__(self):
31+
"""
32+
Returns the total count of ``<w:bookmarkStart>`` elements in the
33+
document
34+
"""
35+
return len(self._bookmarkStarts)
36+
37+
def get(self, name, default=None):
38+
"""
39+
Get method which returns the bookmark corresponding to the name
40+
provided.
41+
"""
42+
for bookmarkStart in self._bookmarkStarts:
43+
if bookmarkStart.name == name:
44+
return Bookmark(bookmarkStart)
45+
return default
46+
47+
@property
48+
def _bookmarkStarts(self):
49+
"""Returns a list of ``<w:bookmarkStart>`` elements """
50+
return self._document.xpath('.//w:bookmarkStart')
51+
52+
@property
53+
def _bookmarkEnds(self):
54+
"""Returns a list of ``<w:bookmarkEnd>`` elements """
55+
return self._document.xpath('.//w:bookmarkEnd')
56+
57+
58+
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+
"""
63+
def __init__(self, doc_element):
64+
super(Bookmark, self).__init__(doc_element)
65+
self._element = doc_element
66+
67+
@property
68+
def id(self):
69+
""" Returns the element's unique identifier."""
70+
return self._element.bmrk_id
71+
72+
@property
73+
def name(self):
74+
""" Returns the element's name."""
75+
return self._element.name
76+
77+
@property
78+
def is_closed(self):
79+
""" If True, the bookmark is closed. """
80+
return self._element.is_closed
81+
82+
83+
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+
"""
89+
def start_bookmark(self, 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
100+
return Bookmark(bookmarkstart)
101+
102+
def end_bookmark(self, bookmark=None):
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()
109+
if bookmark is None:
110+
bookmarkend.bmrk_id = bookmarkend._next_id
111+
if bookmarkend.is_closed:
112+
raise ValueError('Cannot end closed bookmark.')
113+
else:
114+
if bookmark.is_closed:
115+
raise ValueError('Cannot end closed bookmark.')
116+
bookmarkend.bmrk_id = bookmark.id
117+
return Bookmark(bookmarkend)

docx/text/paragraph.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,20 @@
44
Paragraph-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

11-
from ..enum.style import WD_STYLE_TYPE
12-
from .parfmt import ParagraphFormat
13-
from .run import Run
14-
from ..shared import Parented
10+
from docx.enum.style import WD_STYLE_TYPE
11+
from docx.shared import Parented
12+
from docx.text.bookmarks import Bookmark, BookmarkParent
13+
from docx.text.parfmt import ParagraphFormat
14+
from docx.text.run import Run
1515

16-
17-
class Paragraph(Parented):
16+
class Paragraph(Parented, BookmarkParent):
1817
"""
1918
Proxy object wrapping ``<w:p>`` element.
2019
"""
20+
2121
def __init__(self, p, parent):
2222
super(Paragraph, self).__init__(parent)
2323
self._p = self._element = p

docx/text/run.py

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

77
from __future__ import absolute_import, print_function, unicode_literals
88

9-
from ..enum.style import WD_STYLE_TYPE
10-
from ..enum.text import WD_BREAK
11-
from .font import Font
12-
from ..shape import InlineShape
13-
from ..shared import Parented
9+
from docx.enum.style import WD_STYLE_TYPE
10+
from docx.enum.text import WD_BREAK
11+
from docx.shape import InlineShape
12+
from docx.shared import Parented
13+
from docx.text.bookmarks import Bookmark, BookmarkParent
14+
from docx.text.font import Font
1415

1516

16-
class Run(Parented):
17+
class Run(Parented, BookmarkParent):
1718
"""
1819
Proxy object wrapping ``<w:r>`` element. Several of the properties on Run
1920
take a tri-state value, |True|, |False|, or |None|. |True| and |False|

0 commit comments

Comments
 (0)