diff --git a/product/ERP5OOo/FormPrintout.py b/product/ERP5OOo/FormPrintout.py index 6273bd7342370ffdedbaf958140aff924e5d6a1c..fe8f49c0c71c8079d2ea28cd1bcb2b51015d7ad1 100644 --- a/product/ERP5OOo/FormPrintout.py +++ b/product/ERP5OOo/FormPrintout.py @@ -29,7 +29,9 @@ from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate from Products.PageTemplates.PageTemplateFile import PageTemplateFile from Products.CMFCore.utils import _checkPermission, getToolByName from Products.ERP5Type import PropertySheet, Permissions -from Products.ERP5Form.ListBox import ListBox +from Products.ERP5Form.ListBox import ListBox +from Products.ERP5Form.FormBox import FormBox +from Products.ERP5Form.ImageField import ImageField from Products.ERP5OOo.OOoUtils import OOoBuilder from Acquisition import Implicit @@ -73,7 +75,7 @@ def add_and_edit(self, id, REQUEST): 'Add and Edit' button is pressed. Keyword arguments: - id -- id of the object we just added + id -- the id of the object we just added """ if REQUEST is None: return @@ -90,7 +92,7 @@ class FormPrintout(Implicit, Persistent, RoleManager, Item): The Form Printout enables to create a ODF document. - The Form Printout receives a name of a ERP5 form, and a template name. + The Form Printout receives an ERP5 form name, and a template name. Using their parameters, the Form Printout genereate a ODF document, a form as a ODF document content, and a template as a document layout. @@ -133,10 +135,6 @@ class FormPrintout(Implicit, Persistent, RoleManager, Item): template = None form_name = None - def __str__(self): return self.index_html() - - def __len__(self): return 1 - def __init__(self, id, title='', form_name='', template=''): """Initialize id, title, form_name, template. @@ -209,16 +207,17 @@ InitializeClass(FormPrintout) NAME_SPACE_DICT = {'draw':'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0', 'table':'urn:oasis:names:tc:opendocument:xmlns:table:1.0', 'text':'urn:oasis:names:tc:opendocument:xmlns:text:1.0', - 'office':'urn:oasis:names:tc:opendocument:xmlns:office:1.0'} + 'office':'urn:oasis:names:tc:opendocument:xmlns:office:1.0', + 'xlink':'http://www.w3.org/1999/xlink'} class ODFStrategy(Implicit): """ODFStrategy creates a ODF Document. """ def render(self, extra_context={}): - """Render a odt document, form as a content, template as a template. + """Render a odf document, form as a content, template as a template. Keyword arguments: - extra_context -- a dictionary, guess contains + extra_context -- a dictionary, expected: 'here' : where it call 'printout_template' : the template object, tipically a OOoTemplate 'container' : the object which has a form printout object @@ -226,7 +225,6 @@ class ODFStrategy(Implicit): """ here = extra_context['here'] if here is None: - # This is a system error raise ValueError, 'Can not create a ODF Document without a parent acquisition context' form = extra_context['form'] if not extra_context.has_key('printout_template') or extra_context['printout_template'] is None: @@ -234,12 +232,12 @@ class ODFStrategy(Implicit): odf_template = extra_context['printout_template'] - # First, render a OOoTemplate itself with its style + # First, render the Template if it has a pt_render method ooo_document = None if hasattr(odf_template, 'pt_render'): ooo_document = odf_template.pt_render(here, extra_context=extra_context) else: - # File object, OOoBuilder directly support a file object + # File object can be a template ooo_document = odf_template # Create a new builder instance @@ -247,8 +245,9 @@ class ODFStrategy(Implicit): # content.xml ooo_builder = self._replace_content_xml(ooo_builder=ooo_builder, extra_context=extra_context) - # styles.xml and meta.xml are not supported yet - # ooo_builder = self._replace_styles_xml(ooo_builder=ooo_builder, extra_context=extra_context) + # styles.xml + ooo_builder = self._replace_styles_xml(ooo_builder=ooo_builder, extra_context=extra_context) + # meta.xml is not supported yet # ooo_builder = self._replace_meta_xml(ooo_builder=ooo_builder, extra_context=extra_context) # Update the META informations @@ -258,25 +257,25 @@ class ODFStrategy(Implicit): return ooo def _replace_content_xml(self, ooo_builder=None, extra_context=None): - doc_xml = ooo_builder.extract('content.xml') + content_xml = ooo_builder.extract('content.xml') # mapping ERP5Form to ODF form = extra_context['form'] here = getattr(self, 'aq_parent', None) - ordinaly_group_list = [group for group in form.get_groups() if group not in ['meta','style']] - doc_xml = self._replace_xml_by_form(doc_xml=doc_xml, - form=form, - here=here, - extra_context=extra_context, - group_list=ordinaly_group_list) - # mapping ERP5Report report method to ODF - doc_xml = self._replace_xml_by_report_section(doc_xml=doc_xml, - extra_context=extra_context) - if isinstance(doc_xml, unicode): - doc_xml = doc_xml.encode('utf-8') + content_element_tree = etree.XML(content_xml) + content_element_tree = self._replace_xml_by_form(element_tree=content_element_tree, + form=form, + here=here, + extra_context=extra_context) + # mapping ERP5Report report method to ODF + content_element_tree = self._replace_xml_by_report_section(element_tree=content_element_tree, + extra_context=extra_context) + content_xml = etree.tostring(content_element_tree) + if isinstance(content_xml, unicode): + content_xml = content_xml.encode('utf-8') # Replace content.xml in master openoffice template - ooo_builder.replace('content.xml', doc_xml) + ooo_builder.replace('content.xml', content_xml) return ooo_builder # this method not supported yet @@ -284,19 +283,19 @@ class ODFStrategy(Implicit): """ replacing styles.xml file in a ODF document """ - doc_xml = ooo_builder.extract('styles.xml') + styles_xml = ooo_builder.extract('styles.xml') form = extra_context['form'] here = getattr(self, 'aq_parent', None) - doc_xml = self._replace_xml_by_form(doc_xml=doc_xml, - form=form, - here=here, - extra_context=extra_context, - group_list=['styles']) - - if isinstance(doc_xml, unicode): - doc_xml = doc_xml.encode('utf-8') + styles_element_tree = etree.XML(styles_xml) + styles_element_tree = self._replace_xml_by_form(element_tree=styles_element_tree, + form=form, + here=here, + extra_context=extra_context) + styles_xml = etree.tostring(styles_element_tree) + if isinstance(styles_xml, unicode): + styles_xml = styles_xml.encode('utf-8') - ooo_builder.replace('styles.xml', doc_xml) + ooo_builder.replace('styles.xml', styles_xml) return ooo_builder # this method not implemented yet @@ -304,54 +303,60 @@ class ODFStrategy(Implicit): """ replacing meta.xml file in a ODF document """ - doc_xml = ooo_builder.extract('meta.xml') + meta_xml = ooo_builder.extract('meta.xml') if isinstance(doc_xml, unicode): - doc_xml = doc_xml.encode('utf-8') + meta_xml = meta_xml.encode('utf-8') - ooo_builder.replace('meta.xml', doc_xml) + ooo_builder.replace('meta.xml', meta_xml) return ooo_builder - def _replace_xml_by_form(self, doc_xml=None, form=None, here=None, - group_list=[], extra_context=None): - field_list = [f for g in group_list for f in form.get_fields_in_group(g)] - content = etree.XML(doc_xml) + def _replace_xml_by_form(self, element_tree=None, form=None, here=None, + extra_context=None, render_prefix=None): + field_list = form.get_fields() REQUEST = get_request() - for (count,field) in enumerate(field_list): + for (count, field) in enumerate(field_list): if isinstance(field, ListBox): - content = self._append_table_by_listbox(content=content, - listbox=field, - REQUEST=REQUEST) + element_tree = self._append_table_by_listbox(element_tree=element_tree, + listbox=field, + REQUEST=REQUEST, + render_prefix=render_prefix) + elif isinstance(field, FormBox): + sub_form = getattr(here, field.get_value('formbox_target_id')) + content = self._replace_xml_by_formbox(element_tree=element_tree, + field_id=field.id, + form = sub_form, + REQUEST=REQUEST) + #elif isinstance(field, ImageField): + # element_tree = self._replace_xml_by_image_field(element_tree=element_tree, + # image_field=field) else: - field_value = field.get_value('default') - content = self._replace_node_via_reference(content=content, - field_id=field.id, - field_value=field_value) - return etree.tostring(content) - - def _replace_node_via_reference(self, content=None, field_id=None, field_value=None): + element_tree = self._replace_node_via_reference(element_tree=element_tree, + field=field) + return element_tree + + def _replace_node_via_reference(self, element_tree=None, field=None): + field_id = field.id + field_value = field.get_value('default') # text:reference-mark text:name="invoice-date" reference_xpath = '//text:reference-mark[@text:name="%s"]' % field_id - reference_list = content.xpath(reference_xpath, namespaces=NAME_SPACE_DICT) + reference_list = element_tree.xpath(reference_xpath, namespaces=NAME_SPACE_DICT) if len(reference_list) > 0: node = reference_list[0].getparent() - ## remove such a "bbb" - ## <text:p>aaa<br/>bbb</text:p> + # remove such a "bbb": <text:p>aaa<br/>bbb</text:p> for child in node.getchildren(): child.tail = '' node.text = field_value - return content + return element_tree - def _replace_xml_by_report_section(self, doc_xml=None, extra_context=None): + def _replace_xml_by_report_section(self, element_tree=None, extra_context=None): if not extra_context.has_key('report_method') or extra_context['report_method'] is None: - return doc_xml + return element_tree report_method = extra_context['report_method'] report_section_list = report_method() portal_object = self.getPortalObject() - content = etree.XML(doc_xml) REQUEST = get_request() request = extra_context.get('REQUEST', REQUEST) - render_prefix = None for (index, report_item) in enumerate(report_section_list): if index > 0: @@ -360,31 +365,55 @@ class ODFStrategy(Implicit): here = report_item.getObject(portal_object) form_id = report_item.getFormId() form = getattr(here, form_id) - for field in form.get_fields(): - if field.meta_type == 'ListBox': - content = self._append_table_by_listbox(content=content, - listbox=field, - REQUEST=request, - render_prefix=render_prefix) + element_tree = self._replace_xml_by_form(element_tree=element_tree, + form=form, + here=here, + extra_context=extra_context, + render_prefix=render_prefix) report_item.popReport(portal_object, render_prefix = render_prefix) - - return etree.tostring(content) + return element_tree + + def _replace_xml_by_formbox(self, element_tree=None, field_id=None, form=None, REQUEST=None): + draw_xpath = '//draw:frame[@draw:name="%s"]/draw:text-box/*' % field_id + text_list = element_tree.xpath(draw_xpath, namespaces=NAME_SPACE_DICT) + if len(text_list) == 0: + return element_tree + parent = text_list[0].getparent() + parent.clear() + # this form.__call__() possibly has a side effect, + # so must clear the 'here' context for listBox.get_value() + box = form(REQUEST=REQUEST); + REQUEST.set('here', None) + node = etree.XML(box) + # TODO style_copy + if node is not None: + for child in node.getchildren(): + parent.append(child) + return element_tree + + def _replace_xml_by_image_field(self, element_tree=None, image_field=None): + alt = image_field.get_value('description') or image_field.get_value('title') + image_xpath = '//draw:frame[@draw:name="%s"]/*' % image_field.id + image_list = element_tree.xpath(image_xpath, namespaces=NAME_SPACE_DICT) + if len(image_list) > 0: + image_list[0].set("{%s}href" % NAME_SPACE_DICT['xlink'], image_field.absolute_url()) + return element_tree def _append_table_by_listbox(self, - content=None, + element_tree=None, listbox=None, REQUEST=None, render_prefix=None): table_id = listbox.id table_xpath = '//table:table[@table:name="%s"]' % table_id # this list should be one item list - target_table_list = content.xpath(table_xpath, namespaces=NAME_SPACE_DICT) + target_table_list = element_tree.xpath(table_xpath, namespaces=NAME_SPACE_DICT) if len(target_table_list) is 0: - return content + return element_tree target_table = target_table_list[0] newtable = deepcopy(target_table) - # <table:table-header-rows> + table_header_rows_xpath = '%s/table:table-header-rows' % table_xpath table_row_xpath = '%s/table:table-row' % table_xpath table_header_rows_list = newtable.xpath(table_header_rows_xpath, namespaces=NAME_SPACE_DICT) @@ -392,7 +421,6 @@ class ODFStrategy(Implicit): # copy row styles from ODF Document has_header_rows = len(table_header_rows_list) > 0 - LOG('FormPrintout has_header_rows', INFO, has_header_rows) (row_top, row_middle, row_bottom) = self._copy_row_style(table_row_list, has_header_rows=has_header_rows) @@ -411,8 +439,8 @@ class ODFStrategy(Implicit): REQUEST=REQUEST, render_prefix=render_prefix) - # if ODF table has heder rows, does not update the header rows - # if ODF table does not have header rows, insert the listbox title line + # if ODF table has header rows, does not update the header rows + # if does not have header rows, insert the listbox title line is_top = True for (index, listboxline) in enumerate(listboxline_list): listbox_column_list = listboxline.getColumnItemList() @@ -441,9 +469,8 @@ class ODFStrategy(Implicit): else: # report section iteration parent_paragraph.append(newtable) - # TODO: it would be better append a paragraph or linebreak - return content + return element_tree def _copy_row_style(self, table_row_list=[], has_header_rows=False): row_top = None @@ -490,7 +517,7 @@ class ODFStrategy(Implicit): if listbox_column_index >= listbox_column_size: break value = listbox_column_list[listbox_column_index][1] - # if value is None, remaining ODF orinal text + # if value is None, remaining the ODF original text if value is not None: self._set_column_value(column, value) column_span = self._get_column_span_size(column) @@ -502,7 +529,7 @@ class ODFStrategy(Implicit): def _set_column_value(self, column, value): if value is None: # to eliminate a default value, remove "office:*" attributes. - # if remaining "office:*" attribetes, the column shows its default value, + # if remaining these attribetes, the column shows its default value, # such as '0.0', '$0' attrib = column.attrib for key in attrib.keys(): @@ -512,11 +539,25 @@ class ODFStrategy(Implicit): column_value = unicode(str(value),'utf-8') column.text = column_value column_children = column.getchildren() + first_child = None if len(column_children) > 0: - column_children[0].text = column_value + first_child = deepcopy(column_children[0]) + first_child.text = column_value + for child in column_children: + column.remove(child) + column.append(first_child) if column_value != '': - column.set("{%s}value" % NAME_SPACE_DICT['office'], column_value) - + value_attribute = self._get_column_value_attribute(column) + if value_attribute is not None: + column.set(value_attribute, column_value) + + def _get_column_value_attribute(self, column): + attrib = column.attrib + for key in attrib.keys(): + if key.endswith("value"): + return key + return None + def _get_column_span_size(self, column=None): span_attribute = "{%s}number-columns-spanned" % NAME_SPACE_DICT['table'] column_span = 1 @@ -538,7 +579,6 @@ class ODFStrategy(Implicit): return [] odf_cell_list = row_middle.findall("{%s}table-cell" % NAME_SPACE_DICT['table']) column_span_list = [] - span_attribute = "{%s}number-columns-spanned" % NAME_SPACE_DICT['table'] for column in odf_cell_list: column_span = self._get_column_span_size(column) column_span_list.append(column_span) diff --git a/product/ERP5OOo/tests/testFormPrintout.py b/product/ERP5OOo/tests/testFormPrintout.py index 36faa803de951b0c669d9acc7075fc81f0531a1b..7ae0d4793b1f03f3f75c5f13dcc518c2919da037 100644 --- a/product/ERP5OOo/tests/testFormPrintout.py +++ b/product/ERP5OOo/tests/testFormPrintout.py @@ -27,11 +27,8 @@ ############################################################################## import unittest -from threading import Thread from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase from AccessControl.SecurityManagement import newSecurityManager -from Products.ERP5Form.Selection import Selection -from Products.ERP5Form.SelectionTool import SelectionTool from Products.ERP5OOo.OOoUtils import OOoBuilder from zLOG import LOG , INFO import os @@ -270,7 +267,7 @@ class TestFormPrintout(ERP5TypeTestCase): self.assertFalse(content_xml.find("foo_title_2") > 0) self.assertTrue(content_xml.find("foo_title_3") > 0) - # 4. Irregular case: listbox have not a stat line, but table has a stat line + # 4. Irregular case: listbox has not a stat line, but table has a stat line if test1._getOb("foo_2", None) is None: test1.newContent("foo_2", portal_type='Foo Line') get_transaction().commit() @@ -300,7 +297,7 @@ class TestFormPrintout(ERP5TypeTestCase): self.assertFalse(content_xml.find("foo_title_3") > 0) self.assertTrue(content_xml.find("foo_title_4") > 0) - # 5. Normal case: the listobx of a form and the ODF table are same layout + # 5. Normal case: the listobx and the ODF table are same layout foo_form.manage_renameObject('listbox', 'listbox2', REQUEST=request) listbox2 = foo_form.listbox2 test1.foo_1.setTitle('foo_title_5') diff --git a/product/ERP5OOo/tests/test_document/Foo_001.odt b/product/ERP5OOo/tests/test_document/Foo_001.odt index 63e3c38cd56469cbe813102c78bff5041c6bb433..a4e51cd6d71f8a35ca8e10f59481d2b96cb4bed5 100644 Binary files a/product/ERP5OOo/tests/test_document/Foo_001.odt and b/product/ERP5OOo/tests/test_document/Foo_001.odt differ