diff --git a/CHANGES.rst b/CHANGES.rst index 78fa419..65e9539 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -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) ------------------ diff --git a/README.rst b/README.rst index 86a7db7..57dffc4 100644 --- a/README.rst +++ b/README.rst @@ -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 ------------- diff --git a/docs/index.rst b/docs/index.rst index 62becfe..734ca99 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -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 diff --git a/docxtpl/__init__.py b/docxtpl/__init__.py index 1ea1f44..ca1fdcc 100644 --- a/docxtpl/__init__.py +++ b/docxtpl/__init__.py @@ -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 \ No newline at end of file diff --git a/tests/embedded.py b/tests/embedded.py new file mode 100644 index 0000000..9274a32 --- /dev/null +++ b/tests/embedded.py @@ -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') \ No newline at end of file diff --git a/tests/header_footer_image.py b/tests/header_footer_image.py index 56f34b2..3be62de 100644 --- a/tests/header_footer_image.py +++ b/tests/header_footer_image.py @@ -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')]) \ No newline at end of file +tpl.save(DEST_FILE) \ No newline at end of file diff --git a/tests/test_files/cellbg.docx b/tests/test_files/cellbg.docx index 0a34e07..fb18e65 100644 Binary files a/tests/test_files/cellbg.docx and b/tests/test_files/cellbg.docx differ diff --git a/tests/test_files/dynamic_table.docx b/tests/test_files/dynamic_table.docx index 987bbf2..3605135 100644 Binary files a/tests/test_files/dynamic_table.docx and b/tests/test_files/dynamic_table.docx differ diff --git a/tests/test_files/escape.docx b/tests/test_files/escape.docx index 60e8425..0cff2d0 100644 Binary files a/tests/test_files/escape.docx and b/tests/test_files/escape.docx differ diff --git a/tests/test_files/header_footer.docx b/tests/test_files/header_footer.docx index a69df93..c300a9c 100644 Binary files a/tests/test_files/header_footer.docx and b/tests/test_files/header_footer.docx differ diff --git a/tests/test_files/header_footer_entities.docx b/tests/test_files/header_footer_entities.docx index 4e2f8db..31c949c 100644 Binary files a/tests/test_files/header_footer_entities.docx and b/tests/test_files/header_footer_entities.docx differ diff --git a/tests/test_files/header_footer_utf8.docx b/tests/test_files/header_footer_utf8.docx index 96e8ca9..3777d57 100644 Binary files a/tests/test_files/header_footer_utf8.docx and b/tests/test_files/header_footer_utf8.docx differ diff --git a/tests/test_files/inline_image.docx b/tests/test_files/inline_image.docx index 00a5b30..c229cb6 100644 Binary files a/tests/test_files/inline_image.docx and b/tests/test_files/inline_image.docx differ diff --git a/tests/test_files/nested_for.docx b/tests/test_files/nested_for.docx index 726f613..039b4b6 100644 Binary files a/tests/test_files/nested_for.docx and b/tests/test_files/nested_for.docx differ diff --git a/tests/test_files/order.docx b/tests/test_files/order.docx index f968036..57b4ac3 100644 Binary files a/tests/test_files/order.docx and b/tests/test_files/order.docx differ diff --git a/tests/test_files/richtext.docx b/tests/test_files/richtext.docx index 6a56ad6..57cafb0 100644 Binary files a/tests/test_files/richtext.docx and b/tests/test_files/richtext.docx differ diff --git a/tests/test_files/richtext_and_if.docx b/tests/test_files/richtext_and_if.docx index c7a748c..2751687 100644 Binary files a/tests/test_files/richtext_and_if.docx and b/tests/test_files/richtext_and_if.docx differ diff --git a/tests/test_files/subdoc.docx b/tests/test_files/subdoc.docx index a158bc6..335b804 100644 Binary files a/tests/test_files/subdoc.docx and b/tests/test_files/subdoc.docx differ