Commit 97931e46 authored by Fabien Morin's avatar Fabien Morin

Enable image mapping with odg documents :

FormPrintout : clean a bit code
FormPrintout._replaceXmlByForm : change the xpath expression to get the node
from one level higher. In this way we can have all the node tree used for the
field. Add style in style dict for this node
FormPrintout._createOdfUniqueFileName : use quote_plus in both cases, add a comment to explain it
ImageField : add a method _replaceImage used to replace an image in an odg file
FormPrintoutAsODG : add test for proxyfields
Formulator.Field, Formulator.Widget : change render_odg signature to accept
more parameters required for image mapping. Add First level node.
ERP5OOo/tests/test_document/Foo_001.odg add some descriptions arround fields, to make easier to understand the purpose
of each element in the document.

reviewed and approved by Romain

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@31339 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 7ed6dec0
...@@ -29,7 +29,17 @@ ...@@ -29,7 +29,17 @@
from Products.Formulator import Widget, Validator from Products.Formulator import Widget, Validator
from Products.Formulator.Field import ZMIField from Products.Formulator.Field import ZMIField
from Products.Formulator.DummyField import fields from Products.Formulator.DummyField import fields
from lxml.etree import Element
from Acquisition import aq_base
from lxml import etree
DRAW_URI = 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0'
TEXT_URI = 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'
NSMAP = {
'draw': DRAW_URI,
'text': TEXT_URI,
}
class ImageFieldWidget(Widget.TextWidget): class ImageFieldWidget(Widget.TextWidget):
"""ImageField widget. """ImageField widget.
...@@ -92,6 +102,79 @@ class ImageFieldWidget(Widget.TextWidget): ...@@ -92,6 +102,79 @@ class ImageFieldWidget(Widget.TextWidget):
extra=extra, extra=extra,
) )
def _replaceImage(self, image_frame_node, ooo_builder, image_field,
printout, REQUEST, attr_dict):
"""
Replace the image in an odg file using an ERP5Form image field.
return True if the image have been found, else return False
"""
image_node = image_frame_node.getchildren()[0]
path = '/'.join(REQUEST.physicalPathFromURL(image_field.get_value('default')))
if path in (None, ''):
# not possible to add image, remove the existing one
image_frame_node = image_node.getparent()
image_frame_node.remove(image_node)
return False
path = path.encode()
image_object = image_field.getPortalObject().restrictedTraverse(path)
image_parameter_dict = {
'display': image_field.get_value('image_display'),
'format': image_field.get_value('image_format')
}
picture_type, image_data = image_object.convert(**image_parameter_dict)
if image_data is None:
# not possible to add image, remove the existing one
image_frame_node = image_node.getparent()
image_frame_node.remove(image_node)
return False
picture_path = printout._createOdfUniqueFileName(path=path,
picture_type=picture_type)
ooo_builder.addFileEntry(picture_path, media_type=picture_type, content=image_data)
width, height = printout._getPictureSize(image_object, image_node)
image_node.set('{%s}href' % image_node.nsmap['xlink'], picture_path)
image_frame_node.set('{%s}width' % image_node.nsmap['svg'], width)
image_frame_node.set('{%s}height' % image_node.nsmap['svg'], height)
attr_dict.setdefault(image_node.tag, {}).update(image_node.attrib)
attr_dict.setdefault(image_frame_node.tag, {}).update(image_frame_node.attrib)
return True
def render_odg(self, field, as_string, local_name, target_node, printout,
REQUEST, ooo_builder, attr_dict=None):
"""
return an image xml node rendered in odg format
if as_string is True (default) the returned value is a string (xml
reprensation of the node), if it's False, the value returned is the node
object.
attr_dict can be used for additional parameters (like style).
"""
# replace the image in the odg document
if not self._replaceImage(target_node, ooo_builder, field, printout,
REQUEST, attr_dict):
# if image is not found, return None
return None
if attr_dict is None:
attr_dict = {}
draw_frame_tag_name = '{%s}%s' % (DRAW_URI, 'frame')
draw_frame_node = Element(draw_frame_tag_name, nsmap=NSMAP)
draw_frame_node.attrib.update(attr_dict.get(draw_frame_tag_name, {}))
draw_image_tag_name = '{%s}%s' % (DRAW_URI, 'image')
draw_image_node = Element(draw_image_tag_name, nsmap=NSMAP)
draw_image_node.attrib.update(attr_dict.get(draw_image_tag_name, {}))
text_p_tag_name = '{%s}%s' % (TEXT_URI, local_name)
text_p_node = Element(text_p_tag_name, nsmap=NSMAP)
text_p_node.attrib.update(attr_dict.get(text_p_tag_name, {}))
draw_image_node.append(text_p_node)
draw_frame_node.append(draw_image_node)
if as_string:
return etree.tostring(draw_frame_node)
return draw_frame_node
ImageFieldWidgetInstance = ImageFieldWidget() ImageFieldWidgetInstance = ImageFieldWidget()
ImageFieldValidatorInstance = Validator.StringValidator() ImageFieldValidatorInstance = Validator.StringValidator()
......
...@@ -43,7 +43,6 @@ from OFS.SimpleItem import Item ...@@ -43,7 +43,6 @@ from OFS.SimpleItem import Item
from urllib import quote, quote_plus from urllib import quote, quote_plus
from copy import deepcopy from copy import deepcopy
from lxml import etree from lxml import etree
from lxml.etree import _Element, _ElementStringResult
from zLOG import LOG, DEBUG, INFO, WARNING from zLOG import LOG, DEBUG, INFO, WARNING
from mimetypes import guess_extension from mimetypes import guess_extension
from DateTime import DateTime from DateTime import DateTime
...@@ -270,7 +269,8 @@ class ODFStrategy(Implicit): ...@@ -270,7 +269,8 @@ class ODFStrategy(Implicit):
if here is None: if here is None:
raise ValueError, 'Can not create a ODF Document without a parent acquisition context' raise ValueError, 'Can not create a ODF Document without a parent acquisition context'
form = extra_context['form'] form = extra_context['form']
if not extra_context.has_key('printout_template') or extra_context['printout_template'] is None: if not extra_context.has_key('printout_template') or \
extra_context['printout_template'] is None:
raise ValueError, 'Can not create a ODF Document without a printout template' raise ValueError, 'Can not create a ODF Document without a printout template'
odf_template = extra_context['printout_template'] odf_template = extra_context['printout_template']
...@@ -281,7 +281,7 @@ class ODFStrategy(Implicit): ...@@ -281,7 +281,7 @@ class ODFStrategy(Implicit):
ooo_document = odf_template.pt_render(here, extra_context=extra_context) ooo_document = odf_template.pt_render(here, extra_context=extra_context)
else: else:
# File object can be a template # File object can be a template
ooo_document = odf_template ooo_document = odf_template
# Create a new builder instance # Create a new builder instance
ooo_builder = OOoBuilder(ooo_document) ooo_builder = OOoBuilder(ooo_document)
...@@ -393,7 +393,7 @@ class ODFStrategy(Implicit): ...@@ -393,7 +393,7 @@ class ODFStrategy(Implicit):
form = getattr(here, form_id) form = getattr(here, form_id)
target_element_tree = deepcopy(temporary_element_tree) target_element_tree = deepcopy(temporary_element_tree)
# remove original target in the ODF template # remove original target in the ODF template
if index == 0: if index == 0:
office_body.remove(original_target) office_body.remove(original_target)
else: else:
...@@ -514,21 +514,23 @@ class ODFStrategy(Implicit): ...@@ -514,21 +514,23 @@ class ODFStrategy(Implicit):
picture_type = picture.getContentType() picture_type = picture.getContentType()
picture_path = self._createOdfUniqueFileName(path=path, picture_type=picture_type) picture_path = self._createOdfUniqueFileName(path=path, picture_type=picture_type)
ooo_builder.addFileEntry(picture_path, media_type=picture_type, content=picture_data) ooo_builder.addFileEntry(picture_path, media_type=picture_type, content=picture_data)
picture_size = self._getPictureSize(picture, image_node) width, height = self._getPictureSize(picture, image_node)
image_node.set('{%s}href' % element_tree.nsmap['xlink'], picture_path) image_node.set('{%s}href' % element_tree.nsmap['xlink'], picture_path)
image_frame.set('{%s}width' % element_tree.nsmap['svg'], picture_size[0]) image_frame.set('{%s}width' % element_tree.nsmap['svg'], width)
image_frame.set('{%s}height' % element_tree.nsmap['svg'], picture_size[1]) image_frame.set('{%s}height' % element_tree.nsmap['svg'], height)
# set when using report section # set when using report section
self._setUniqueElementName(image_field.id, iteration_index, image_xpath, element_tree) self._setUniqueElementName(image_field.id, iteration_index, image_xpath, element_tree)
def _createOdfUniqueFileName(self, path='', picture_type=''): def _createOdfUniqueFileName(self, path='', picture_type=''):
extension = guess_extension(picture_type) extension = guess_extension(picture_type)
picture_path = 'Pictures/%s%s' % (quote_plus(path), extension) # here, it's needed to use quote_plus to escape '/' caracters to make valid
# paths in the odf archive.
picture_path = 'Pictures/%s%s' % (quote_plus(path), extension)
if picture_path not in self.odf_existent_name_list: if picture_path not in self.odf_existent_name_list:
return picture_path return picture_path
number = 0 number = 0
while True: while True:
picture_path = 'Pictures/%s_%s%s' % (path, number, extension) picture_path = 'Pictures/%s_%s%s' % (quote_plus(path), number, extension)
if picture_path not in self.odf_existent_name_list: if picture_path not in self.odf_existent_name_list:
return picture_path return picture_path
number += 1 number += 1
...@@ -949,15 +951,22 @@ class ODGStrategy(ODFStrategy): ...@@ -949,15 +951,22 @@ class ODGStrategy(ODFStrategy):
def _replaceXmlByForm(self, element_tree, form, here, extra_context, def _replaceXmlByForm(self, element_tree, form, here, extra_context,
ooo_builder, iteration_index=0): ooo_builder, iteration_index=0):
field_list = form.get_fields(include_disabled=1) field_list = form.get_fields(include_disabled=1)
for (count, field) in enumerate(field_list): for (count, field) in enumerate(field_list):
text_xpath = '//draw:frame[@draw:name="%s"]/*' % field.id text_xpath = '//draw:frame[@draw:name="%s"]' % field.id
node_list = element_tree.xpath(text_xpath, namespaces=element_tree.nsmap) node_list = element_tree.xpath(text_xpath, namespaces=element_tree.nsmap)
for target_node in node_list: for target_node in node_list:
attr_dict = {} attr_dict = {}
attr_dict.setdefault(target_node.tag, {}).update(target_node.attrib)
# store child style using their local-name as key # store child style using their local-name as key
for descendant in target_node.iterdescendants(): for descendant in target_node.iterdescendants():
attr_dict.setdefault(descendant.tag, {}).update(descendant.attrib) attr_dict.setdefault(descendant.tag, {}).update(descendant.attrib)
new_node = field.render_odg(attr_dict=attr_dict) # render the field in odg xml node format
parent_node = target_node.getparent().replace(target_node, new_node) new_node = field.render_odg(target_node=target_node, printout=self,
REQUEST=self.REQUEST, ooo_builder=ooo_builder, attr_dict=attr_dict)
if new_node is not None:
target_node.getparent().replace(target_node, new_node)
else:
# if the render return None, remove the node
target_node.getparent().remove(target_node)
...@@ -80,6 +80,9 @@ class TestFormPrintoutAsODG(TestFormPrintoutMixin): ...@@ -80,6 +80,9 @@ class TestFormPrintoutAsODG(TestFormPrintoutMixin):
if custom._getOb('Foo_viewAsODGPrintout', None) is None: if custom._getOb('Foo_viewAsODGPrintout', None) is None:
erp5OOo.addFormPrintout(id='Foo_viewAsODGPrintout', title='', erp5OOo.addFormPrintout(id='Foo_viewAsODGPrintout', title='',
form_name='Foo_view', template='Foo_getODGStyleSheet') form_name='Foo_view', template='Foo_getODGStyleSheet')
if custom._getOb('Foo_viewProxyFieldAsODGPrintout', None) is None:
erp5OOo.addFormPrintout(id='Foo_viewProxyFieldAsODGPrintout', title='',
form_name='Foo_viewProxyField', template='Foo_getODGStyleSheet')
if custom._getOb('FooReport_viewAsODGPrintout', None) is None: if custom._getOb('FooReport_viewAsODGPrintout', None) is None:
erp5OOo.addFormPrintout(id='FooReport_viewAsODGPrintout', erp5OOo.addFormPrintout(id='FooReport_viewAsODGPrintout',
title='') title='')
...@@ -363,6 +366,126 @@ class TestFormPrintoutAsODG(TestFormPrintoutMixin): ...@@ -363,6 +366,126 @@ class TestFormPrintoutAsODG(TestFormPrintoutMixin):
self.assertTrue(content_xml.find('<draw:image xlink:href') < 0) self.assertTrue(content_xml.find('<draw:image xlink:href') < 0)
self._validate(odf_document) self._validate(odf_document)
def test_04_ProxyField(self):
"""
Check it's possible to use an odg document to map proxyfields
"""
portal = self.getPortal()
foo_module = self.portal.foo_module
if foo_module._getOb('test1', None) is None:
foo_module.newContent(id='test1', portal_type='Foo')
test1 = foo_module.test1
test1.setTitle('Foo title!')
transaction.commit()
self.tic()
style_dict = {'{urn:oasis:names:tc:opendocument:xmlns:text:1.0}span':
{'{urn:oasis:names:tc:opendocument:xmlns:text:1.0}style-name': 'T2'},
'{urn:oasis:names:tc:opendocument:xmlns:text:1.0}p': {}
}
# test target
foo_printout = portal.foo_module.test1.Foo_viewProxyFieldAsODGPrintout
original_file_content = self.getODFDocumentFromPrintout(foo_printout)
self._validate(original_file_content)
# extract content.xml from original odg document
original_doc_builder = OOoBuilder(original_file_content)
original_content_xml = original_doc_builder.extract("content.xml")
# get style of the title in the orignal test document
original_document_style_dict = self.getStyleDictFromFieldName(original_content_xml,
'my_title')
# check the style is good before the odg generation
self.assertEqual(original_document_style_dict, style_dict)
request = self.app.REQUEST
# 1. Normal case: "my_title" field to the "my_title" reference in the ODF document
odf_document = foo_printout.index_html(REQUEST=request)
self.assertTrue(odf_document is not None)
builder = OOoBuilder(odf_document)
content_xml = builder.extract("content.xml")
final_document_style_dict = self.getStyleDictFromFieldName(content_xml,
'my_title')
# check the style is keept after the odg generation
self.assertEqual(final_document_style_dict, style_dict)
self.assertTrue(content_xml.find("Foo title!") > 0)
self.assertEqual(request.RESPONSE.getHeader('content-type'),
'application/vnd.oasis.opendocument.graphics; charset=utf-8')
self.assertEqual(request.RESPONSE.getHeader('content-disposition'),
'inline;filename="Foo_viewProxyFieldAsODGPrintout.odg"')
self._validate(odf_document)
# 2. Normal case: change the field value and check again the ODF document
test1.setTitle("Changed Title!")
#foo_form.my_title.set_value('default', "Changed Title!")
odf_document = foo_printout.index_html(REQUEST=request)
self.assertTrue(odf_document is not None)
builder = OOoBuilder(odf_document)
content_xml = builder.extract("content.xml")
self.assertTrue(content_xml.find("Changed Title!") > 0)
self._validate(odf_document)
# 3. False case: change the field name
test1.setTitle("you cannot find")
# rename id 'my_title' to 'xxx_title', then does not match in the ODF document
foo_form = portal.foo_module.test1.Foo_viewProxyField
foo_form.manage_renameObject('my_title', 'xxx_title', REQUEST=request)
odf_document = foo_printout.index_html(REQUEST=request)
self.assertTrue(odf_document is not None)
builder = OOoBuilder(odf_document)
content_xml = builder.extract("content.xml")
self.assertFalse(content_xml.find("you cannot find") > 0)
self._validate(odf_document)
# put back
foo_form.manage_renameObject('xxx_title', 'my_title', REQUEST=request)
## 4. False case: does not set a ODF template
self.assertTrue(foo_printout.template == 'Foo_getODGStyleSheet')
tmp_template = foo_printout.template
foo_printout.template = None
# template == None, causes a ValueError
try:
foo_printout.index_html(REQUEST=request)
except ValueError, e:
# e -> 'Can not create a ODF Document without a odf_template'
self.assertTrue(True)
# put back
foo_printout.template = tmp_template
# 5. Normal case: just call a FormPrintout object
request.RESPONSE.setHeader('Content-Type', 'text/html')
test1.setTitle("call!")
odf_document = foo_printout() # call
self.assertTrue(odf_document is not None)
builder = OOoBuilder(odf_document)
content_xml = builder.extract("content.xml")
self.assertTrue(content_xml.find("call!") > 0)
# when just call FormPrintout, it does not change content-type
self.assertEqual(request.RESPONSE.getHeader('content-type'), 'text/html')
self._validate(odf_document)
# 5. Normal case: utf-8 string
test1.setTitle("Français")
odf_document = foo_printout()
self.assertTrue(odf_document is not None)
builder = OOoBuilder(odf_document)
content_xml = builder.extract("content.xml")
self.assertTrue(content_xml.find("Français") > 0)
self._validate(odf_document)
# 6. Normal case: unicode string
test1.setTitle(u'Français test2')
odf_document = foo_printout()
self.assertTrue(odf_document is not None)
builder = OOoBuilder(odf_document)
content_xml = builder.extract("content.xml")
self.assertTrue(content_xml.find("Français test2") > 0)
self._validate(odf_document)
def test_suite(): def test_suite():
suite = unittest.TestSuite() suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestFormPrintoutAsODG)) suite.addTest(unittest.makeSuite(TestFormPrintoutAsODG))
......
...@@ -1111,8 +1111,6 @@ return [] ...@@ -1111,8 +1111,6 @@ return []
# 01: Normal # 01: Normal
odf_document = foo_printout() odf_document = foo_printout()
self.assertTrue(odf_document is not None) self.assertTrue(odf_document is not None)
#test_output = open("/tmp/test_07_Image_01_Normal.odf", "w")
#test_output.write(odf_document)
builder = OOoBuilder(odf_document) builder = OOoBuilder(odf_document)
content_xml = builder.extract("content.xml") content_xml = builder.extract("content.xml")
self.assertTrue(content_xml.find("test_image.png") > 0) self.assertTrue(content_xml.find("test_image.png") > 0)
...@@ -1131,8 +1129,6 @@ return [] ...@@ -1131,8 +1129,6 @@ return []
my_default_image_absolute_url.values['default'] = '' my_default_image_absolute_url.values['default'] = ''
odf_document = foo_printout() odf_document = foo_printout()
self.assertTrue(odf_document is not None) self.assertTrue(odf_document is not None)
#test_output = open("/tmp/test_07_Image_02_NoData.odf", "w")
#test_output.write(odf_document)
builder = OOoBuilder(odf_document) builder = OOoBuilder(odf_document)
content_xml = builder.extract("content.xml") content_xml = builder.extract("content.xml")
# confirming the image was removed # confirming the image was removed
......
...@@ -284,8 +284,10 @@ class Field: ...@@ -284,8 +284,10 @@ class Field:
return self.widget.render_odt(self, as_string, local_name, attr_dict=attr_dict) return self.widget.render_odt(self, as_string, local_name, attr_dict=attr_dict)
security.declareProtected('View', 'render_odg') security.declareProtected('View', 'render_odg')
def render_odg(self, as_string=False, local_name='p', attr_dict=None): def render_odg(self, as_string=False, local_name='p', target_node=None,
return self.widget.render_odg(self, as_string, local_name, attr_dict=attr_dict) printout=None, REQUEST=None, ooo_builder=None, attr_dict=None):
return self.widget.render_odg(self, as_string, local_name, target_node,
printout, REQUEST, ooo_builder, attr_dict)
security.declareProtected('View', 'render_css') security.declareProtected('View', 'render_css')
def render_css(self, REQUEST=None): def render_css(self, REQUEST=None):
......
...@@ -190,10 +190,11 @@ class Widget: ...@@ -190,10 +190,11 @@ class Widget:
return etree.tostring(text_node) return etree.tostring(text_node)
return text_node return text_node
def render_odg(self, field, as_string, local_name, attr_dict=None): def render_odg(self, field, as_string, local_name, target_node=None,
printout=None, REQUEST=None, ooo_builder=None, attr_dict=None):
""" """
Default render odg for widget - to be overwritten in field classes. Default render odg for widget - to be overwritten in field classes.
Return a field value rendered in odg format. Return a field node rendered in odg format.
if as_string is True (default) the returned value is a string (xml if as_string is True (default) the returned value is a string (xml
reprensation of the node), if it's False, the value returned is the node reprensation of the node), if it's False, the value returned is the node
object. object.
...@@ -211,17 +212,25 @@ class Widget: ...@@ -211,17 +212,25 @@ class Widget:
value = '\n'.join(value) value = '\n'.join(value)
value.replace('\r', '') value.replace('\r', '')
draw_frame_tag_name = '{%s}%s' % (DRAW_URI, 'frame')
draw_frame_node = Element(draw_frame_tag_name, nsmap=NSMAP)
draw_frame_node.attrib.update(attr_dict.get(draw_frame_tag_name, {}))
draw_tag_name = '{%s}%s' % (DRAW_URI, 'text-box') draw_tag_name = '{%s}%s' % (DRAW_URI, 'text-box')
draw_node = Element(draw_tag_name, nsmap=NSMAP) draw_node = Element(draw_tag_name, nsmap=NSMAP)
draw_node.attrib.update(attr_dict.get(draw_tag_name, {})) draw_node.attrib.update(attr_dict.get(draw_tag_name, {}))
text_p_tag_name = '{%s}%s' % (TEXT_URI, local_name) text_p_tag_name = '{%s}%s' % (TEXT_URI, local_name)
text_p_node = Element(text_p_tag_name, nsmap=NSMAP) text_p_node = Element(text_p_tag_name, nsmap=NSMAP)
text_p_node.attrib.update(attr_dict.get(text_p_tag_name, {})) text_p_node.attrib.update(attr_dict.get(text_p_tag_name, {}))
text_span_tag_name = '{%s}%s' % (TEXT_URI, 'span') text_span_tag_name = '{%s}%s' % (TEXT_URI, 'span')
text_span_node = Element(text_span_tag_name, nsmap=NSMAP) text_span_node = Element(text_span_tag_name, nsmap=NSMAP)
text_span_node.attrib.update(attr_dict.get(text_span_tag_name, {})) text_span_node.attrib.update(attr_dict.get(text_span_tag_name, {}))
text_p_node.append(text_span_node) text_p_node.append(text_span_node)
draw_node.append(text_p_node) draw_node.append(text_p_node)
draw_frame_node.append(draw_node)
# XXX copy from render_odt, need to be unified # XXX copy from render_odt, need to be unified
def replaceCharsByNode(match_object): def replaceCharsByNode(match_object):
...@@ -235,11 +244,9 @@ class Widget: ...@@ -235,11 +244,9 @@ class Widget:
line_break = SubElement(text_span_node, '{%s}%s' % (TEXT_URI, 'tab')) line_break = SubElement(text_span_node, '{%s}%s' % (TEXT_URI, 'tab'))
line_break.tail = match_object.group(2) line_break.tail = match_object.group(2)
re.sub('([\n\t])?([^\n\t]*)', replaceCharsByNode, value) re.sub('([\n\t])?([^\n\t]*)', replaceCharsByNode, value)
#text_span_node.text = value
if as_string: if as_string:
return etree.tostring(draw_node) return etree.tostring(draw_frame_node)
return draw_node return draw_frame_node
class TextWidget(Widget): class TextWidget(Widget):
"""Text widget """Text widget
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment