Merge branch 'master' into defer-image-attachment

This commit is contained in:
Eric Lapouyade 2017-09-10 12:33:18 +02:00 committed by GitHub
commit b2295a7d4b
19 changed files with 165 additions and 5 deletions

View File

@ -1,3 +1,12 @@
0.4.0 (2017-09-09)
------------------
- Add replace_media() method (useful for header/footer images)
- Add replace_embedded() method (useful for embedding docx)
0.3.9 (2017-06-27)
------------------
- Fix exception in fix_table()
0.3.8 (2017-06-20)
------------------
- Fix bug when using more than one {{r }} or {%r %} in the same run

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

@ -165,6 +165,37 @@ in your python code ::
in your docx template just use ``{{ mylisting }}``
With ``Listing()``, you will keep the current character styling (except after a ``\a`` as you start a new paragraph).
Replace docx medias
-------------------
It is not possible to dynamically add images in header/footer, but you can change them.
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.
(dummy file name is not important)
Syntax to replace dummy_header_pic.jpg::
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
--------------------
@ -186,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

@ -5,7 +5,7 @@ Created : 2015-03-12
@author: Eric Lapouyade
'''
__version__ = '0.3.8'
__version__ = '0.4.0'
from lxml import etree
from docx import Document
@ -14,6 +14,9 @@ from jinja2 import Template
from cgi import escape
import re
import six
import binascii
import os
import zipfile
NEWLINE = '</w:t><w:br/><w:t xml:space="preserve">'
NEWPARAGRAPH = '</w:t></w:r></w:p><w:p><w:r><w:t xml:space="preserve">'
@ -26,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)
@ -174,13 +179,72 @@ class DocxTemplate(object):
for c in columns:
c.set(ns+'w', str(int(float(c.get(ns+'w')) * new_average/old_average)))
# add new columns
for i in range(to_add):
etree.SubElement(tblGrid, ns+'gridCol', {ns+'w': str(int(new_average))})
for i in range(to_add):
etree.SubElement(tblGrid, ns+'gridCol', {ns+'w': str(int(new_average))})
return tree
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):
@ -310,4 +374,4 @@ class InlineImage(object):
return self._insert_image()
def __str__(self):
return self._insert_image()
return self._insert_image()

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

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
'''
Created : 2017-09-03
@author: Eric Lapouyade
'''
from docxtpl import DocxTemplate
DEST_FILE = 'test_files/header_footer_image.docx'
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)

View File

@ -2,7 +2,7 @@ import subprocess
import glob
import six
tests = glob.glob('[A-Za-z]*.py')
tests = sorted(glob.glob('[A-Za-z]*.py'))
excludes = ['runtests.py']
for test in tests:

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.