Update RichText

This commit is contained in:
Eric Lapouyade 2015-03-26 11:23:05 +01:00
parent 28f7f6b6dd
commit 1483bae331
8 changed files with 94 additions and 50 deletions

View File

@ -2,4 +2,5 @@ eclipse.preferences.version=1
encoding//docxtpl/__init__.py=utf-8 encoding//docxtpl/__init__.py=utf-8
encoding//tests/cellbg.py=utf-8 encoding//tests/cellbg.py=utf-8
encoding//tests/order.py=utf-8 encoding//tests/order.py=utf-8
encoding//tests/richtext.py=utf-8
encoding//tests/subdoc.py=utf-8 encoding//tests/subdoc.py=utf-8

View File

@ -10,6 +10,7 @@ __version__ = '0.1.1'
from lxml import etree from lxml import etree
from docx import Document from docx import Document
from jinja2 import Template from jinja2 import Template
from cgi import escape
import copy import copy
import re import re
@ -17,27 +18,27 @@ class DocxTemplate(object):
""" Class for managing docx files as they were jinja2 templates """ """ Class for managing docx files as they were jinja2 templates """
def __init__(self, docx): def __init__(self, docx):
self.docx = Document(docx) self.docx = Document(docx)
def __getattr__(self, name): def __getattr__(self, name):
return getattr(self.docx, name) return getattr(self.docx, name)
def get_docx(self): def get_docx(self):
return self.docx return self.docx
def get_xml(self): def get_xml(self):
return etree.tostring(self.docx._element.body, pretty_print=True) return etree.tostring(self.docx._element.body, pretty_print=True)
def write_xml(self,filename): def write_xml(self,filename):
with open(filename,'w') as fh: with open(filename,'w') as fh:
fh.write(self.get_xml()) fh.write(self.get_xml())
def patch_xml(self,src_xml): def patch_xml(self,src_xml):
# strip all xml tags inside {% %} and {{ }} # strip all xml tags inside {% %} and {{ }}
# that Microsoft word can insert into xml code for this part of the document # that Microsoft word can insert into xml code for this part of the document
def striptags(m): def striptags(m):
return re.sub('</w:t>.*?(<w:t>|<w:t [^>]*>)','',m.group(0),flags=re.DOTALL) return re.sub('</w:t>.*?(<w:t>|<w:t [^>]*>)','',m.group(0),flags=re.DOTALL)
src_xml = re.sub(r'{%(?:(?!%}).)*|{{(?:(?!}}).)*',striptags,src_xml,flags=re.DOTALL) src_xml = re.sub(r'{%(?:(?!%}).)*|{{(?:(?!}}).)*',striptags,src_xml,flags=re.DOTALL)
# manage table cell background color # manage table cell background color
def cellbg(m): def cellbg(m):
cell_xml = m.group(1) + m.group(3) cell_xml = m.group(1) + m.group(3)
@ -45,19 +46,20 @@ class DocxTemplate(object):
cell_xml = re.sub(r'<w:shd[^/]*/>','', cell_xml, count=1) cell_xml = re.sub(r'<w:shd[^/]*/>','', cell_xml, count=1)
return re.sub(r'(<w:tcPr[^>]*>)',r'\1<w:shd w:val="clear" w:color="auto" w:fill="{{%s}}"/>' % m.group(2), cell_xml) return re.sub(r'(<w:tcPr[^>]*>)',r'\1<w:shd w:val="clear" w:color="auto" w:fill="{{%s}}"/>' % m.group(2), cell_xml)
src_xml = re.sub(r'(<w:tc[ >](?:(?!<w:tc[ >]).)*){%\s*cellbg\s+([^%]*)\s*%}(.*?</w:tc>)',cellbg,src_xml,flags=re.DOTALL) src_xml = re.sub(r'(<w:tc[ >](?:(?!<w:tc[ >]).)*){%\s*cellbg\s+([^%]*)\s*%}(.*?</w:tc>)',cellbg,src_xml,flags=re.DOTALL)
for y in ['tr', 'p', 'r']: 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 : # by {% xxx %} or {{ xx }} without any surronding xml tags :
# This is mandatory to have jinja2 generating correct xml code # This is mandatory to have jinja2 generating correct xml code
pat = r'<w:%(y)s[ >](?:(?!<w:%(y)s[ >]).)*({%%|{{)%(y)s ([^}%%]*(?:%%}|}})).*?</w:%(y)s>' % {'y':y} pat = r'<w:%(y)s[ >](?:(?!<w:%(y)s[ >]).)*({%%|{{)%(y)s ([^}%%]*(?:%%}|}})).*?</w:%(y)s>' % {'y':y}
src_xml = re.sub(pat, r'\1 \2',src_xml,flags=re.DOTALL) src_xml = re.sub(pat, r'\1 \2',src_xml,flags=re.DOTALL)
return src_xml return src_xml
def render_xml(self,src_xml,context): def render_xml(self,src_xml,context):
template = Template(src_xml) 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 return dst_xml
def build_xml(self,context): def build_xml(self,context):
@ -65,7 +67,7 @@ class DocxTemplate(object):
xml = self.patch_xml(xml) xml = self.patch_xml(xml)
xml = self.render_xml(xml, context) xml = self.render_xml(xml, context)
return xml return xml
def map_xml(self,xml): def map_xml(self,xml):
root = self.docx._element root = self.docx._element
body = root.body body = root.body
@ -74,7 +76,7 @@ class DocxTemplate(object):
def render(self,context): def render(self,context):
xml = self.build_xml(context) xml = self.build_xml(context)
self.map_xml(xml) self.map_xml(xml)
def new_subdoc(self): def new_subdoc(self):
return Subdoc(self) return Subdoc(self)
@ -86,66 +88,71 @@ class Subdoc(object):
self.subdocx = Document() self.subdocx = Document()
self.subdocx._part = self.docx._part self.subdocx._part = self.docx._part
def __getattr__(self, name): def __getattr__(self, name):
return getattr(self.subdocx, name) return getattr(self.subdocx, name)
def __unicode__(self): def __unicode__(self):
xml = '' xml = ''
for p in self.paragraphs: for p in self.paragraphs:
xml += '<w:p>\n' + re.sub(r'^.*\n', '', etree.tostring(p._element,pretty_print=True)) xml += '<w:p>\n' + re.sub(r'^.*\n', '', etree.tostring(p._element,pretty_print=True))
return xml return xml
class RichText(object): class RichText(object):
""" class to generate Rich Text when using templates variables """ 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): def __init__(self, text=None, **text_prop):
self.xml = '' self.xml = ''
if text: if text:
self.add_run(text, **text_prop) self.add(text, **text_prop)
def add(self, text, style=None, def add(self, text, style=None,
color=None, color=None,
highlight=None, highlight=None,
size=None, size=None,
bold=False, bold=False,
italic=False, italic=False,
underline=False, underline=False,
strike=False): strike=False):
prop = ''
if not isinstance(text, unicode):
text = text.decode('utf-8',errors='ignore')
text = escape(text).replace('\n','<w:br/>')
prop = u''
if style: if style:
prop += '<w:rStyle w:val="%s"/>' % style prop += u'<w:rStyle w:val="%s"/>' % style
if color: if color:
if color[0] == '#': if color[0] == '#':
color = color[1:] color = color[1:]
prop += '<w:color w:val="%s"/>' % color prop += u'<w:color w:val="%s"/>' % color
if highlight: if highlight:
if highlight[0] == '#': if highlight[0] == '#':
highlight = highlight[1:] highlight = highlight[1:]
prop += '<w:highlight w:val="%s"/>' % highlight prop += u'<w:highlight w:val="%s"/>' % highlight
if size: if size:
prop += '<w:sz w:val="%s"/>' % size prop += u'<w:sz w:val="%s"/>' % size
prop += '<w:szCs w:val="%s"/>' % size prop += u'<w:szCs w:val="%s"/>' % size
if bold: if bold:
prop += '<w:b/>' prop += u'<w:b/>'
if italic: if italic:
prop += '<w:i/>' prop += u'<w:i/>'
if underline: if underline:
if underline not in ['single','double']: if underline not in ['single','double']:
underline = 'single' underline = 'single'
prop += '<w:u w:val="%s"/>' % underline prop += u'<w:u w:val="%s"/>' % underline
if strike: if strike:
prop += '<w:strike/>' prop += u'<w:strike/>'
self.xml += '<w:r>' self.xml += u'<w:r>'
if prop: if prop:
self.xml += '<w:rPr>%s</w:rPr>' % prop self.xml += u'<w:rPr>%s</w:rPr>' % prop
self.xml += '<w:t>%s</w:t></w:r>\n' % text self.xml += u'<w:t xml:space="preserve">%s</w:t></w:r>\n' % text
def __unicode__(self): def __unicode__(self):
return xml return self.xml

View File

@ -5,17 +5,17 @@ Created : 2015-03-12
@author: Eric Lapouyade @author: Eric Lapouyade
''' '''
from docxtpl import DocxTemplate from docxtpl import DocxTemplate, RichText
tpl=DocxTemplate('test_files/cellbg_tpl.docx') tpl=DocxTemplate('test_files/cellbg_tpl.docx')
context = { context = {
'alerts' : [ 'alerts' : [
{'date' : '2015-03-10', 'desc' : 'Very critical alert', 'type' : 'CRITICAL', 'bg': 'FF0000' }, {'date' : '2015-03-10', 'desc' : RichText('Very critical alert',color='FF0000', bold=True), 'type' : 'CRITICAL', 'bg': 'FF0000' },
{'date' : '2015-03-11', 'desc' : 'Just a warning', 'type' : 'WARNING', 'bg': 'FFDD00' }, {'date' : '2015-03-11', 'desc' : RichText('Just a warning'), 'type' : 'WARNING', 'bg': 'FFDD00' },
{'date' : '2015-03-12', 'desc' : 'Information', 'type' : 'INFO', 'bg': '8888FF' }, {'date' : '2015-03-12', 'desc' : RichText('Information'), 'type' : 'INFO', 'bg': '8888FF' },
{'date' : '2015-03-13', 'desc' : 'Debug trace', 'type' : 'DEBUG', 'bg': 'FF00FF' }, {'date' : '2015-03-13', 'desc' : RichText('Debug trace'), 'type' : 'DEBUG', 'bg': 'FF00FF' },
], ],
} }
tpl.render(context) tpl.render(context)

36
tests/richtext.py Normal file
View File

@ -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<cool>')
context = {
'example' : rt,
}
tpl.render(context)
tpl.save('test_files/richtext.docx')

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.