Skip to content

Commit e648135

Browse files
author
Steve Canny
committed
shp: migrate InlineShapes to docx.shape
1 parent 8202bb7 commit e648135

File tree

8 files changed

+200
-193
lines changed

8 files changed

+200
-193
lines changed

docx/parts/document.py

Lines changed: 7 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,19 @@
1212
from .numbering import NumberingPart
1313
from ..opc.constants import RELATIONSHIP_TYPE as RT
1414
from ..opc.part import XmlPart
15-
from ..shape import InlineShape
16-
from ..shared import lazyproperty, Parented
15+
from ..shape import InlineShapes
16+
from ..shared import lazyproperty
1717
from .styles import StylesPart
1818

1919

2020
class DocumentPart(XmlPart):
2121
"""
2222
Main document part of a WordprocessingML (WML) package, aka a .docx file.
23+
Acts as broker to other parts such as image, core properties, and style
24+
parts. It also acts as a convenient delegate when a mid-document object
25+
needs a service involving a remote ancestor. The `Parented.part` property
26+
inherited by many content objects provides access to this part object for
27+
that purpose.
2328
"""
2429
@property
2530
def core_properties(self):
@@ -129,49 +134,3 @@ def _styles_part(self):
129134
styles_part = StylesPart.default(self.package)
130135
self.relate_to(styles_part, RT.STYLES)
131136
return styles_part
132-
133-
134-
class InlineShapes(Parented):
135-
"""
136-
Sequence of |InlineShape| instances, supporting len(), iteration, and
137-
indexed access.
138-
"""
139-
def __init__(self, body_elm, parent):
140-
super(InlineShapes, self).__init__(parent)
141-
self._body = body_elm
142-
143-
def __getitem__(self, idx):
144-
"""
145-
Provide indexed access, e.g. 'inline_shapes[idx]'
146-
"""
147-
try:
148-
inline = self._inline_lst[idx]
149-
except IndexError:
150-
msg = "inline shape index [%d] out of range" % idx
151-
raise IndexError(msg)
152-
return InlineShape(inline)
153-
154-
def __iter__(self):
155-
return (InlineShape(inline) for inline in self._inline_lst)
156-
157-
def __len__(self):
158-
return len(self._inline_lst)
159-
160-
def add_picture(self, image_descriptor, run):
161-
"""
162-
Return an |InlineShape| instance containing the picture identified by
163-
*image_descriptor* and added to the end of *run*. The picture shape
164-
has the native size of the image. *image_descriptor* can be a path (a
165-
string) or a file-like object containing a binary image.
166-
"""
167-
image_part, rId = self.part.get_or_add_image_part(image_descriptor)
168-
shape_id = self.part.next_id
169-
r = run._r
170-
picture = InlineShape.new_picture(r, image_part, rId, shape_id)
171-
return picture
172-
173-
@property
174-
def _inline_lst(self):
175-
body = self._body
176-
xpath = '//w:p/w:r/w:drawing/wp:inline'
177-
return body.xpath(xpath)

docx/shape.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,53 @@
1212
from .enum.shape import WD_INLINE_SHAPE
1313
from .oxml.shape import CT_Inline, CT_Picture
1414
from .oxml.ns import nsmap
15+
from .shared import Parented
16+
17+
18+
class InlineShapes(Parented):
19+
"""
20+
Sequence of |InlineShape| instances, supporting len(), iteration, and
21+
indexed access.
22+
"""
23+
def __init__(self, body_elm, parent):
24+
super(InlineShapes, self).__init__(parent)
25+
self._body = body_elm
26+
27+
def __getitem__(self, idx):
28+
"""
29+
Provide indexed access, e.g. 'inline_shapes[idx]'
30+
"""
31+
try:
32+
inline = self._inline_lst[idx]
33+
except IndexError:
34+
msg = "inline shape index [%d] out of range" % idx
35+
raise IndexError(msg)
36+
return InlineShape(inline)
37+
38+
def __iter__(self):
39+
return (InlineShape(inline) for inline in self._inline_lst)
40+
41+
def __len__(self):
42+
return len(self._inline_lst)
43+
44+
def add_picture(self, image_descriptor, run):
45+
"""
46+
Return an |InlineShape| instance containing the picture identified by
47+
*image_descriptor* and added to the end of *run*. The picture shape
48+
has the native size of the image. *image_descriptor* can be a path (a
49+
string) or a file-like object containing a binary image.
50+
"""
51+
image_part, rId = self.part.get_or_add_image_part(image_descriptor)
52+
shape_id = self.part.next_id
53+
r = run._r
54+
picture = InlineShape.new_picture(r, image_part, rId, shape_id)
55+
return picture
56+
57+
@property
58+
def _inline_lst(self):
59+
body = self._body
60+
xpath = '//w:p/w:r/w:drawing/wp:inline'
61+
return body.xpath(xpath)
1562

1663

1764
class InlineShape(object):

features/steps/document.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from docx import Document
1212
from docx.enum.section import WD_ORIENT, WD_SECTION
13-
from docx.parts.document import InlineShapes
13+
from docx.shape import InlineShapes
1414
from docx.shared import Inches
1515
from docx.section import Sections
1616
from docx.styles.styles import Styles

features/steps/shape.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from docx import Document
1414
from docx.enum.shape import WD_INLINE_SHAPE
15-
from docx.parts.document import InlineShape
15+
from docx.shape import InlineShape
1616
from docx.shared import Inches
1717

1818
from helpers import test_docx, test_file

tests/parts/test_document.py

Lines changed: 2 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,19 @@
1010

1111
from docx.opc.constants import RELATIONSHIP_TYPE as RT
1212
from docx.opc.coreprops import CoreProperties
13-
from docx.oxml.parts.document import CT_Body
14-
from docx.oxml.text.run import CT_R
1513
from docx.package import ImageParts, Package
16-
from docx.parts.document import DocumentPart, InlineShapes
14+
from docx.parts.document import DocumentPart
1715
from docx.parts.image import ImagePart
1816
from docx.parts.numbering import NumberingPart
1917
from docx.parts.styles import StylesPart
20-
from docx.shape import InlineShape
2118
from docx.styles.style import BaseStyle
2219
from docx.styles.styles import Styles
2320
from docx.text.paragraph import Paragraph
24-
from docx.text.run import Run
2521

2622
from ..oxml.parts.unitdata.document import a_body, a_document
2723
from ..oxml.unitdata.text import a_p
28-
from ..unitutil.cxml import element
2924
from ..unitutil.mock import (
30-
instance_mock, class_mock, loose_mock, method_mock, property_mock
25+
instance_mock, class_mock, method_mock, property_mock
3126
)
3227

3328

@@ -300,133 +295,3 @@ def styles_prop_(self, request, styles_):
300295
@pytest.fixture
301296
def _styles_part_prop_(self, request):
302297
return property_mock(request, DocumentPart, '_styles_part')
303-
304-
305-
class DescribeInlineShapes(object):
306-
307-
def it_knows_how_many_inline_shapes_it_contains(
308-
self, inline_shapes_fixture):
309-
inline_shapes, expected_count = inline_shapes_fixture
310-
assert len(inline_shapes) == expected_count
311-
312-
def it_can_iterate_over_its_InlineShape_instances(
313-
self, inline_shapes_fixture):
314-
inline_shapes, inline_shape_count = inline_shapes_fixture
315-
actual_count = 0
316-
for inline_shape in inline_shapes:
317-
assert isinstance(inline_shape, InlineShape)
318-
actual_count += 1
319-
assert actual_count == inline_shape_count
320-
321-
def it_provides_indexed_access_to_inline_shapes(
322-
self, inline_shapes_fixture):
323-
inline_shapes, inline_shape_count = inline_shapes_fixture
324-
for idx in range(-inline_shape_count, inline_shape_count):
325-
inline_shape = inline_shapes[idx]
326-
assert isinstance(inline_shape, InlineShape)
327-
328-
def it_raises_on_indexed_access_out_of_range(
329-
self, inline_shapes_fixture):
330-
inline_shapes, inline_shape_count = inline_shapes_fixture
331-
with pytest.raises(IndexError):
332-
too_low = -1 - inline_shape_count
333-
inline_shapes[too_low]
334-
with pytest.raises(IndexError):
335-
too_high = inline_shape_count
336-
inline_shapes[too_high]
337-
338-
def it_can_add_an_inline_picture_to_the_document(
339-
self, add_picture_fixture):
340-
# fixture ----------------------
341-
(inline_shapes, image_descriptor_, document_, InlineShape_,
342-
run, r_, image_part_, rId_, shape_id_, new_picture_shape_
343-
) = add_picture_fixture
344-
# exercise ---------------------
345-
picture_shape = inline_shapes.add_picture(image_descriptor_, run)
346-
# verify -----------------------
347-
document_.get_or_add_image_part.assert_called_once_with(
348-
image_descriptor_
349-
)
350-
InlineShape_.new_picture.assert_called_once_with(
351-
r_, image_part_, rId_, shape_id_
352-
)
353-
assert picture_shape is new_picture_shape_
354-
355-
def it_knows_the_part_it_belongs_to(self, inline_shapes_with_parent_):
356-
inline_shapes, parent_ = inline_shapes_with_parent_
357-
part = inline_shapes.part
358-
assert part is parent_.part
359-
360-
# fixtures -------------------------------------------------------
361-
362-
@pytest.fixture
363-
def add_picture_fixture(
364-
self, request, body_, document_, image_descriptor_, InlineShape_,
365-
r_, image_part_, rId_, shape_id_, new_picture_shape_):
366-
inline_shapes = InlineShapes(body_, None)
367-
property_mock(request, InlineShapes, 'part', return_value=document_)
368-
run = Run(r_, None)
369-
return (
370-
inline_shapes, image_descriptor_, document_, InlineShape_, run,
371-
r_, image_part_, rId_, shape_id_, new_picture_shape_
372-
)
373-
374-
@pytest.fixture
375-
def inline_shapes_fixture(self):
376-
body = element(
377-
'w:body/w:p/(w:r/w:drawing/wp:inline, w:r/w:drawing/wp:inline)'
378-
)
379-
inline_shapes = InlineShapes(body, None)
380-
expected_count = 2
381-
return inline_shapes, expected_count
382-
383-
# fixture components ---------------------------------------------
384-
385-
@pytest.fixture
386-
def body_(self, request, r_):
387-
body_ = instance_mock(request, CT_Body)
388-
body_.add_p.return_value.add_r.return_value = r_
389-
return body_
390-
391-
@pytest.fixture
392-
def document_(self, request, rId_, image_part_, shape_id_):
393-
document_ = instance_mock(request, DocumentPart, name='document_')
394-
document_.get_or_add_image_part.return_value = image_part_, rId_
395-
document_.next_id = shape_id_
396-
return document_
397-
398-
@pytest.fixture
399-
def image_part_(self, request):
400-
return instance_mock(request, ImagePart)
401-
402-
@pytest.fixture
403-
def image_descriptor_(self, request):
404-
return instance_mock(request, str)
405-
406-
@pytest.fixture
407-
def InlineShape_(self, request, new_picture_shape_):
408-
InlineShape_ = class_mock(request, 'docx.parts.document.InlineShape')
409-
InlineShape_.new_picture.return_value = new_picture_shape_
410-
return InlineShape_
411-
412-
@pytest.fixture
413-
def inline_shapes_with_parent_(self, request):
414-
parent_ = loose_mock(request, name='parent_')
415-
inline_shapes = InlineShapes(None, parent_)
416-
return inline_shapes, parent_
417-
418-
@pytest.fixture
419-
def new_picture_shape_(self, request):
420-
return instance_mock(request, InlineShape)
421-
422-
@pytest.fixture
423-
def r_(self, request):
424-
return instance_mock(request, CT_R)
425-
426-
@pytest.fixture
427-
def rId_(self, request):
428-
return instance_mock(request, str)
429-
430-
@pytest.fixture
431-
def shape_id_(self, request):
432-
return instance_mock(request, int)

tests/test_document.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
from docx.enum.section import WD_SECTION
1515
from docx.enum.text import WD_BREAK
1616
from docx.opc.coreprops import CoreProperties
17-
from docx.parts.document import DocumentPart, InlineShapes
17+
from docx.parts.document import DocumentPart
1818
from docx.section import Sections
19-
from docx.shape import InlineShape
19+
from docx.shape import InlineShape, InlineShapes
2020
from docx.styles.styles import Styles
2121
from docx.table import Table
2222
from docx.text.paragraph import Paragraph

0 commit comments

Comments
 (0)