- Add replace_embedded() method (useful for embedding docx)

- Add replace_media() method (useful for header/footer images)
This commit is contained in:
Eric Lapouyade 2017-09-09 12:28:22 +02:00
parent b7947ca10c
commit be149b367d
18 changed files with 125 additions and 43 deletions

View File

@ -1,6 +1,7 @@
0.4.0 (2017-09-03)
0.4.0 (2017-09-09)
------------------
- Add replace_medias() utility (useful for header/footer images)
- Add replace_media() method (useful for header/footer images)
- Add replace_embedded() method (useful for embedding docx)
0.3.9 (2017-06-27)
------------------

View File

@ -21,6 +21,11 @@ You save the document as a .docx file (xml format) : it will be your .docx templ
Now you can use python-docx-template to generate as many word documents you want from this .docx template and context variables you will associate.
Share
-----
If you like this project, please rate and share it here : http://rate.re/github/elapouya/python-docx-template
Documentation
-------------

View File

@ -172,13 +172,30 @@ It is not possible to dynamically add images in header/footer, but you can chang
The idea is to put a dummy picture in your template, render the template as usual, then replace the dummy picture with another one.
You can do that for all medias at the same time.
Note: for images, the aspect ratio will be the same as the replaced image
Note2 : it is important to have the source media files as they are required to calculate their CRC to find them in the docx
Note2 : it is important to have the source media files as they are required to calculate their CRC to find them in the docx.
(dummy file name is not important)
Syntax to replace dummy_header_pic.jpg and dummy2.jpg in mydoc.docx::
Syntax to replace dummy_header_pic.jpg::
replace_medias('mydoc.docx',[('dummy_header_pic.jpg','header_pic_i_want.jpg'),('dummy2.png','mycompany.png')])
tpl.replace_media('dummy_header_pic.jpg','header_pic_i_want.jpg')
dummy_header_pic.jpg must exist in the template directory when rendering and saving the generated docx. It must be the same
file as the one inserted manually in the docx template.
Replace embedded objects
------------------------
It works like medias replacement, except it is for embedded objects like embedded docx.
Syntax to replace embedded_dummy.docx::
tpl.replace_embedded('embdded_dummy.docx','embdded_docx_i_want.docx')
embdded_dummy.docx must exist in the template directory when rendering and saving the generated docx. It must be the same
file as the one inserted manually in the docx template.
Jinja custom filters
--------------------
@ -200,7 +217,10 @@ Examples
The best way to see how it works is to read examples, they are located in `tests/` directory. Templates and generated .docx files are in `tests/test_files/`.
Share
-----
If you like this project, please rate and share it here : http://rate.re/github/elapouya/python-docx-template
.. rubric:: Functions index

View File

@ -29,6 +29,8 @@ class DocxTemplate(object):
def __init__(self, docx):
self.docx = Document(docx)
self.crc_to_new_media = {}
self.crc_to_new_embedded = {}
def __getattr__(self, name):
return getattr(self.docx, name)
@ -184,6 +186,65 @@ class DocxTemplate(object):
def new_subdoc(self):
return Subdoc(self)
@staticmethod
def get_file_crc(filename):
with open(filename, 'rb') as fh:
buf = fh.read()
crc = (binascii.crc32(buf) & 0xFFFFFFFF)
return crc
def replace_media(self,src_file,dst_file):
"""Replace one media by another one into a docx
This has been done mainly because it is not possible to add images in docx header/footer.
With this function, put a dummy picture in your header/footer, then specify it with its replacement in this function
Syntax: tpl.replace_media('dummy_media_to_replace.png','media_to_paste.jpg')
Note: for images, the aspect ratio will be the same as the replaced image
Note2 : it is important to have the source media file as it is required to calculate its CRC to find them in the docx
"""
with open(dst_file, 'rb') as fh:
crc = self.get_file_crc(src_file)
self.crc_to_new_media[crc] = fh.read()
def replace_embedded(self,src_file,dst_file):
"""Replace one embdded object by another one into a docx
This has been done mainly because it is not possible to add images in docx header/footer.
With this function, put a dummy picture in your header/footer, then specify it with its replacement in this function
Syntax: tpl.replace_embedded('dummy_doc.docx','doc_to_paste.docx')
Note2 : it is important to have the source file as it is required to calculate its CRC to find them in the docx
"""
with open(dst_file, 'rb') as fh:
crc = self.get_file_crc(src_file)
self.crc_to_new_embedded[crc] = fh.read()
def post_processing(self,docx_filename):
if self.crc_to_new_media or self.crc_to_new_embedded:
backup_filename = '%s_docxtpl_before_replace_medias' % docx_filename
os.rename(docx_filename,backup_filename)
with zipfile.ZipFile(backup_filename) as zin:
with zipfile.ZipFile(docx_filename, 'w') as zout:
for item in zin.infolist():
buf = zin.read(item.filename)
if item.filename.startswith('word/media/') and item.CRC in self.crc_to_new_media:
zout.writestr(item, self.crc_to_new_media[item.CRC])
elif item.filename.startswith('word/embeddings/') and item.CRC in self.crc_to_new_embedded:
zout.writestr(item, self.crc_to_new_embedded[item.CRC])
else:
zout.writestr(item, buf)
os.remove(backup_filename)
def save(self,filename,*args,**kwargs):
self.docx.save(filename,*args,**kwargs)
self.post_processing(filename)
class Subdoc(object):
""" Class for subdocument to insert into master document """
def __init__(self, tpl):
@ -298,37 +359,4 @@ class InlineImage(object):
return self.xml
def __str__(self):
return self.xml
def replace_medias(docx_filename,src_dst_lst):
"""Utility function replace any media by another into a docx
This has been done mainly because it is not possible to add images in docx header/footer.
With this function, put a dummy picture in your header/footer, then specify it with its replacement in this function
Syntax: replace_medias('mydoc.docx',[('dummy_header_pic.jpg','header_pic_i_want.jpg'),('dummy2.png','mycompany.png')])
Note: for images, the aspect ratio will be the same as the replaced image
Note2 : it is important to have the source media files as they are required to calculate their CRC to find them in the docx
"""
crc_to_new_media = {}
for src,dst in src_dst_lst:
with open(src,'rb') as fh:
buf = fh.read()
crc = (binascii.crc32(buf) & 0xFFFFFFFF)
with open(dst,'rb') as fh:
crc_to_new_media[crc] = fh.read()
backup_filename = '%s_before_replace_medias' % docx_filename
os.rename(docx_filename,backup_filename)
with zipfile.ZipFile(backup_filename) as zin:
with zipfile.ZipFile(docx_filename, 'w') as zout:
for item in zin.infolist():
buf = zin.read(item.filename)
if item.filename.startswith('word/media/') and item.CRC in crc_to_new_media:
zout.writestr(item, crc_to_new_media[item.CRC])
else:
zout.writestr(item, buf)
os.remove(backup_filename)
return self.xml

29
tests/embedded.py Normal file
View File

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
'''
Created : 2017-09-09
@author: Eric Lapouyade
'''
from docxtpl import DocxTemplate
# rendering the "dynamic embedded docx":
embedded_docx_tpl=DocxTemplate('test_files/embedded_embedded_docx_tpl.docx')
context = {
'name' : 'John Doe',
}
embedded_docx_tpl.render(context)
embedded_docx_tpl.save('test_files/embedded_embedded_docx.docx')
# rendring the main document :
tpl=DocxTemplate('test_files/embedded_main_tpl.docx')
context = {
'name' : 'John Doe',
}
tpl.replace_embedded('test_files/embedded_dummy.docx','test_files/embedded_static_docx.docx')
tpl.replace_embedded('test_files/embedded_dummy2.docx','test_files/embedded_embedded_docx.docx')
tpl.render(context)
tpl.save('test_files/embedded.docx')

View File

@ -5,7 +5,7 @@ Created : 2017-09-03
@author: Eric Lapouyade
'''
from docxtpl import DocxTemplate, replace_medias
from docxtpl import DocxTemplate
DEST_FILE = 'test_files/header_footer_image.docx'
@ -14,7 +14,6 @@ tpl=DocxTemplate('test_files/header_footer_image_tpl.docx')
context = {
'mycompany' : 'The World Wide company',
}
tpl.replace_media('test_files/dummy_pic_for_header.png','test_files/python.png')
tpl.render(context)
tpl.save(DEST_FILE)
replace_medias(DEST_FILE,[('test_files/dummy_pic_for_header.png','test_files/python.png')])
tpl.save(DEST_FILE)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.