[tryton-debian-vcs] relatorio branch upstream updated. upstream/0.5.7-1-g5305f6d
Mathias Behrle
tryton-debian-vcs at alioth.debian.org
Thu Nov 28 18:46:26 UTC 2013
The following commit has been merged in the upstream branch:
https://alioth.debian.org/plugins/scmgit/cgi-bin/gitweb.cgi/?p=tryton/relatorio.git;a=commitdiff;h=upstream/0.5.7-1-g5305f6d
commit 5305f6de86a7f60007366c55d31b9b5385f8ebfb
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Thu Nov 28 19:45:55 2013 +0100
Adding upstream version 0.6.0.
diff --git a/CHANGES b/CHANGES
index c466577..15bec1b 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,7 @@
+0.6.0 - 20130810
+ * Add support for Python 3
+ * Allow to pass only source to Template
+
0.5.7 - 20130126
* Allow string as bitstream for images
diff --git a/MANIFEST.in b/MANIFEST.in
index a72bf8f..9f38fae 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,3 +1,7 @@
include AUTHORS
include CHANGES
include LICENSE
+include relatorio/tests/*.jpg
+include relatorio/tests/*.odt
+include relatorio/tests/*.png
+include relatorio/tests/templates/*.tmpl
diff --git a/PKG-INFO b/PKG-INFO
index 4ed7bfc..e112e9f 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: relatorio
-Version: 0.5.7
+Version: 0.6.0
Summary: A templating library able to output odt and pdf files
Home-page: http://relatorio.openhex.org/
Author: Cedric Krier
@@ -22,6 +22,7 @@ Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU General Public License (GPL)
Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Text Processing
diff --git a/relatorio.egg-info/PKG-INFO b/relatorio.egg-info/PKG-INFO
index 4ed7bfc..e112e9f 100644
--- a/relatorio.egg-info/PKG-INFO
+++ b/relatorio.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: relatorio
-Version: 0.5.7
+Version: 0.6.0
Summary: A templating library able to output odt and pdf files
Home-page: http://relatorio.openhex.org/
Author: Cedric Krier
@@ -22,6 +22,7 @@ Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU General Public License (GPL)
Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Text Processing
diff --git a/relatorio.egg-info/SOURCES.txt b/relatorio.egg-info/SOURCES.txt
index 99038ab..017f677 100644
--- a/relatorio.egg-info/SOURCES.txt
+++ b/relatorio.egg-info/SOURCES.txt
@@ -16,4 +16,15 @@ relatorio/templates/__init__.py
relatorio/templates/base.py
relatorio/templates/chart.py
relatorio/templates/opendocument.py
-relatorio/templates/pdf.py
\ No newline at end of file
+relatorio/templates/pdf.py
+relatorio/tests/__init__.py
+relatorio/tests/egg.jpg
+relatorio/tests/one.jpg
+relatorio/tests/test.odt
+relatorio/tests/test_api.py
+relatorio/tests/test_odt.py
+relatorio/tests/two.png
+relatorio/tests/templates/include.tmpl
+relatorio/tests/templates/other.tmpl
+relatorio/tests/templates/test.tmpl
+relatorio/tests/templates/time.tmpl
\ No newline at end of file
diff --git a/relatorio/__init__.py b/relatorio/__init__.py
index cdd9d54..09888e1 100644
--- a/relatorio/__init__.py
+++ b/relatorio/__init__.py
@@ -12,4 +12,4 @@ and report together, find reports by mimetypes/name/python objects.
from relatorio.reporting import MIMETemplateLoader, ReportRepository, Report
import templates
-__version__ = '0.5.7'
+__version__ = '0.6.0'
diff --git a/relatorio/templates/base.py b/relatorio/templates/base.py
index 5a3d46c..2b69076 100644
--- a/relatorio/templates/base.py
+++ b/relatorio/templates/base.py
@@ -20,11 +20,6 @@
__metaclass__ = type
-try:
- from cStringIO import OutputType
-except ImportError:
- from StringIO import StringIO as OutputType
-
import genshi.core
from genshi.template import NewTextTemplate, MarkupTemplate
@@ -46,12 +41,5 @@ class RelatorioStream(genshi.core.Stream):
"Support for the bitwise operator"
return RelatorioStream(self.events | function, self.serializer)
- def __str__(self):
- val = self.render()
- if isinstance(val, OutputType):
- return val.getvalue()
- else:
- return val
-
MIMETemplateLoader.add_factory('text', NewTextTemplate)
MIMETemplateLoader.add_factory('xml', MarkupTemplate)
diff --git a/relatorio/templates/opendocument.py b/relatorio/templates/opendocument.py
index 4f59972..159eff0 100644
--- a/relatorio/templates/opendocument.py
+++ b/relatorio/templates/opendocument.py
@@ -32,9 +32,12 @@ import time
import urllib
import zipfile
try:
- from cStringIO import StringIO
+ from io import BytesIO
except ImportError:
- from StringIO import StringIO
+ try:
+ from cStringIO import StringIO as BytesIO
+ except ImportError:
+ from StringIO import StringIO as BytesIO
from copy import deepcopy
import datetime
from decimal import Decimal
@@ -130,7 +133,7 @@ class ImageHref:
elif isinstance(bitstream, ChartTemplate):
bitstream = bitstream.generate(**self.context).render()
elif not hasattr(bitstream, 'seek') or not hasattr(bitstream, 'read'):
- bitstream = StringIO(bitstream)
+ bitstream = BytesIO(bitstream)
bitstream.seek(0)
file_content = bitstream.read()
name = md5(file_content).hexdigest()
@@ -237,6 +240,7 @@ class Template(MarkupTemplate):
self.namespaces = {}
self.inner_docs = []
self.has_col_loop = False
+ self._zip_source = None
super(Template, self).__init__(source, filepath, filename, loader,
encoding, lookup, allow_exec)
@@ -245,7 +249,19 @@ class Template(MarkupTemplate):
It adds genshi directives and finds the inner docs.
"""
- zf = zipfile.ZipFile(self.filepath)
+ if not self.filepath:
+ if hasattr(source, 'read') and hasattr(source, 'mode'):
+ if 'U' in source.mode:
+ # TemplateLoader of Genshi <= 0.6 open files with universal
+ # newlines which is not suitable for zipfile
+ raise ValueError('filepath is required '
+ 'if source is openned with universal newlines')
+ else:
+ # source could be closed before generate calls
+ source = BytesIO(source.read())
+ else:
+ source = self.filepath
+ self._zip_source = zf = zipfile.ZipFile(source)
content = zf.read('content.xml')
styles = zf.read('styles.xml')
@@ -268,7 +284,6 @@ class Template(MarkupTemplate):
content_files.append((c_path, c_parsed))
styles_files.append((s_path, s_parsed))
- zf.close()
parsed = []
for fpath, fparsed in content_files + styles_files:
parsed.append((genshi.core.PI, ('relatorio', fpath), None))
@@ -279,7 +294,7 @@ class Template(MarkupTemplate):
def insert_directives(self, content):
"""adds the genshi directives, handle the images and the innerdocs.
"""
- tree = lxml.etree.parse(StringIO(content))
+ tree = lxml.etree.parse(BytesIO(content))
root = tree.getroot()
# assign default/fake namespaces so that documents do not need to
@@ -306,7 +321,7 @@ class Template(MarkupTemplate):
self._handle_images(tree)
self._handle_innerdocs(tree)
self._escape_values(tree)
- return StringIO(lxml.etree.tostring(tree))
+ return BytesIO(lxml.etree.tostring(tree))
def _invert_style(self, tree):
"inverts the text:a and text:span"
@@ -692,7 +707,7 @@ class Template(MarkupTemplate):
def generate(self, *args, **kwargs):
"creates the RelatorioStream."
- serializer = OOSerializer(self.filepath)
+ serializer = OOSerializer(self._zip_source)
kwargs['__relatorio_make_href'] = ImageHref(serializer.outzip,
serializer.manifest,
kwargs)
@@ -752,13 +767,17 @@ class DuplicateColumnHeaders(object):
class Manifest(object):
def __init__(self, content):
- self.tree = lxml.etree.parse(StringIO(content))
+ self.tree = lxml.etree.parse(BytesIO(content))
self.root = self.tree.getroot()
self.namespaces = self.root.nsmap
def __str__(self):
- return lxml.etree.tostring(self.tree, encoding='UTF-8',
- xml_declaration=True)
+ val = lxml.etree.tostring(self.tree, encoding='UTF-8',
+ xml_declaration=True)
+ # In Python 3, val will be bytes
+ if not isinstance(val, str):
+ return str(val, 'utf-8')
+ return val
def add_file_entry(self, path, mimetype=None):
manifest_namespace = self.namespaces['manifest']
@@ -782,7 +801,7 @@ class Manifest(object):
class Meta(object):
def __init__(self, content):
- self.tree = lxml.etree.parse(StringIO(content))
+ self.tree = lxml.etree.parse(BytesIO(content))
root = self.tree.getroot()
self.namespaces = root.nsmap
path = '/office:document-meta/office:meta'
@@ -816,17 +835,21 @@ class Meta(object):
self.remove('printed-by')
self.remove('creator', 'dc')
self.remove('date', 'dc')
- return lxml.etree.tostring(self.tree, encoding='UTF-8',
- xml_declaration=True)
+ val = lxml.etree.tostring(self.tree, encoding='UTF-8',
+ xml_declaration=True)
+ # In Python 3, val will be bytes
+ if not isinstance(val, str):
+ return str(val, 'utf-8')
+ return val
class OOSerializer:
- def __init__(self, oo_path):
- self.inzip = zipfile.ZipFile(oo_path)
+ def __init__(self, inzip):
+ self.inzip = inzip
self.manifest = Manifest(self.inzip.read(MANIFEST))
self.meta = Meta(self.inzip.read(META))
- self.new_oo = StringIO()
+ self.new_oo = BytesIO()
self.outzip = zipfile.ZipFile(self.new_oo, 'w')
self.xml_serializer = genshi.output.XMLSerializer()
@@ -850,7 +873,8 @@ class OOSerializer:
new_info = zipfile.ZipInfo(f_info.filename, now)
for attr in ('compress_type', 'flag_bits', 'create_system'):
setattr(new_info, attr, getattr(f_info, attr))
- serialized_stream = output_encode(self.xml_serializer(stream))
+ serialized_stream = output_encode(self.xml_serializer(stream),
+ encoding='utf-8')
self.outzip.writestr(new_info, serialized_stream)
elif f_info.filename == MANIFEST:
manifest_info = f_info
@@ -863,7 +887,6 @@ class OOSerializer:
self.manifest.remove_file_entry(THUMBNAILS + '/')
if manifest_info:
self.outzip.writestr(manifest_info, str(self.manifest))
- self.inzip.close()
self.outzip.close()
return self.new_oo
diff --git a/relatorio/tests/__init__.py b/relatorio/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/relatorio/tests/egg.jpg b/relatorio/tests/egg.jpg
new file mode 100644
index 0000000..2700e16
Binary files /dev/null and b/relatorio/tests/egg.jpg differ
diff --git a/relatorio/tests/one.jpg b/relatorio/tests/one.jpg
new file mode 100644
index 0000000..48723f0
Binary files /dev/null and b/relatorio/tests/one.jpg differ
diff --git a/relatorio/tests/templates/include.tmpl b/relatorio/tests/templates/include.tmpl
new file mode 100644
index 0000000..cb46df8
--- /dev/null
+++ b/relatorio/tests/templates/include.tmpl
@@ -0,0 +1 @@
+{% include other.tmpl %}
diff --git a/relatorio/tests/templates/other.tmpl b/relatorio/tests/templates/other.tmpl
new file mode 100644
index 0000000..224a5d0
--- /dev/null
+++ b/relatorio/tests/templates/other.tmpl
@@ -0,0 +1 @@
+Another Hello.
diff --git a/relatorio/tests/templates/test.tmpl b/relatorio/tests/templates/test.tmpl
new file mode 100644
index 0000000..3b06a49
--- /dev/null
+++ b/relatorio/tests/templates/test.tmpl
@@ -0,0 +1 @@
+Hello ${o.name}.
diff --git a/relatorio/tests/templates/time.tmpl b/relatorio/tests/templates/time.tmpl
new file mode 100644
index 0000000..8467d56
--- /dev/null
+++ b/relatorio/tests/templates/time.tmpl
@@ -0,0 +1,2 @@
+Hi ${o.name},
+It's ${time} to ${func(y)} !
diff --git a/relatorio/tests/test.odt b/relatorio/tests/test.odt
new file mode 100644
index 0000000..6c3e196
Binary files /dev/null and b/relatorio/tests/test.odt differ
diff --git a/relatorio/tests/test_api.py b/relatorio/tests/test_api.py
new file mode 100644
index 0000000..11e8993
--- /dev/null
+++ b/relatorio/tests/test_api.py
@@ -0,0 +1,124 @@
+###############################################################################
+#
+# Copyright (c) 2007, 2008 OpenHex SPRL. (http://openhex.com) All Rights
+# Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+#
+###############################################################################
+
+
+import os
+from nose.tools import *
+
+from relatorio.reporting import (ReportRepository, Report, MIMETemplateLoader,
+ DefaultFactory, _absolute, _guess_type)
+
+
+class StubObject(object):
+
+ def __init__(self, **kwargs):
+ for key, val in kwargs.iteritems():
+ setattr(self, key, val)
+
+
+class TestRepository(object):
+
+ def test_register(self):
+ "Testing the registration"
+ reporting = ReportRepository()
+ reporting.add_report(StubObject, 'text/plain',
+ os.path.join('templates', 'test.tmpl'),
+ description='Test report')
+
+ assert_true(StubObject in reporting.classes)
+ assert_true('default' in reporting.classes[StubObject].ids)
+ assert_true('text/plain' in reporting.classes[StubObject].mimetypes)
+
+ report, mime, desc = reporting.classes[StubObject].ids['default']
+ eq_(mime, 'text/plain')
+ eq_(desc, 'Test report')
+ eq_(report.mimetype, 'text/plain')
+ assert_true(report.fpath.endswith(os.path.join('templates',
+ 'test.tmpl')))
+
+ report2, name = (reporting.classes[StubObject]
+ .mimetypes['text/plain'][0])
+ eq_(name, 'default')
+ eq_(report, report2)
+
+ def test_mimeguesser(self):
+ eq_(_guess_type('application/pdf'), 'pdf')
+ eq_(_guess_type('text/plain'), 'text')
+ eq_(_guess_type('text/xhtml'), 'markup')
+ eq_(_guess_type('application/vnd.oasis.opendocument.text'), 'oo.org')
+
+ def abspath_helper(self, path):
+ return _absolute(path)
+
+ def test_absolute(self):
+ "Test the absolute path calculation"
+ eq_("/home/nicoe/python/mock.py",
+ _absolute("/home/nicoe/python/mock.py"))
+
+ our_dir, _ = os.path.split(__file__)
+ # We use this because me go up by two frames
+ new_path = self.abspath_helper(os.path.join('brol', 'toto'))
+ eq_(os.path.join(our_dir, 'brol', 'toto'), new_path)
+
+
+class TestReport(object):
+
+ def setup(self):
+ self.loader = MIMETemplateLoader()
+ our_dir, _ = os.path.split(__file__)
+ self.report = Report(os.path.join(our_dir, 'templates', 'test.tmpl'),
+ 'text/plain', DefaultFactory(), self.loader)
+
+ def test_report(self):
+ "Testing the report generation"
+ a = StubObject(name='OpenHex')
+ eq_(self.report(o=a).render(), 'Hello OpenHex.\n')
+
+ def test_factory(self):
+ "Testing the data factory"
+ class MyFactory:
+ def __call__(self, o, time, y=1):
+ d = dict()
+ d['o'] = o
+ d['y'] = y
+ d['time'] = time
+ d['func'] = lambda x: x + 1
+ return d
+
+ our_dir, _ = os.path.split(__file__)
+ report = Report(os.path.join(our_dir, 'templates', 'time.tmpl'),
+ 'text/plain', MyFactory(), self.loader)
+
+ a = StubObject(name='Foo')
+ eq_(report(o=a, time="One o'clock").render(),
+ "Hi Foo,\nIt's One o'clock to 2 !\n")
+ eq_(report(o=a, time="One o'clock", y=4).render(),
+ "Hi Foo,\nIt's One o'clock to 5 !\n")
+ assert_raises(TypeError, report, a)
+
+
+class TestReportInclude(object):
+
+ def test_include(self):
+ our_dir = os.path.dirname(__file__)
+ template_path = os.path.join(our_dir, 'templates')
+ relative_report = Report(os.path.join(template_path, 'include.tmpl'),
+ 'text/plain')
+ eq_(relative_report().render(), 'Another Hello.\n\n')
diff --git a/relatorio/tests/test_odt.py b/relatorio/tests/test_odt.py
new file mode 100644
index 0000000..2d982ff
--- /dev/null
+++ b/relatorio/tests/test_odt.py
@@ -0,0 +1,312 @@
+# -*- encoding: utf-8 -*-
+###############################################################################
+#
+# Copyright (c) 2007, 2008 OpenHex SPRL. (http://openhex.com) All Rights
+# Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+#
+###############################################################################
+
+
+import os
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+import lxml.etree
+from nose.tools import *
+from genshi.filters import Translator
+from genshi.core import PI
+from genshi.template.eval import UndefinedError
+
+from relatorio.templates.opendocument import Template, GENSHI_EXPR,\
+ GENSHI_URI, RELATORIO_URI
+
+OO_TABLE_NS = "urn:oasis:names:tc:opendocument:xmlns:table:1.0"
+
+
+def pseudo_gettext(string):
+ catalog = {'Mes collègues sont:': 'My colleagues are:',
+ 'Bonjour,': 'Hello,',
+ 'Je suis un test de templating en odt.':
+ 'I am an odt templating test',
+ 'Felix da housecat': u'Félix le chat de la maison',
+ 'We sell stuff': u'On vend des choses',
+ }
+ return catalog.get(string, string)
+
+def stream_to_string(stream):
+ # In Python 3, stream will be bytes
+ if not isinstance(stream, str):
+ return str(stream, 'utf-8')
+ return stream
+
+
+class TestOOTemplating(object):
+
+ def setup(self):
+ thisdir = os.path.dirname(__file__)
+ filepath = os.path.join(thisdir, 'test.odt')
+ self.oot = Template(open(filepath, mode='rb'))
+ self.data = {'first_name': u'Trente',
+ 'last_name': u'Møller',
+ 'ville': u'Liège',
+ 'friends': [{'first_name': u'Camille',
+ 'last_name': u'Salauhpe'},
+ {'first_name': u'Mathias',
+ 'last_name': u'Lechat'}],
+ 'hobbies': [u'Music', u'Dancing', u'DJing'],
+ 'animals': [u'Felix da housecat', u'Dog eat Dog'],
+ 'images': [(open(os.path.join(thisdir, 'one.jpg'), 'rb'),
+ 'image/jpeg'),
+ (open(os.path.join(thisdir, 'two.png'), 'rb'),
+ 'image/png')],
+ 'oeuf': open(os.path.join(thisdir, 'egg.jpg'), 'rb'),
+ 'footer': u'We sell stuff'}
+
+ def test_init(self):
+ "Testing the correct handling of the styles.xml and content.xml files"
+ ok_(isinstance(self.oot.stream, list))
+ eq_(self.oot.stream[0], (PI, ('relatorio', 'content.xml'), None))
+ ok_((PI, ('relatorio', 'content.xml'), None) in self.oot.stream)
+
+ def test_directives(self):
+ "Testing the directives interpolation"
+ xml = b'''<xml xmlns:text="urn:text" xmlns:xlink="urn:xlink">
+ <text:a xlink:href="relatorio://foo">foo</text:a>
+ </xml>'''
+ interpolated = self.oot.insert_directives(xml)
+ root_interpolated = lxml.etree.parse(interpolated).getroot()
+ child = root_interpolated[0]
+ eq_(child.get('{http://genshi.edgewall.org/}replace'), 'foo')
+
+ def test_column_looping(self):
+ xml = b'''
+<table:table
+ xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"
+ xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
+ xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ table:name="Tableau1"
+ table:style-name="Tableau1">
+ <table:table-column table:style-name="Tableau1.A"
+ table:number-columns-repeated="2"/>
+ <table:table-column table:style-name="Tableau1.C"/>
+ <table:table-column table:style-name="Tableau1.A"/>
+ <table:table-column table:style-name="Tableau1.E"/>
+ <table:table-header-rows>
+ <table:table-row table:style-name="Tableau1.1">
+ <table:table-cell table:style-name="Tableau1.A1"
+ office:value-type="string">
+ <text:p text:style-name="Table_20_Heading">Brol</text:p>
+ </table:table-cell>
+ <table:table-cell table:style-name="Tableau1.A1"
+ office:value-type="string">
+ <text:p text:style-name="Table_20_Heading">
+ <text:a xlink:type="simple"
+ xlink:href="relatorio://for each="title in titles"">for each="title in titles"</text:a>
+ </text:p>
+ </table:table-cell>
+ <table:table-cell table:style-name="Tableau1.A1"
+ office:value-type="string">
+ <text:p text:style-name="Table_20_Heading">${title}</text:p>
+ <text:p text:style-name="Table_20_Heading"/>
+ </table:table-cell>
+ <table:table-cell table:style-name="Tableau1.A1"
+ office:value-type="string">
+ <text:p text:style-name="Table_20_Heading">
+ <text:a xlink:type="simple"
+ xlink:href="relatorio:///for">/for</text:a>
+ </text:p>
+ </table:table-cell>
+ <table:table-cell table:style-name="Tableau1.E1"
+ office:value-type="string">
+ <text:p text:style-name="Table_20_Heading">Truc</text:p>
+ </table:table-cell>
+ </table:table-row>
+ </table:table-header-rows>
+ <table:table-row>
+ <table:table-cell table:style-name="Tableau1.A2"
+ table:number-columns-spanned="5"
+ office:value-type="string">
+ <text:p text:style-name="Table_20_Contents">
+ <text:a xlink:type="simple"
+ xlink:href="relatorio://for%20each=%22items%20in%20lst%22">for each="items in lst"</text:a>
+ </text:p>
+ </table:table-cell>
+ <table:covered-table-cell/>
+ <table:covered-table-cell/>
+ <table:covered-table-cell/>
+ <table:covered-table-cell/>
+ </table:table-row>
+ <table:table-row>
+ <table:table-cell table:style-name="Tableau1.A3"
+ office:value-type="string">
+ <text:p text:style-name="Table_20_Contents">Brol</text:p>
+ </table:table-cell>
+ <table:table-cell table:style-name="Tableau1.A3"
+ office:value-type="string">
+ <text:p text:style-name="Table_20_Contents">
+ <text:a xlink:type="simple"
+ xlink:href="relatorio://for%20each=%22item%20in%20items%22">for each="item in items"</text:a>
+ </text:p>
+ </table:table-cell>
+ <table:table-cell table:style-name="Tableau1.A3"
+ office:value-type="string">
+ <text:p text:style-name="Table_20_Contents">${item}</text:p>
+ <text:p text:style-name="Table_20_Contents"/>
+ </table:table-cell>
+ <table:table-cell table:style-name="Tableau1.A3"
+ office:value-type="string">
+ <text:p text:style-name="Table_20_Contents">
+ <text:a xlink:type="simple"
+ xlink:href="relatorio:///for">/for</text:a>
+ </text:p>
+ </table:table-cell>
+ <table:table-cell table:style-name="Tableau1.A2"
+ office:value-type="string">
+ <text:p text:style-name="Table_20_Contents">Truc</text:p>
+ </table:table-cell>
+ </table:table-row>
+ <table:table-row>
+ <table:table-cell table:style-name="Tableau1.A2"
+ table:number-columns-spanned="5"
+ office:value-type="string">
+ <text:p text:style-name="Table_20_Contents">
+ <text:a xlink:type="simple"
+ xlink:href="relatorio:///for">/for</text:a>
+ </text:p>
+ </table:table-cell>
+ <table:covered-table-cell/>
+ <table:covered-table-cell/>
+ <table:covered-table-cell/>
+ <table:covered-table-cell/>
+ </table:table-row>
+</table:table>'''
+ interpolated = self.oot.insert_directives(xml)
+ root = lxml.etree.parse(interpolated).getroot()
+ child2 = root[1]
+ eq_(child2.tag, "{%s}repeat" % RELATORIO_URI)
+ eq_(child2.get("closing"), "3")
+ eq_(child2.get("opening"), "1")
+ eq_(len(child2), 1)
+ child4 = root[3]
+ eq_(child4.tag, "{%s}table-header-rows" % OO_TABLE_NS)
+ row1 = child4[0]
+ ok_(row1.get("{%s}attrs" % GENSHI_URI)
+ .startswith('__relatorio_reset_col_count'))
+ eq_(len(row1), 4)
+ loop = row1[1]
+ eq_(loop.tag, "{%s}for" % GENSHI_URI)
+ cell = loop[0]
+ ok_(cell.get("{%s}attrs" % GENSHI_URI)
+ .startswith('__relatorio_inc_col_count'))
+ last_row_node = row1[3]
+ eq_(last_row_node.tag, "{%s}replace" % GENSHI_URI)
+ ok_(last_row_node.get("value")
+ .startswith('__relatorio_store_col_count'))
+
+ def test_text_outside_p(self):
+ "Testing that the tail text of a directive node is handled properly"
+ xml = b'''<xml xmlns:text="urn:text" xmlns:xlink="urn:xlink">
+ <text:a xlink:href="relatorio://if%20test=%22True%22">if test="True"</text:a>
+ xxx
+ <text:p text:style-name="other">yyy</text:p>
+ zzz
+ <text:a xlink:href="relatorio:///if">/if</text:a>
+ aaa
+ </xml>'''
+ interpolated = self.oot.insert_directives(xml)
+ root_interpolated = lxml.etree.parse(interpolated).getroot()
+ child = root_interpolated[0]
+ eq_(child.tag, '{http://genshi.edgewall.org/}if')
+ eq_(child.text.strip(), 'xxx')
+ eq_(child.tail.strip(), 'aaa')
+
+ def test_styles(self):
+ "Testing that styles get rendered"
+ stream = self.oot.generate(**self.data)
+ rendered = stream_to_string(stream.events.render(encoding='utf-8'))
+ ok_('We sell stuff' in rendered)
+
+ dico = self.data.copy()
+ del dico['footer']
+ stream = self.oot.generate(**dico)
+ assert_raises(UndefinedError,
+ lambda: stream.events.render(encoding='utf-8'))
+
+ def test_generate(self):
+ "Testing that content get rendered"
+ stream = self.oot.generate(**self.data)
+ rendered = stream_to_string(stream.events.render(encoding='utf-8'))
+ ok_('Bonjour,' in rendered)
+ ok_('Trente' in rendered)
+ ok_('Møller' in rendered)
+ ok_('Dog eat Dog' in rendered)
+ ok_('Felix da housecat' in rendered)
+
+ def test_filters(self):
+ "Testing the filters with the Translator filter"
+ stream = self.oot.generate(**self.data)
+ translated = stream.filter(Translator(pseudo_gettext))
+ translated_xml = stream_to_string(
+ translated.events.render(encoding='utf-8'))
+ ok_("Hello," in translated_xml)
+ ok_("I am an odt templating test" in translated_xml)
+ ok_('Felix da housecat' not in translated_xml)
+ ok_('Félix le chat de la maison' in translated_xml)
+ ok_('We sell stuff' not in translated_xml)
+ ok_('On vend des choses' in translated_xml)
+
+ def test_images(self):
+ "Testing the image replacement directive"
+ stream = self.oot.generate(**self.data)
+ rendered = stream_to_string(stream.events.render(encoding='utf-8'))
+ styles_idx = rendered.find('<?relatorio styles.xml?>')
+ tree = lxml.etree.parse(StringIO(rendered[25:styles_idx]))
+ root = tree.getroot()
+ images = root.xpath('//draw:frame', namespaces=self.oot.namespaces)
+ eq_(len(images), 3)
+ eq_(images[0].get('{%s}name' % self.oot.namespaces['draw']), "")
+ eq_(images[1].get('{%s}name' % self.oot.namespaces['draw']), '')
+ eq_(images[1].get('{%s}width' % self.oot.namespaces['svg']),
+ '1.732cm')
+ eq_(images[1].get('{%s}height' % self.oot.namespaces['svg']),
+ '1.513cm')
+ eq_(images[2].get('{%s}width' % self.oot.namespaces['svg']),
+ '1.732cm')
+ eq_(images[2].get('{%s}height' % self.oot.namespaces['svg']),
+ '1.513cm')
+
+ def test_regexp(self):
+ "Testing the regexp used to find relatorio tags"
+ # a valid expression
+ group = GENSHI_EXPR.match('for each="foo in bar"').groups()
+ eq_(group, (None, 'for', 'each', 'foo in bar'))
+
+ # invalid expr
+ group = GENSHI_EXPR.match('foreach="foo in bar"').groups()
+ eq_(group, (None, None, None, None))
+
+ # valid closing tags
+ group = GENSHI_EXPR.match('/for').groups()
+ eq_(group, ('/', 'for', None, None))
+ group = GENSHI_EXPR.match('/for ').groups()
+ eq_(group, ('/', 'for', None, None))
+
+ # another non matching expr
+ group = GENSHI_EXPR.match('formatLang("en")').groups()
+ eq_(group, (None, None, None, None))
diff --git a/relatorio/tests/two.png b/relatorio/tests/two.png
new file mode 100644
index 0000000..f7d65d5
Binary files /dev/null and b/relatorio/tests/two.png differ
diff --git a/setup.py b/setup.py
index ef10e1a..f342bf6 100644
--- a/setup.py
+++ b/setup.py
@@ -12,7 +12,7 @@ setup(
url="http://relatorio.openhex.org/",
author="Nicolas Evrard",
author_email="nicoe at openhex.org",
- maintainer=u"Cedric Krier",
+ maintainer="Cedric Krier",
maintainer_email="cedric.krier at b2ck.com",
description="A templating library able to output odt and pdf files",
long_description="""
@@ -28,7 +28,10 @@ and report together, find reports by mimetypes/name/python objects.
""",
license="GPL License",
version=get_version(),
- packages=find_packages(exclude=['relatorio.tests', 'examples']),
+ packages=find_packages(exclude=['examples']),
+ package_data={
+ 'relatorio.tests': ['*.jpg', '*.odt', '*.png', 'templates/*.tmpl'],
+ },
install_requires=[
"Genshi >= 0.5",
"lxml >= 2.0"
@@ -38,9 +41,13 @@ and report together, find reports by mimetypes/name/python objects.
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU General Public License (GPL)",
"Operating System :: OS Independent",
- "Programming Language :: Python",
+ "Programming Language :: Python :: 2",
+ "Programming Language :: Python :: 3",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Text Processing",
],
test_suite="nose.collector",
- )
+ tests_require=[
+ "nose",
+ ],
+ use_2to3=True)
--
relatorio
More information about the tryton-debian-vcs
mailing list