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.
This commit is contained in:
Alan Evangelista 2021-04-20 18:45:17 -03:00
parent d90087d6f7
commit 63cd4c31f7

View File

@ -569,32 +569,33 @@ class DocxTemplate(object):
def pre_processing(self): def pre_processing(self):
if self.pics_to_replace: if self.pics_to_replace:
self.build_pic_map() self._replace_pics()
# Do the actual replacement def _replace_pics(self):
for embedded_file, stream in six.iteritems(self.pics_to_replace): """Replaces pictures xml tags in the docx template with pictures provided by the user"""
if embedded_file not in self.pic_map:
raise ValueError('Picture "%s" not found in the docx template' replaced_pics = {key: False for key in self.pics_to_replace}
% embedded_file)
self.pic_map[embedded_file][1]._blob = stream
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 # Main document
part = self.docx.part part = self.docx.part
self.pic_map.update(self._img_filename_to_part(part)) self._replace_docx_part_pics(part, replaced_pics)
# Header/Footer # Header/Footer
for relid, rel in six.iteritems(self.docx.part.rels): for relid, rel in six.iteritems(part.rels):
if rel.reltype in (REL_TYPE.HEADER, REL_TYPE.FOOTER): if rel.reltype in (REL_TYPE.HEADER, REL_TYPE.FOOTER):
self.pic_map.update(self._img_filename_to_part(rel.target_part)) 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): def get_pic_map(self):
return self.pic_map 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) et = etree.fromstring(doc_part.blob)
@ -617,17 +618,37 @@ class DocxTemplate(object):
else: else:
continue continue
# title=inl.xpath('wp:docPr/@title',namespaces=docx.oxml.ns.nsmap)[0] non_visual_properties = 'pic:pic/pic:nvPicPr/pic:cNvPr/'
name = gd.xpath('pic:pic/pic:nvPicPr/pic:cNvPr/@name', filename = gd.xpath('%s@name' % non_visual_properties,
namespaces=docx.oxml.ns.nsmap)[0] 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[name] = (doc_part.rels[rel].target_ref, part_map[filename] = (doc_part.rels[rel].target_ref,
doc_part.rels[rel].target_part) 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
# FIXME: figure out what exceptions are thrown here and catch more specific exceptions # FIXME: figure out what exceptions are thrown here and catch more specific exceptions
except Exception: except Exception:
continue continue
return part_map self.pic_map.update(part_map)
def build_url_id(self, url): def build_url_id(self, url):
return self.docx._part.relate_to(url, REL_TYPE.HYPERLINK, return self.docx._part.relate_to(url, REL_TYPE.HYPERLINK,