def it_can_calculate_relative_ref_value(self): cases = ( ('/', '/ppt/presentation.xml', 'ppt/presentation.xml'), ('/ppt', '/ppt/slideMasters/slideMaster1.xml', 'slideMasters/slideMaster1.xml'), ('/ppt/slides', '/ppt/slideLayouts/slideLayout1.xml', '../slideLayouts/slideLayout1.xml'), ) for baseURI, uri_str, expected_relative_ref in cases: pack_uri = PackURI(uri_str) assert pack_uri.relative_ref(baseURI) == expected_relative_ref
def it_can_calculate_relative_ref_value(self): cases = ( ("/", "/ppt/presentation.xml", "ppt/presentation.xml"), ( "/ppt", "/ppt/slideMasters/slideMaster1.xml", "slideMasters/slideMaster1.xml", ), ( "/ppt/slides", "/ppt/slideLayouts/slideLayout1.xml", "../slideLayouts/slideLayout1.xml", ), ) for baseURI, uri_str, expected_relative_ref in cases: pack_uri = PackURI(uri_str) assert pack_uri.relative_ref(baseURI) == expected_relative_ref
def but_it_returns_None_when_the_part_has_no_rels(self, _blob_reader_prop_): _blob_reader_prop_.return_value = { "/ppt/_rels/presentation.xml.rels": b"blob" } package_reader = PackageReader(None) assert package_reader.rels_xml_for( PackURI("/ppt/slides.slide1.xml")) is None
def it_can_add_a_new_slide(self, slides, slidelayout_, Slide_, slide_): slide = slides.add_slide(slidelayout_) Slide_.new.assert_called_once_with( slidelayout_, PackURI('/ppt/slides/slide3.xml'), slides._prs.package ) slides._prs.relate_to.assert_called_once_with(slide_, RT.SLIDE) slides._sldIdLst.add_sldId.assert_called_once_with(ANY) assert slide is slide_
def _new(cls, package): """ Create and return a standalone, default notes master part based on the built-in template (without any related parts, such as theme). """ partname = PackURI("/ppt/notesMasters/notesMaster1.xml") content_type = CT.PML_NOTES_MASTER notesMaster = CT_NotesMaster.new_default() return NotesMasterPart(partname, content_type, notesMaster, package)
def test_construction_from_file(self): """Image(path) constructor produces correct attribute values""" # exercise --------------------- partname = PackURI('/ppt/media/image1.jpeg') image = Image.new(partname, test_image_path) # verify ----------------------- assert image.ext == 'jpeg' assert image.content_type == 'image/jpeg' assert len(image._blob) == 3277 assert image._desc == 'python-icon.jpeg'
def test_construction_from_file(self): """Image(path) constructor produces correct attribute values""" # exercise --------------------- partname = PackURI('/ppt/media/image1.jpeg') image = Image.new(partname, test_image_path) # verify ----------------------- assert_that(image.ext, is_(equal_to('jpeg'))) assert_that(image.content_type, is_(equal_to('image/jpeg'))) assert_that(len(image._blob), is_(equal_to(3277))) assert_that(image._desc, is_(equal_to('python-icon.jpeg')))
def _rename_images(self): """ Assign partnames like ``/ppt/media/image9.png`` to all images in the collection. The name portion is always ``image``. The number part forms a continuous sequence starting at 1 (e.g. 1, 2, 3, ...). The extension is preserved during renaming. """ for idx, image in enumerate(self._values): partname_str = '/ppt/media/image%d.%s' % (idx + 1, image.ext) image.partname = PackURI(partname_str)
def it_should_have_relative_ref_for_internal_rel(self): """ Internal relationships (TargetMode == 'Internal' in the XML) should have a relative ref, e.g. '../slideLayouts/slideLayout1.xml', for the target_ref attribute. """ part = Mock(name="part", partname=PackURI("/ppt/media/image1.png")) baseURI = "/ppt/slides" rel = _Relationship(None, None, part, baseURI) # external=False assert rel.target_ref == "../media/image1.png"
def test_add_part_preserves_sort_order(self): partname1 = PackURI('/ppt/slides/slide1.xml') partname2 = PackURI('/ppt/slides/slide2.xml') partname3 = PackURI('/ppt/slides/slide3.xml') part1 = Mock(name='part1') part1.partname = partname1 part2 = Mock(name='part2') part2.partname = partname2 part3 = Mock(name='part3') part3.partname = partname3 parts = PartCollection() # exercise --------------------- parts.add_part(part2) parts.add_part(part3) parts.add_part(part1) # verify ----------------------- expected = [partname1, partname2, partname3] actual = [part.partname for part in parts] msg = "expected %s, got %s" % (expected, actual) self.assertEqual(expected, actual, msg)
def _new(cls, package): """ Create and return a standalone, default notes master part based on the built-in template (without any related parts, such as theme). """ return NotesMasterPart( PackURI("/ppt/notesMasters/notesMaster1.xml"), CT.PML_NOTES_MASTER, package, CT_NotesMaster.new_default(), )
def cases(self, expected_values): """ Return list of tuples zipped from uri_str cases and *expected_values*. Raise if lengths don't match. """ uri_str_cases = ["/", "/ppt/presentation.xml", "/ppt/slides/slide1.xml"] if len(expected_values) != len(uri_str_cases): msg = "len(expected_values) differs from len(uri_str_cases)" raise AssertionError(msg) pack_uris = [PackURI(uri_str) for uri_str in uri_str_cases] return zip(pack_uris, expected_values)
def add_part(self, part): """ Insert a new part into the collection such that list remains sorted in logical partname order (e.g. slide10.xml comes after slide9.xml). """ new_partidx = part.partname.idx for idx, seq_part in enumerate(self._values): partidx = PackURI(seq_part.partname).idx if partidx > new_partidx: self._values.insert(idx, part) return self._values.append(part)
def rename_slide_parts(self, rIds): """Assign incrementing partnames to the slide parts identified by `rIds`. Partnames are like `/ppt/slides/slide9.xml` and are assigned in the order their id appears in the `rIds` sequence. The name portion is always ``slide``. The number part forms a continuous sequence starting at 1 (e.g. 1, 2, ... 10, ...). The extension is always ``.xml``. """ for idx, rId in enumerate(rIds): slide_part = self.related_part(rId) slide_part.partname = PackURI("/ppt/slides/slide%d.xml" % (idx + 1))
def test_construction_from_stream(self): """Image(stream) construction produces correct attribute values""" # exercise --------------------- partname = PackURI('/ppt/media/image1.jpeg') with open(test_image_path, 'rb') as f: stream = StringIO(f.read()) image = Image.new(partname, stream) # verify ----------------------- assert image.ext == 'jpg' assert image.content_type == 'image/jpeg' assert len(image._blob) == 3277 assert image._desc == 'image.jpg'
def next_partname_fixture(self, request, iter_parts_): existing_partname_numbers, next_partname_number = request.param package = OpcPackage() parts = [ instance_mock( request, Part, name="part[%d]" % idx, partname="/foo/bar/baz%d.xml" % n ) for idx, n in enumerate(existing_partname_numbers) ] iter_parts_.return_value = iter(parts) partname_template = "/foo/bar/baz%d.xml" expected_partname = PackURI("/foo/bar/baz%d.xml" % next_partname_number) return package, partname_template, expected_partname
def add_fixture(self, package_, slide_part_, notes_master_part_, notes_slide_part_, NotesSlidePart_, new_): partname = PackURI('/ppt/notesSlides/notesSlide42.xml') content_type = CT.PML_NOTES_SLIDE notes = element('p:notes') calls = [ call(notes_master_part_, RT.NOTES_MASTER), call(slide_part_, RT.SLIDE), ] package_.next_partname.return_value = partname new_.return_value = notes return (package_, slide_part_, notes_master_part_, notes_slide_part_, NotesSlidePart_, partname, content_type, notes, calls)
def test__scale_calculates_correct_dimensions(self): """Image._scale() calculates correct dimensions""" # setup ------------------------ test_cases = (((None, None), (Px(204), Px(204))), ((1000, None), (1000, 1000)), ((None, 3000), (3000, 3000)), ((3337, 9999), (3337, 9999))) partname = PackURI('/ppt/media/image1.png') image = Image.new(partname, test_image_path) # verify ----------------------- for params, expected in test_cases: width, height = params assert image._scale(width, height) == expected
def next_partname(self, tmpl): """ Return a |PackURI| instance representing the next available partname matching *tmpl*, which is a printf (%)-style template string containing a single replacement item, a '%d' to be used to insert the integer portion of the partname. Example: '/ppt/slides/slide%d.xml' """ partnames = [part.partname for part in self.iter_parts()] for n in range(1, len(partnames) + 2): candidate_partname = tmpl % n if candidate_partname not in partnames: return PackURI(candidate_partname) raise Exception("ProgrammingError: ran out of candidate_partnames")
def iter_valid_rels(): """Filter out broken relationships such as those pointing to NULL.""" for rel_elm in xml_rels.relationship_lst: # --- Occasionally a PowerPoint plugin or other client will "remove" # --- a relationship simply by "voiding" its Target value, like making # --- it "/ppt/slides/NULL". Skip any relationships linking to a # --- partname that is not present in the package. if rel_elm.targetMode == RTM.INTERNAL: partname = PackURI.from_rel_ref(base_uri, rel_elm.target_ref) if partname not in parts: continue yield _Relationship.from_xml(base_uri, rel_elm, parts)
def it_can_create_a_new_slide_part(self, request, package_, relate_to_): partname = PackURI("/foobar.xml") SlidePart_init_ = initializer_mock(request, SlidePart) slide_layout_part_ = instance_mock(request, SlideLayoutPart) CT_Slide_ = class_mock(request, "pptx.parts.slide.CT_Slide") CT_Slide_.new.return_value = sld = element("c:sld") slide_part = SlidePart.new(partname, package_, slide_layout_part_) SlidePart_init_.assert_called_once_with(partname, CT.PML_SLIDE, sld, package_) slide_part.relate_to.assert_called_once_with(slide_part, slide_layout_part_, RT.SLIDE_LAYOUT) assert isinstance(slide_part, SlidePart)
def load_rels(source_partname, rels): """Populate `xml_rels` dict by traversing relationships depth-first.""" xml_rels[source_partname] = rels visited_partnames.add(source_partname) base_uri = source_partname.baseURI # --- recursion stops when there are no unvisited partnames in rels --- for rel in rels: if rel.targetMode == RTM.EXTERNAL: continue target_partname = PackURI.from_rel_ref(base_uri, rel.target_ref) if target_partname in visited_partnames: continue load_rels(target_partname, self._xml_rels_for(target_partname))
def it_can_write_a_blob(self, pkg_file): # setup ------------------------ pack_uri = PackURI("/part/name.xml") blob = "<BlobbityFooBlob/>".encode("utf-8") # exercise --------------------- pkg_writer = PhysPkgWriter(pkg_file) pkg_writer.write(pack_uri, blob) pkg_writer.close() # verify ----------------------- written_blob_sha1 = hashlib.sha1(blob).hexdigest() zipf = ZipFile(pkg_file, "r") retrieved_blob = zipf.read(pack_uri.membername) zipf.close() retrieved_blob_sha1 = hashlib.sha1(retrieved_blob).hexdigest() assert retrieved_blob_sha1 == written_blob_sha1
def it_can_rename_related_slide_parts(self, request, related_part_): rIds = tuple("rId%d" % n for n in range(5, 0, -1)) slide_parts = tuple( instance_mock(request, SlidePart) for _ in range(5)) related_part_.side_effect = iter(slide_parts) prs_part = PresentationPart(None, None, None, None) prs_part.rename_slide_parts(rIds) assert related_part_.call_args_list == [ call(prs_part, rId) for rId in rIds ] assert [s.partname for s in slide_parts] == [ PackURI("/ppt/slides/slide%d.xml" % (i + 1)) for i in range(len(rIds)) ]
def add_image(self, file): """ Return image part containing the image in *file*, which is either a path to an image file or a file-like object containing an image. If an image instance containing this same image already exists, that instance is returned. If it does not yet exist, a new one is created. """ # use Image constructor to validate and characterize image file partname = PackURI('/ppt/media/image1.jpeg') # dummy just for baseURI image = Image.new(partname, file) # return matching image if found for existing_image in self._values: if existing_image._sha1 == image._sha1: return existing_image # otherwise add it to collection and return new image self._values.append(image) self._rename_images() return image
def next_partname(self, tmpl): """Return |PackURI| next available partname matching `tmpl`. `tmpl` is a printf (%)-style template string containing a single replacement item, a '%d' to be used to insert the integer portion of the partname. Example: '/ppt/slides/slide%d.xml' """ # --- expected next partname is tmpl % n where n is one greater than the number # --- of existing partnames that match tmpl. Speed up finding the next one # --- (maybe) by searching from the end downward rather than from 1 upward. prefix = tmpl[:(tmpl % 42).find("42")] partnames = set(p.partname for p in self.iter_parts() if p.partname.startswith(prefix)) for n in range(len(partnames) + 1, 0, -1): candidate_partname = tmpl % n if candidate_partname not in partnames: return PackURI(candidate_partname) raise Exception( # pragma: no cover "ProgrammingError: ran out of candidate_partnames")
def it_can_write_a_sequence_of_parts(self, request, phys_writer_): parts_ = (instance_mock( request, Part, partname=PackURI("/ppt/%s.xml" % x), blob="blob_%s" % x, rels=instance_mock(request, _Relationships, xml="rels_xml_%s" % x), ) for x in ("a", "b", "c")) package_writer = PackageWriter(None, None, parts_) package_writer._write_parts(phys_writer_) assert phys_writer_.write.call_args_list == [ call("/ppt/a.xml", "blob_a"), call("/ppt/_rels/a.xml.rels", "rels_xml_a"), call("/ppt/b.xml", "blob_b"), call("/ppt/_rels/b.xml.rels", "rels_xml_b"), call("/ppt/c.xml", "blob_c"), call("/ppt/_rels/c.xml.rels", "rels_xml_c"), ]
def next_media_partname(self, ext): """Return |PackURI| instance for next available media partname. Partname is first available, starting at sequence number 1. Empty sequence numbers are reused. *ext* is used as the extension on the returned partname. """ def first_available_media_idx(): media_idxs = sorted([ part.partname.idx for part in self.iter_parts() if part.partname.startswith("/ppt/media/media") ]) for i, media_idx in enumerate(media_idxs): idx = i + 1 if idx < media_idx: return idx return len(media_idxs) + 1 idx = first_available_media_idx() return PackURI("/ppt/media/media%d.%s" % (idx, ext))
def next_image_partname(self, ext): """ Return a |PackURI| instance representing the next available image partname, by sequence number. *ext* is used as the extention on the returned partname. """ def first_available_image_idx(): image_idxs = sorted([ part.partname.idx for part in self.iter_parts() if part.partname.startswith("/ppt/media/image") and part.partname.idx is not None ]) for i, image_idx in enumerate(image_idxs): idx = i + 1 if idx < image_idx: return idx return len(image_idxs) + 1 idx = first_available_image_idx() return PackURI("/ppt/media/image%d.%s" % (idx, ext))
def it_creates_a_new_theme_part_to_help(self, request, package_, theme_part_): XmlPart_ = class_mock( request, "pptx.parts.slide.XmlPart", return_value=theme_part_ ) theme_elm = element("p:theme") method_mock( request, CT_OfficeStyleSheet, "new_default", autospec=False, return_value=theme_elm, ) pn_tmpl = "/ppt/theme/theme%d.xml" partname = PackURI("/ppt/theme/theme2.xml") package_.next_partname.return_value = partname theme_part = NotesMasterPart._new_theme_part(package_) package_.next_partname.assert_called_once_with(pn_tmpl) CT_OfficeStyleSheet.new_default.assert_called_once_with() XmlPart_.assert_called_once_with(partname, CT.OFC_THEME, package_, theme_elm) assert theme_part is theme_part_
def it_can_construct_from_chart_type_and_data(self, request): chart_data_ = instance_mock(request, ChartData, xlsx_blob=b"xlsx-blob") chart_data_.xml_bytes.return_value = b"chart-blob" package_ = instance_mock(request, OpcPackage) package_.next_partname.return_value = PackURI("/ppt/charts/chart42.xml") chart_part_ = instance_mock(request, ChartPart) # --- load() must have autospec turned off to work in Python 2.7 mock --- load_ = method_mock( request, ChartPart, "load", autospec=False, return_value=chart_part_ ) chart_part = ChartPart.new(XCT.RADAR, chart_data_, package_) package_.next_partname.assert_called_once_with("/ppt/charts/chart%d.xml") chart_data_.xml_bytes.assert_called_once_with(XCT.RADAR) load_.assert_called_once_with( "/ppt/charts/chart42.xml", CT.DML_CHART, package_, b"chart-blob" ) chart_part_.chart_workbook.update_from_xlsx_blob.assert_called_once_with( b"xlsx-blob" ) assert chart_part is chart_part_
def it_can_add_a_new_slide(self, request, package_, slide_part_, slide_, relate_to_): slide_layout_ = instance_mock(request, SlideLayout) partname = PackURI("/ppt/slides/slide9.xml") property_mock(request, PresentationPart, "_next_slide_partname", return_value=partname) SlidePart_ = class_mock(request, "pptx.parts.presentation.SlidePart") SlidePart_.new.return_value = slide_part_ relate_to_.return_value = "rId42" slide_layout_part_ = slide_layout_.part slide_part_.slide = slide_ prs_part = PresentationPart(None, None, package_, None) rId, slide = prs_part.add_slide(slide_layout_) SlidePart_.new.assert_called_once_with(partname, package_, slide_layout_part_) prs_part.relate_to.assert_called_once_with(prs_part, slide_part_, RT.SLIDE) assert rId == "rId42" assert slide is slide_
def it_can_construct_from_relative_ref(self): baseURI = '/ppt/slides' relative_ref = '../slideLayouts/slideLayout1.xml' pack_uri = PackURI.from_rel_ref(baseURI, relative_ref) assert pack_uri == '/ppt/slideLayouts/slideLayout1.xml'