From d90087d6f70a0b2586db049187997d75866f1f0e Mon Sep 17 00:00:00 2001 From: Alan Evangelista Date: Tue, 20 Apr 2021 11:59:45 -0300 Subject: [PATCH 1/2] Rename pic_to_replace to pics_to_replace Latter name is more intuitive because this dictionary may hold the metadata of several pictures. --- docxtpl/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docxtpl/__init__.py b/docxtpl/__init__.py index 734e2d5..81636ab 100644 --- a/docxtpl/__init__.py +++ b/docxtpl/__init__.py @@ -39,7 +39,7 @@ class DocxTemplate(object): self.crc_to_new_media = {} self.crc_to_new_embedded = {} self.zipname_to_replace = {} - self.pic_to_replace = {} + self.pics_to_replace = {} self.pic_map = {} self.current_rendering_part = None @@ -477,10 +477,10 @@ class DocxTemplate(object): if hasattr(dst_file, 'read'): # NOTE: file extension not checked - self.pic_to_replace[embedded_file] = dst_file.read() + self.pics_to_replace[embedded_file] = dst_file.read() else: with open(dst_file, 'rb') as fh: - self.pic_to_replace[embedded_file] = fh.read() + self.pics_to_replace[embedded_file] = fh.read() def replace_embedded(self, src_file, dst_file): """Replace one embedded object by another one into a docx @@ -568,11 +568,11 @@ class DocxTemplate(object): def pre_processing(self): - if self.pic_to_replace: + if self.pics_to_replace: self.build_pic_map() # Do the actual replacement - for embedded_file, stream in six.iteritems(self.pic_to_replace): + for embedded_file, stream in six.iteritems(self.pics_to_replace): if embedded_file not in self.pic_map: raise ValueError('Picture "%s" not found in the docx template' % embedded_file) @@ -581,7 +581,7 @@ class DocxTemplate(object): def build_pic_map(self): """Searches in docx template all the xml pictures tag and store them in pic_map dict""" - if self.pic_to_replace: + if self.pics_to_replace: # Main document part = self.docx.part self.pic_map.update(self._img_filename_to_part(part)) From 63cd4c31f723da0996a9b8f17a9f2c83dd6817c4 Mon Sep 17 00:00:00 2001 From: Alan Evangelista Date: Tue, 20 Apr 2021 18:45:17 -0300 Subject: [PATCH 2/2] Also look for title and description when replacing pictures Before, the only searched picture tag was 'name', which corresponds to the file name of the image added to the docx. Also searching in title and description tags makes creating docx templates to be rendered by this more flexible and easier in document editors which do not support viewing the image's original filename, such as Google Docs. Also, picture replacement was comprised of picture map creation and picture replacing. Both processes were merged in order to implement the above change more easily. --- docxtpl/__init__.py | 71 +++++++++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/docxtpl/__init__.py b/docxtpl/__init__.py index 81636ab..921991a 100644 --- a/docxtpl/__init__.py +++ b/docxtpl/__init__.py @@ -569,32 +569,33 @@ class DocxTemplate(object): def pre_processing(self): if self.pics_to_replace: - self.build_pic_map() + self._replace_pics() - # Do the actual replacement - for embedded_file, stream in six.iteritems(self.pics_to_replace): - if embedded_file not in self.pic_map: - raise ValueError('Picture "%s" not found in the docx template' - % embedded_file) - self.pic_map[embedded_file][1]._blob = stream + def _replace_pics(self): + """Replaces pictures xml tags in the docx template with pictures provided by the user""" - def build_pic_map(self): - """Searches in docx template all the xml pictures tag and store them - in pic_map dict""" - if self.pics_to_replace: - # Main document - part = self.docx.part - self.pic_map.update(self._img_filename_to_part(part)) + replaced_pics = {key: False for key in self.pics_to_replace} - # Header/Footer - for relid, rel in six.iteritems(self.docx.part.rels): - if rel.reltype in (REL_TYPE.HEADER, REL_TYPE.FOOTER): - self.pic_map.update(self._img_filename_to_part(rel.target_part)) + # Main document + part = self.docx.part + self._replace_docx_part_pics(part, replaced_pics) + + # Header/Footer + for relid, rel in six.iteritems(part.rels): + if rel.reltype in (REL_TYPE.HEADER, REL_TYPE.FOOTER): + self._replace_docx_part_pics(rel.target_part, replaced_pics) + + # make sure all template images defined by user were replaced + for img_id, replaced in replaced_pics.items(): + if not replaced: + raise ValueError( + "Picture %s not found in the docx template" % img_id + ) def get_pic_map(self): return self.pic_map - def _img_filename_to_part(self, doc_part): + def _replace_docx_part_pics(self, doc_part, replaced_pics): et = etree.fromstring(doc_part.blob) @@ -617,17 +618,37 @@ class DocxTemplate(object): else: continue - # title=inl.xpath('wp:docPr/@title',namespaces=docx.oxml.ns.nsmap)[0] - name = gd.xpath('pic:pic/pic:nvPicPr/pic:cNvPr/@name', - namespaces=docx.oxml.ns.nsmap)[0] + non_visual_properties = 'pic:pic/pic:nvPicPr/pic:cNvPr/' + filename = gd.xpath('%s@name' % non_visual_properties, + namespaces=docx.oxml.ns.nsmap)[0] + titles = gd.xpath('%s@title' % non_visual_properties, + namespaces=docx.oxml.ns.nsmap) + if titles: + title = titles[0] + else: + title = "" + descriptions = gd.xpath('%s@descr' % non_visual_properties, + namespaces=docx.oxml.ns.nsmap) + if descriptions: + description = descriptions[0] + else: + description = "" + + part_map[filename] = (doc_part.rels[rel].target_ref, + doc_part.rels[rel].target_part) + + # replace data + for img_id, img_data in six.iteritems(self.pics_to_replace): + if img_id == filename or img_id == title or img_id == description: + part_map[filename][1]._blob = img_data + replaced_pics[img_id] = True + break - part_map[name] = (doc_part.rels[rel].target_ref, - doc_part.rels[rel].target_part) # FIXME: figure out what exceptions are thrown here and catch more specific exceptions except Exception: continue - return part_map + self.pic_map.update(part_map) def build_url_id(self, url): return self.docx._part.relate_to(url, REL_TYPE.HYPERLINK,