Skip to content

Commit b7d39cd

Browse files
author
Steve Canny
committed
img: add _MarkerParser.iter_markers()
1 parent 6456d86 commit b7d39cd

File tree

2 files changed

+103
-2
lines changed

2 files changed

+103
-2
lines changed

docx/image/jpeg.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,13 +123,38 @@ def iter_markers(self):
123123
Generate a (marker_code, segment_offset) 2-tuple for each marker in
124124
the JPEG *stream*, in the order they occur in the stream.
125125
"""
126-
raise NotImplementedError
126+
marker_finder = _MarkerFinder.from_stream(self._stream)
127+
start = 0
128+
marker_code = None
129+
while marker_code != JPEG_MARKER_CODE.EOI:
130+
marker_code, segment_offset = marker_finder.next(start)
131+
marker = _MarkerFactory(
132+
marker_code, self._stream, segment_offset
133+
)
134+
yield marker
135+
start = segment_offset + marker.segment_length
127136

128137

129138
class _MarkerFinder(object):
130139
"""
131140
Service class that knows how to find the next JFIF marker in a stream.
132141
"""
142+
@classmethod
143+
def from_stream(cls, stream):
144+
"""
145+
Return a |_MarkerFinder| instance to find JFIF markers in *stream*.
146+
"""
147+
raise NotImplementedError
148+
149+
def next(self, start):
150+
"""
151+
Return a (marker_code, segment_offset) 2-tuple identifying and
152+
locating the first marker in *stream* occuring after offset *start*.
153+
The returned *segment_offset* points to the position immediately
154+
following the 2-byte marker code, the start of the marker segment,
155+
for those markers that have a segment.
156+
"""
157+
raise NotImplementedError
133158

134159

135160
def _MarkerFactory(marker_code, stream, offset):
@@ -153,6 +178,13 @@ def marker_code(self):
153178
"""
154179
raise NotImplementedError
155180

181+
@property
182+
def segment_length(self):
183+
"""
184+
The length in bytes of this marker's segment
185+
"""
186+
raise NotImplementedError
187+
156188

157189
class _App0Marker(_Marker):
158190
"""

tests/image/test_jpeg.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@
88

99
import pytest
1010

11+
from mock import call
12+
1113
from docx.compat import BytesIO
1214
from docx.image.constants import JPEG_MARKER_CODE
1315
from docx.image.helpers import BIG_ENDIAN, StreamReader
1416
from docx.image.jpeg import (
15-
_App0Marker, Jfif, _JfifMarkers, _Marker, _MarkerParser, _SofMarker
17+
_App0Marker, Jfif, _JfifMarkers, _Marker, _MarkerFinder, _MarkerParser,
18+
_SofMarker
1619
)
1720

1821
from ..unitutil import class_mock, initializer_mock, instance_mock
@@ -159,18 +162,84 @@ def it_can_construct_from_a_jfif_stream(self, from_stream_fixture):
159162
_MarkerParser__init_.assert_called_once_with(stream_reader_)
160163
assert isinstance(marker_parser, _MarkerParser)
161164

165+
def it_can_iterate_over_the_jfif_markers_in_its_stream(
166+
self, iter_markers_fixture):
167+
(marker_parser, stream_, _MarkerFinder_, marker_finder_,
168+
_MarkerFactory_, marker_codes, offsets,
169+
marker_lst) = iter_markers_fixture
170+
markers = [marker for marker in marker_parser.iter_markers()]
171+
_MarkerFinder_.from_stream.assert_called_once_with(stream_)
172+
assert marker_finder_.next.call_args_list == [
173+
call(0), call(2), call(20)
174+
]
175+
assert _MarkerFactory_.call_args_list == [
176+
call(marker_codes[0], stream_, offsets[0]),
177+
call(marker_codes[1], stream_, offsets[1]),
178+
call(marker_codes[2], stream_, offsets[2]),
179+
]
180+
assert markers == marker_lst
181+
162182
# fixtures -------------------------------------------------------
163183

184+
@pytest.fixture
185+
def app0_(self, request):
186+
return instance_mock(request, _App0Marker, segment_length=16)
187+
188+
@pytest.fixture
189+
def eoi_(self, request):
190+
return instance_mock(request, _Marker, segment_length=0)
191+
164192
@pytest.fixture
165193
def from_stream_fixture(
166194
self, stream_, StreamReader_, _MarkerParser__init_,
167195
stream_reader_):
168196
return stream_, StreamReader_, _MarkerParser__init_, stream_reader_
169197

198+
@pytest.fixture
199+
def iter_markers_fixture(
200+
self, stream_reader_, _MarkerFinder_, marker_finder_,
201+
_MarkerFactory_, soi_, app0_, eoi_):
202+
marker_parser = _MarkerParser(stream_reader_)
203+
offsets = [2, 4, 22]
204+
marker_lst = [soi_, app0_, eoi_]
205+
marker_finder_.next.side_effect = [
206+
(JPEG_MARKER_CODE.SOI, offsets[0]),
207+
(JPEG_MARKER_CODE.APP0, offsets[1]),
208+
(JPEG_MARKER_CODE.EOI, offsets[2]),
209+
]
210+
marker_codes = [
211+
JPEG_MARKER_CODE.SOI, JPEG_MARKER_CODE.APP0, JPEG_MARKER_CODE.EOI
212+
]
213+
return (
214+
marker_parser, stream_reader_, _MarkerFinder_, marker_finder_,
215+
_MarkerFactory_, marker_codes, offsets, marker_lst
216+
)
217+
218+
@pytest.fixture
219+
def _MarkerFactory_(self, request, soi_, app0_, eoi_):
220+
return class_mock(
221+
request, 'docx.image.jpeg._MarkerFactory',
222+
side_effect=[soi_, app0_, eoi_]
223+
)
224+
225+
@pytest.fixture
226+
def _MarkerFinder_(self, request, marker_finder_):
227+
_MarkerFinder_ = class_mock(request, 'docx.image.jpeg._MarkerFinder')
228+
_MarkerFinder_.from_stream.return_value = marker_finder_
229+
return _MarkerFinder_
230+
231+
@pytest.fixture
232+
def marker_finder_(self, request):
233+
return instance_mock(request, _MarkerFinder)
234+
170235
@pytest.fixture
171236
def _MarkerParser__init_(self, request):
172237
return initializer_mock(request, _MarkerParser)
173238

239+
@pytest.fixture
240+
def soi_(self, request):
241+
return instance_mock(request, _Marker, segment_length=0)
242+
174243
@pytest.fixture
175244
def stream_(self, request):
176245
return instance_mock(request, BytesIO)

0 commit comments

Comments
 (0)