diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs index 0e5c50a..80bd8f5 100644 --- a/.settings/org.eclipse.core.resources.prefs +++ b/.settings/org.eclipse.core.resources.prefs @@ -2,4 +2,5 @@ eclipse.preferences.version=1 encoding//docxtpl/__init__.py=utf-8 encoding//tests/cellbg.py=utf-8 encoding//tests/order.py=utf-8 +encoding//tests/richtext.py=utf-8 encoding//tests/subdoc.py=utf-8 diff --git a/docxtpl/__init__.py b/docxtpl/__init__.py index 20a659c..9895a93 100644 --- a/docxtpl/__init__.py +++ b/docxtpl/__init__.py @@ -10,6 +10,7 @@ __version__ = '0.1.1' from lxml import etree from docx import Document from jinja2 import Template +from cgi import escape import copy import re @@ -17,27 +18,27 @@ class DocxTemplate(object): """ Class for managing docx files as they were jinja2 templates """ def __init__(self, docx): self.docx = Document(docx) - - def __getattr__(self, name): + + def __getattr__(self, name): return getattr(self.docx, name) - + def get_docx(self): return self.docx - + def get_xml(self): return etree.tostring(self.docx._element.body, pretty_print=True) def write_xml(self,filename): with open(filename,'w') as fh: fh.write(self.get_xml()) - + def patch_xml(self,src_xml): # strip all xml tags inside {% %} and {{ }} # that Microsoft word can insert into xml code for this part of the document def striptags(m): return re.sub('.*?(|]*>)','',m.group(0),flags=re.DOTALL) src_xml = re.sub(r'{%(?:(?!%}).)*|{{(?:(?!}}).)*',striptags,src_xml,flags=re.DOTALL) - + # manage table cell background color def cellbg(m): cell_xml = m.group(1) + m.group(3) @@ -45,19 +46,20 @@ class DocxTemplate(object): cell_xml = re.sub(r'','', cell_xml, count=1) return re.sub(r'(]*>)',r'\1' % m.group(2), cell_xml) src_xml = re.sub(r'(](?:(?!]).)*){%\s*cellbg\s+([^%]*)\s*%}(.*?)',cellbg,src_xml,flags=re.DOTALL) - + for y in ['tr', 'p', 'r']: - # replace into xml code the row/paragraph/run containing {%y xxx %} or {{y xxx}} template tag + # replace into xml code the row/paragraph/run containing {%y xxx %} or {{y xxx}} template tag # by {% xxx %} or {{ xx }} without any surronding xml tags : # This is mandatory to have jinja2 generating correct xml code pat = r'](?:(?!]).)*({%%|{{)%(y)s ([^}%%]*(?:%%}|}})).*?' % {'y':y} src_xml = re.sub(pat, r'\1 \2',src_xml,flags=re.DOTALL) - + return src_xml def render_xml(self,src_xml,context): template = Template(src_xml) - dst_xml = template.render(context) + dst_xml = template.render(context) + dst_xml = dst_xml.replace('{_{','{{').replace('}_}','}}').replace('{_%','{%').replace('%_}','%}') return dst_xml def build_xml(self,context): @@ -65,7 +67,7 @@ class DocxTemplate(object): xml = self.patch_xml(xml) xml = self.render_xml(xml, context) return xml - + def map_xml(self,xml): root = self.docx._element body = root.body @@ -74,7 +76,7 @@ class DocxTemplate(object): def render(self,context): xml = self.build_xml(context) self.map_xml(xml) - + def new_subdoc(self): return Subdoc(self) @@ -86,66 +88,71 @@ class Subdoc(object): self.subdocx = Document() self.subdocx._part = self.docx._part - def __getattr__(self, name): + def __getattr__(self, name): return getattr(self.subdocx, name) - + def __unicode__(self): xml = '' for p in self.paragraphs: xml += '\n' + re.sub(r'^.*\n', '', etree.tostring(p._element,pretty_print=True)) return xml - - + + class RichText(object): """ class to generate Rich Text when using templates variables - - This is much faster than using Subdoc class, but this only for texts INSIDE an existing paragraph. + + This is much faster than using Subdoc class, but this only for texts INSIDE an existing paragraph. """ def __init__(self, text=None, **text_prop): self.xml = '' if text: - self.add_run(text, **text_prop) - - def add(self, text, style=None, - color=None, - highlight=None, - size=None, - bold=False, - italic=False, - underline=False, + self.add(text, **text_prop) + + def add(self, text, style=None, + color=None, + highlight=None, + size=None, + bold=False, + italic=False, + underline=False, strike=False): - - prop = '' + + + if not isinstance(text, unicode): + text = text.decode('utf-8',errors='ignore') + text = escape(text).replace('\n','') + + prop = u'' if style: - prop += '' % style + prop += u'' % style if color: if color[0] == '#': color = color[1:] - prop += '' % color + prop += u'' % color if highlight: if highlight[0] == '#': highlight = highlight[1:] - prop += '' % highlight + prop += u'' % highlight if size: - prop += '' % size - prop += '' % size + prop += u'' % size + prop += u'' % size if bold: - prop += '' + prop += u'' if italic: - prop += '' + prop += u'' if underline: if underline not in ['single','double']: underline = 'single' - prop += '' % underline + prop += u'' % underline if strike: - prop += '' - - self.xml += '' + prop += u'' + + self.xml += u'' if prop: - self.xml += '%s' % prop - self.xml += '%s\n' % text + self.xml += u'%s' % prop + self.xml += u'%s\n' % text def __unicode__(self): - return xml + return self.xml diff --git a/tests/cellbg.py b/tests/cellbg.py index d42ffc9..3e63b55 100644 --- a/tests/cellbg.py +++ b/tests/cellbg.py @@ -5,17 +5,17 @@ Created : 2015-03-12 @author: Eric Lapouyade ''' -from docxtpl import DocxTemplate +from docxtpl import DocxTemplate, RichText tpl=DocxTemplate('test_files/cellbg_tpl.docx') -context = { +context = { 'alerts' : [ - {'date' : '2015-03-10', 'desc' : 'Very critical alert', 'type' : 'CRITICAL', 'bg': 'FF0000' }, - {'date' : '2015-03-11', 'desc' : 'Just a warning', 'type' : 'WARNING', 'bg': 'FFDD00' }, - {'date' : '2015-03-12', 'desc' : 'Information', 'type' : 'INFO', 'bg': '8888FF' }, - {'date' : '2015-03-13', 'desc' : 'Debug trace', 'type' : 'DEBUG', 'bg': 'FF00FF' }, - ], + {'date' : '2015-03-10', 'desc' : RichText('Very critical alert',color='FF0000', bold=True), 'type' : 'CRITICAL', 'bg': 'FF0000' }, + {'date' : '2015-03-11', 'desc' : RichText('Just a warning'), 'type' : 'WARNING', 'bg': 'FFDD00' }, + {'date' : '2015-03-12', 'desc' : RichText('Information'), 'type' : 'INFO', 'bg': '8888FF' }, + {'date' : '2015-03-13', 'desc' : RichText('Debug trace'), 'type' : 'DEBUG', 'bg': 'FF00FF' }, + ], } tpl.render(context) diff --git a/tests/richtext.py b/tests/richtext.py new file mode 100644 index 0000000..781e0e7 --- /dev/null +++ b/tests/richtext.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +''' +Created : 2015-03-26 + +@author: Eric Lapouyade +''' + +from docxtpl import DocxTemplate, RichText + +tpl=DocxTemplate('test_files/richtext_tpl.docx') + +rt = RichText('an exemple of ') +rt.add('a rich text', style='myrichtextstyle') +rt.add(' with ') +rt.add('some italic', italic=True) +rt.add(' and ') +rt.add('some violet', color='#ff00ff') +rt.add(' and ') +rt.add('some striked', strike=True) +rt.add(' and ') +rt.add('some small', size=14) +rt.add(' or ') +rt.add('big', size=60) +rt.add(' text.') +rt.add(' Et voilĂ  ! ') +rt.add('\n1st line') +rt.add('\n2nd line') +rt.add('\n3rd line') +rt.add('\n\n') + +context = { + 'example' : rt, +} + +tpl.render(context) +tpl.save('test_files/richtext.docx') diff --git a/tests/test_files/cellbg.docx b/tests/test_files/cellbg.docx index 478ab71..2512c6b 100644 Binary files a/tests/test_files/cellbg.docx and b/tests/test_files/cellbg.docx differ diff --git a/tests/test_files/cellbg_tpl.docx b/tests/test_files/cellbg_tpl.docx index 1b63f8c..7347855 100644 Binary files a/tests/test_files/cellbg_tpl.docx and b/tests/test_files/cellbg_tpl.docx differ diff --git a/tests/test_files/richtext.docx b/tests/test_files/richtext.docx new file mode 100644 index 0000000..d100cf6 Binary files /dev/null and b/tests/test_files/richtext.docx differ diff --git a/tests/test_files/richtext_tpl.docx b/tests/test_files/richtext_tpl.docx new file mode 100644 index 0000000..bcf589e Binary files /dev/null and b/tests/test_files/richtext_tpl.docx differ