Skip to content

Commit 770db0a

Browse files
author
Steve Canny
committed
api: add Document.add_paragraph()
Along the way: * move DocumentPart.get_or_add_image_part() into proper sort order
1 parent 3ab7ad1 commit 770db0a

3 files changed

Lines changed: 75 additions & 12 deletions

File tree

docx/api.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,19 @@ def add_inline_picture(self, image_path_or_stream):
3737
"""
3838
return self.inline_shapes.add_picture(image_path_or_stream)
3939

40+
def add_paragraph(self, text='', style=None):
41+
"""
42+
Return a paragraph newly added to the end of the document, populated
43+
with *text* and having paragraph style *style*.
44+
"""
45+
p = self._document_part.add_paragraph()
46+
if text:
47+
r = p.add_run()
48+
r.add_text(text)
49+
if style is not None:
50+
p.style = style
51+
return p
52+
4053
@property
4154
def body(self):
4255
"""

docx/parts/document.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,11 @@ def __init__(self, partname, content_type, document_elm, package):
2828
)
2929
self._element = document_elm
3030

31-
def get_or_add_image_part(self, image_descriptor):
31+
def add_paragraph(self):
3232
"""
33-
Return an ``(image_part, rId)`` 2-tuple for the image identified by
34-
*image_descriptor*. *image_part* is an |Image| instance corresponding
35-
to the image, newly created if no matching image part is found. *rId*
36-
is the key for the relationship between this document part and the
37-
image part, reused if already present, newly created if not.
33+
Return a paragraph newly added to the end of body content.
3834
"""
39-
image_parts = self._package.image_parts
40-
image_part = image_parts.get_or_add_image_part(image_descriptor)
41-
rId = self.relate_to(image_part, RT.IMAGE)
42-
return (image_part, rId)
35+
raise NotImplementedError
4336

4437
@property
4538
def blob(self):
@@ -52,6 +45,19 @@ def body(self):
5245
"""
5346
return _Body(self._element.body)
5447

48+
def get_or_add_image_part(self, image_descriptor):
49+
"""
50+
Return an ``(image_part, rId)`` 2-tuple for the image identified by
51+
*image_descriptor*. *image_part* is an |Image| instance corresponding
52+
to the image, newly created if no matching image part is found. *rId*
53+
is the key for the relationship between this document part and the
54+
image part, reused if already present, newly created if not.
55+
"""
56+
image_parts = self._package.image_parts
57+
image_part = image_parts.get_or_add_image_part(image_descriptor)
58+
rId = self.relate_to(image_part, RT.IMAGE)
59+
return (image_part, rId)
60+
5561
@lazyproperty
5662
def inline_shapes(self):
5763
"""

tests/test_api.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from docx.opc.constants import CONTENT_TYPE as CT
1111
from docx.package import Package
1212
from docx.parts.document import DocumentPart, InlineShapes
13+
from docx.text import Paragraph, Run
1314

1415
from .unitutil import class_mock, instance_mock, method_mock, var_mock
1516

@@ -39,6 +40,23 @@ def it_should_raise_if_not_a_Word_file(self, Package_, package_, docx_):
3940
with pytest.raises(ValueError):
4041
Document._open(docx_)
4142

43+
def it_can_add_an_empty_paragraph(self, add_empty_paragraph_fixture):
44+
document, document_part_, p_ = add_empty_paragraph_fixture
45+
p = document.add_paragraph()
46+
document_part_.add_paragraph.assert_called_once_with()
47+
assert p is p_
48+
49+
def it_can_add_a_paragraph_of_text(self, add_text_paragraph_fixture):
50+
document, text, p_, r_ = add_text_paragraph_fixture
51+
p = document.add_paragraph(text)
52+
p.add_run.assert_called_once_with()
53+
r_.add_text.assert_called_once_with(text)
54+
55+
def it_can_add_a_styled_paragraph(self, add_styled_paragraph_fixture):
56+
document, style, p_ = add_styled_paragraph_fixture
57+
p = document.add_paragraph(style=style)
58+
assert p.style == style
59+
4260
def it_provides_access_to_the_document_body(self, document):
4361
body = document.body
4462
assert body is document._document_part.body
@@ -64,6 +82,10 @@ def it_can_save_the_package(self, save_fixture):
6482

6583
# fixtures -------------------------------------------------------
6684

85+
@pytest.fixture
86+
def add_empty_paragraph_fixture(self, document, document_part_, p_):
87+
return document, document_part_, p_
88+
6789
@pytest.fixture
6890
def add_picture_fixture(self, request, open_, document_part_):
6991
document = Document()
@@ -73,6 +95,16 @@ def add_picture_fixture(self, request, open_, document_part_):
7395
picture_shape_ = inline_shapes.add_picture.return_value
7496
return document, inline_shapes, image_path_, picture_shape_
7597

98+
@pytest.fixture
99+
def add_styled_paragraph_fixture(self, document, p_):
100+
style = 'foobaresque'
101+
return document, style, p_
102+
103+
@pytest.fixture
104+
def add_text_paragraph_fixture(self, document, p_, r_):
105+
text = 'foobar\rbarfoo'
106+
return document, text, p_, r_
107+
76108
@pytest.fixture
77109
def default_docx_(self, request):
78110
return var_mock(request, 'docx.api._default_docx_path')
@@ -82,10 +114,12 @@ def document(self, open_):
82114
return Document()
83115

84116
@pytest.fixture
85-
def document_part_(self, request):
86-
return instance_mock(
117+
def document_part_(self, request, p_):
118+
document_part_ = instance_mock(
87119
request, DocumentPart, content_type=CT.WML_DOCUMENT_MAIN
88120
)
121+
document_part_.add_paragraph.return_value = p_
122+
return document_part_
89123

90124
@pytest.fixture
91125
def docx_(self, request):
@@ -106,6 +140,12 @@ def open_(self, request, document_part_, package_):
106140
def open_fixture(self, docx_, Package_, package_, document_part_):
107141
return docx_, Package_, package_, document_part_
108142

143+
@pytest.fixture
144+
def p_(self, request, r_):
145+
p_ = instance_mock(request, Paragraph)
146+
p_.add_run.return_value = r_
147+
return p_
148+
109149
@pytest.fixture
110150
def Package_(self, request, package_):
111151
Package_ = class_mock(request, 'docx.api.Package')
@@ -118,6 +158,10 @@ def package_(self, request, document_part_):
118158
package_.main_document = document_part_
119159
return package_
120160

161+
@pytest.fixture
162+
def r_(self, request):
163+
return instance_mock(request, Run)
164+
121165
@pytest.fixture
122166
def save_fixture(self, request, open_, package_):
123167
file_ = instance_mock(request, str)

0 commit comments

Comments
 (0)