############################################################################## # # Copyright (c) 2009 Nexedi KK and Contributors. All Rights Reserved. # Tatuya Kamada <tatuya@nexedi.com> # # WARNING: This program as such is intended to be used by professional # programmers who take the whole responsability of assessing all potential # consequences resulting from its eventual inadequacies and bugs # End users who are looking for a ready-to-use solution with commercial # garantees and support are strongly adviced to contract a Free Software # Service Company # # 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 2 # 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, write to the Free Software # Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, # USA. ############################################################################## from Products.PageTemplates.PageTemplateFile import PageTemplateFile from Products.CMFCore.utils import _checkPermission from Products.ERP5Type import PropertySheet, Permissions from Products.ERP5Form.ListBox import ListBox from Products.ERP5Form.FormBox import FormBox from Products.ERP5Form.ReportBox import ReportBox from Products.ERP5Form.ImageField import ImageField from Products.ERP5OOo.OOoUtils import OOoBuilder from Products.CMFCore.exceptions import AccessControl_Unauthorized from Acquisition import Implicit, aq_base from Products.ERP5Type.Globals import InitializeClass, DTMLFile, Persistent, get_request from AccessControl import ClassSecurityInfo from AccessControl.Role import RoleManager from OFS.SimpleItem import Item from urllib import quote, quote_plus from copy import deepcopy from lxml import etree from zLOG import LOG, DEBUG, INFO, WARNING from mimetypes import guess_extension from DateTime import DateTime from decimal import Decimal from xml.sax.saxutils import escape import re try: from webdav.Lockable import ResourceLockedError SUPPORTS_WEBDAV_LOCKS = 1 except ImportError: SUPPORTS_WEBDAV_LOCKS = 0 # Constructors manage_addFormPrintout = DTMLFile("dtml/FormPrintout_add", globals()) def addFormPrintout(self, id, title="", form_name='', template='', REQUEST=None): """Add form printout to folder. Keyword arguments: id -- the id of the new form printout to add title -- the title of the form printout to add form_name -- the name of a form which contains data to printout template -- the name of a template which describes printout layout """ # add actual object id = self._setObject(id, FormPrintout(id, title, form_name, template)) # respond to the add_and_edit button if necessary add_and_edit(self, id, REQUEST) return '' def add_and_edit(self, id, REQUEST): """Helper method to point to the object's management screen if 'Add and Edit' button is pressed. Keyword arguments: id -- the id of the object we just added """ if REQUEST is None: return try: u = self.DestinationURL() except AttributeError: u = REQUEST['URL1'] if REQUEST['submit'] == " Add and Edit ": u = "%s/%s" % (u, quote(id)) REQUEST.RESPONSE.redirect(u+'/manage_main') class FormPrintout(Implicit, Persistent, RoleManager, Item): """Form Printout The Form Printout enables to create a ODF document. 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. WARNING: The Form Printout currently supports only ODT format document. The functions status: Fields -> Paragraphs: supported ListBox -> Table: supported Report Section -> Frames or Sections: supported FormBox -> Frame: experimentally supported ImageField -> Photo: supported styles.xml: supported meta.xml: not supported yet """ meta_type = "ERP5 Form Printout" icon = "www/form_printout_icon.png" # Declarative Security security = ClassSecurityInfo() # Declarative properties property_sheets = ( PropertySheet.Base , PropertySheet.SimpleItem) # Constructors constructors = (manage_addFormPrintout, addFormPrintout) # Tabs in ZMI manage_options = (( {'label':'Edit', 'action':'manage_editFormPrintout'}, {'label':'View', 'action': '' }, ) + Item.manage_options) security.declareProtected('View management screens', 'manage_editFormPrintout') manage_editFormPrintout = PageTemplateFile('www/FormPrintout_manageEdit', globals(), __name__='manage_editFormPrintout') manage_editFormPrintout._owner = None # alias definition to do 'add_and_edit' security.declareProtected('View management screens', 'manage_main') manage_main = manage_editFormPrintout # default attributes template = None form_name = None def __init__(self, id, title='', form_name='', template=''): """Initialize id, title, form_name, template. Keyword arguments: id -- the id of a form printout title -- the title of a form printout form_name -- the name of a form which as a document content template -- the name of a template which as a document layout """ self.id = id self.title = title self.form_name = form_name self.template = template security.declareProtected('View', 'index_html') def index_html(self, icon=0, preview=0, width=None, height=None, REQUEST=None): """Render and view a printout document.""" obj = getattr(self, 'aq_parent', None) if obj is not None: container = obj.aq_inner.aq_parent if not _checkPermission(Permissions.View, obj): raise AccessControl_Unauthorized('This document is not authorized for view.') else: container = None form = getattr(obj, self.form_name) if self.template is None or self.template == '': raise ValueError, 'Can not create a ODF Document without a printout template' printout_template = getattr(obj, self.template) report_method = None if hasattr(form, 'report_method'): report_method = getattr(obj, form.report_method) extra_context = dict(container=container, printout_template=printout_template, report_method=report_method, form=form, here=obj) # set property to do aquisition content_type = printout_template.content_type self.strategy = self._createStrategy(content_type) printout = self.strategy.render(extra_context=extra_context) return self._oooConvertByFormat(printout, content_type=content_type, extra_context=extra_context, REQUEST=REQUEST) security.declareProtected('View', '__call__') __call__ = index_html security.declareProtected('Manage properties', 'doSettings') def doSettings(self, REQUEST, title='', form_name='', template=''): """Change title, form_name, template.""" if SUPPORTS_WEBDAV_LOCKS and self.wl_isLocked(): raise ResourceLockedError, "File is locked via WebDAV" self.form_name = form_name self.template = template self.title = title message = "Saved changes." if getattr(self, '_v_warnings', None): message = ("<strong>Warning:</strong> <i>%s</i>" % '<br>'.join(self._v_warnings)) return self.manage_editFormPrintout(manage_tabs_message=message) def _createStrategy(slef, content_type=''): if guess_extension(content_type) == '.odt': return ODTStrategy() raise ValueError, 'Do not support the template type:%s' % content_type def _oooConvertByFormat(self, printout, content_type=None, extra_context={}, REQUEST=None): """ Convert the ODF document into the given format. Keyword arguments: printout -- ODF document content_type -- the content type of the printout extra_context -- extra_context including a format REQUEST -- Request object """ options = extra_context.get('options', {}) format = None if REQUEST is not None: format = options.get('format', REQUEST.get('format', None)) if format is None: if REQUEST is not None: REQUEST.RESPONSE.setHeader('Content-Type','%s; charset=utf-8' % content_type) REQUEST.RESPONSE.setHeader('Content-disposition', 'inline;filename="%s%s"' % (self.title_or_id(), guess_extension(content_type))) return printout from Products.ERP5Type.Document import newTempOOoDocument tmp_ooo = newTempOOoDocument(self, self.title_or_id()) tmp_ooo.edit(base_data=printout, fname=self.title_or_id(), source_reference=self.title_or_id(), base_content_type=content_type) tmp_ooo.oo_data = printout mime, data = tmp_ooo.convert(format) if REQUEST is not None: REQUEST.RESPONSE.setHeader('Content-type', mime) REQUEST.RESPONSE.setHeader('Content-disposition', 'attachment;filename="%s.%s"' % (self.title_or_id(), format)) return data InitializeClass(FormPrintout) class ODFStrategy(Implicit): """ODFStrategy creates a ODF Document. """ odf_existent_name_list = [] def render(self, extra_context={}): """Render a odf document, form as a content, template as a template. Keyword arguments: 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 'form' : the form as a content """ here = extra_context['here'] if here is None: 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: raise ValueError, 'Can not create a ODF Document without a printout template' odf_template = extra_context['printout_template'] # 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 can be a template ooo_document = odf_template # Create a new builder instance ooo_builder = OOoBuilder(ooo_document) self.odf_existent_name_list = ooo_builder.getNameList() # content.xml ooo_builder = self._replaceContentXml(ooo_builder=ooo_builder, extra_context=extra_context) # styles.xml ooo_builder = self._replaceStylesXml(ooo_builder=ooo_builder, extra_context=extra_context) # meta.xml is not supported yet # ooo_builder = self._replaceMetaXml(ooo_builder=ooo_builder, extra_context=extra_context) # Update the META informations ooo_builder.updateManifest() ooo = ooo_builder.render(name=odf_template.title or odf_template.id) return ooo def _replaceContentXml(self, ooo_builder=None, extra_context=None): content_xml = ooo_builder.extract('content.xml') # mapping ERP5Form to ODF form = extra_context['form'] here = getattr(self, 'aq_parent', None) content_element_tree = etree.XML(content_xml) content_element_tree = self._replaceXmlByForm(element_tree=content_element_tree, form=form, here=here, extra_context=extra_context, ooo_builder=ooo_builder) # mapping ERP5Report report method to ODF report_method=extra_context.get('report_method') base_name = (report_method is not None) and report_method.__name__ or None content_element_tree = self._replaceXmlByReportSection(element_tree=content_element_tree, extra_context=extra_context, report_method=report_method, base_name=base_name, ooo_builder=ooo_builder) content_xml = etree.tostring(content_element_tree, encoding='utf-8') # Replace content.xml in master openoffice template ooo_builder.replace('content.xml', content_xml) return ooo_builder # this method not supported yet def _replaceStylesXml(self, ooo_builder=None, extra_context=None): """ replacing styles.xml file in a ODF document """ styles_xml = ooo_builder.extract('styles.xml') form = extra_context['form'] here = getattr(self, 'aq_parent', None) styles_element_tree = etree.XML(styles_xml) styles_element_tree = self._replaceXmlByForm(element_tree=styles_element_tree, form=form, here=here, extra_context=extra_context, ooo_builder=ooo_builder) styles_xml = etree.tostring(styles_element_tree, encoding='utf-8') ooo_builder.replace('styles.xml', styles_xml) return ooo_builder # this method not implemented yet def _replaceMetaXml(self, ooo_builder=None, extra_context=None): """ replacing meta.xml file in a ODF document """ return ooo_builder def _replaceXmlByForm(self, element_tree=None, form=None, here=None, extra_context=None, ooo_builder=None, iteration_index=0): field_list = form.get_fields(include_disabled=1) REQUEST = get_request() for (count, field) in enumerate(field_list): if isinstance(field, ListBox): element_tree = self._appendTableByListbox(element_tree=element_tree, listbox=field, REQUEST=REQUEST, iteration_index=iteration_index) elif isinstance(field, FormBox): if not hasattr(here, field.get_value('formbox_target_id')): continue sub_form = getattr(here, field.get_value('formbox_target_id')) content = self._replaceXmlByFormbox(element_tree=element_tree, field=field, form=sub_form, extra_context=extra_context, ooo_builder=ooo_builder, iteration_index=iteration_index) elif isinstance(field, ReportBox): report_method = getattr(field, field.get_value('report_method'), None) element_tree = self._replaceXmlByReportSection(element_tree=element_tree, extra_context=extra_context, report_method=report_method, base_name=field.id, ooo_builder=ooo_builder) elif isinstance(field, ImageField): element_tree = self._replaceXmlByImageField(element_tree=element_tree, image_field=field, ooo_builder=ooo_builder, iteration_index=iteration_index) else: element_tree = self._replaceNodeViaReference(element_tree=element_tree, field=field, iteration_index=iteration_index) return element_tree def _replaceNodeViaReference(self, element_tree=None, field=None, iteration_index=0): """replace nodes (e.g. paragraphs) via ODF reference""" element_tree = self._replaceNodeViaRangeReference(element_tree=element_tree, field=field) element_tree = self._replaceNodeViaPointReference(element_tree=element_tree, field=field) return element_tree def _renderField(self, field): # XXX It looks ugly to use render_pdf to extract text. Probably # it should be renamed to render_text. return field.render_pdf(field.get_value('default')) def _replaceNodeViaPointReference(self, element_tree=None, field=None, iteration_index=0): """replace via ODF point reference point reference example: <text:reference-mark text:name="invoice-date"/> """ field_id = field.id field_value = self._renderField(field) value = self._toUnicodeString(field_value) # text:reference-mark text:name="invoice-date" reference_xpath = '//text:reference-mark[@text:name="%s"]' % field_id reference_list = element_tree.xpath(reference_xpath, namespaces=element_tree.nsmap) if len(reference_list) > 0: target_node = reference_list[0] paragraph_node = reference_list[0].getparent() if not isinstance(field_value, list): # remove such a "bbb": <text:p>aaa<text:line-break/>bbb</text:p> for child in paragraph_node.getchildren(): child.tail = '' paragraph_node.text = value else: self._appendParagraphsWithLineList(target_node=target_node, line_list=field_value) # set when using report section self._setUniqueElementName(base_name=field.id, iteration_index=iteration_index, xpath=reference_xpath, element_tree=element_tree) return element_tree def _replaceNodeViaRangeReference(self, element_tree=None, field=None, iteration_index=0): """replace via ODF range reference range reference example: <text:reference-mark-start text:name="week"/>Monday<text:reference-mark-end text:name="week"/> or <text:reference-mark-start text:name="my_title"/><text:span text:style-name="T1">title</text:span> <text:reference-mark-end text:name="my_title"/> """ field_value = self._renderField(field) value = self._toUnicodeString(field_value) range_reference_xpath = '//text:reference-mark-start[@text:name="%s"]' % field.id reference_list = element_tree.xpath(range_reference_xpath, namespaces=element_tree.nsmap) if len(reference_list) is 0: return element_tree target_node = reference_list[0] if not isinstance(field_value, list): next_node = target_node.getnext() span_tag_name = '{%s}span' % element_tree.nsmap['text'] if next_node is not None and next_node.tag == span_tag_name: next_node.text = value else: target_node.tail = value # clear text until 'reference-mark-end' for node in target_node.itersiblings(): end_tag_name = '{%s}reference-mark-end' % element_tree.nsmap['text'] name_attribute = '{%s}name' % element_tree.nsmap['text'] if node.tag == end_tag_name and node.get(name_attribute) == field.id: break node.tail = '' else: self._appendParagraphsWithLineList(target_node=target_node, line_list=field_value) # set when using report section self._setUniqueElementName(base_name=field.id, iteration_index=iteration_index, xpath=range_reference_xpath, element_tree=element_tree) return element_tree def _appendParagraphsWithLineList(self, target_node=None, line_list=None): """create paragraphs example: -- first line second line -- <p:text> first line </p:text> <p:text> second line </p:text> """ paragraph_node = target_node.getparent() parent_node = paragraph_node.getparent() paragraph_list = [] for line in line_list: p = deepcopy(paragraph_node) for child in p.getchildren(): child.tail = '' value = self._toUnicodeString(line) p.text = value paragraph_list.append(p) paragraph_node_index = parent_node.index(paragraph_node) parent_node.remove(paragraph_node) for (index, paragraph) in enumerate(paragraph_list): parent_node.insert(paragraph_node_index, paragraph) paragraph_node_index = paragraph_node_index + 1 def _replaceXmlByReportSection(self, element_tree=None, extra_context=None, report_method=None, base_name=None, ooo_builder=None): if report_method is None: return element_tree report_section_list = report_method() portal_object = self.getPortalObject() target_tuple = self._pickUpTargetSection(base_name=base_name, report_section_list=report_section_list, element_tree=element_tree) if target_tuple is None: return element_tree target_xpath, original_target = target_tuple office_body = original_target.getparent() target_index = office_body.index(original_target) temporary_element_tree = deepcopy(original_target) for (index, report_item) in enumerate(report_section_list): report_item.pushReport(portal_object, render_prefix=None) here = report_item.getObject(portal_object) form_id = report_item.getFormId() form = getattr(here, form_id) target_element_tree = deepcopy(temporary_element_tree) # remove original target in the ODF template if index is 0: office_body.remove(original_target) else: self._setUniqueElementName(base_name=base_name, iteration_index=index, xpath=target_xpath, element_tree=target_element_tree) target_element_tree = self._replaceXmlByForm(element_tree=target_element_tree, form=form, here=here, extra_context=extra_context, ooo_builder=ooo_builder, iteration_index=index) office_body.insert(target_index, target_element_tree) target_index += 1 report_item.popReport(portal_object, render_prefix=None) return element_tree def _pickUpTargetSection(self, base_name='', report_section_list=[], element_tree=None): """pick up a ODF target object to iterate ReportSection base_name -- the target name to replace in an ODF document report_section_list -- ERP5Form ReportSection List which was created by a report method element_tree -- XML ElementTree object """ frame_xpath = '//draw:frame[@draw:name="%s"]' % base_name frame_list = element_tree.xpath(frame_xpath, namespaces=element_tree.nsmap) # <text:section text:style-name="Sect2" text:name="Section2"> section_xpath = '//text:section[@text:name="%s"]' % base_name section_list = element_tree.xpath(section_xpath, namespaces=element_tree.nsmap) if len(frame_list) is 0 and len(section_list) is 0: return None office_body = None original_target = None target_xpath = '' if len(frame_list) > 0: frame = frame_list[0] original_target = frame.getparent() target_xpath = frame_xpath elif len(section_list) > 0: original_target = section_list[0] target_xpath = section_xpath office_body = original_target.getparent() # remove if no report section if len(report_section_list) is 0: office_body.remove(original_target) return None return (target_xpath, original_target) def _setUniqueElementName(self, base_name='', iteration_index=0, xpath='', element_tree=None): """create a unique element name and set it to the element tree Keyword arguments: base_name -- the base name of the element iteration_index -- iteration index xpath -- xpath expression which was used to search the element element_tree -- element tree """ if iteration_index is 0: return def getNameAttribute(target_element=None): if target_element is None: return None attrib = target_element.attrib for key in attrib.keys(): if key.endswith("}name"): return key return None odf_element_name = "%s_%s" % (base_name, iteration_index) result_list = element_tree.xpath(xpath, namespaces=element_tree.nsmap) if len(result_list) is 0: return target_element = result_list[0] name_attribute = getNameAttribute(target_element) if name_attribute is not None: target_element.set(name_attribute, odf_element_name) def _replaceXmlByFormbox(self, element_tree=None, field=None, form=None, extra_context=None, ooo_builder=None, iteration_index=0): field_id = field.id enabled = field.get_value('enabled') draw_xpath = '//draw:frame[@draw:name="%s"]/draw:text-box/*' % field_id text_list = element_tree.xpath(draw_xpath, namespaces=element_tree.nsmap) if len(text_list) == 0: return element_tree target_element = text_list[0] frame_paragraph = target_element.getparent() office_body = frame_paragraph.getparent() if not enabled: office_body.remove(frame_paragraph) return element_tree # set when using report section self._setUniqueElementName(base_name=field_id, iteration_index=iteration_index, xpath=draw_xpath, element_tree=element_tree) self._replaceXmlByForm(element_tree=frame_paragraph, form=form, here=extra_context['here'], extra_context=extra_context, ooo_builder=ooo_builder, iteration_index=iteration_index) return element_tree def _replaceXmlByImageField(self, element_tree=None, image_field=None, ooo_builder=None, iteration_index=0): 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=element_tree.nsmap) if len(image_list) is 0: return element_tree path = image_field.get_value('default') image_node = image_list[0] image_frame = image_node.getparent() if path is not None: path = path.encode() picture = self.getPortalObject().restrictedTraverse(path) picture_data = getattr(aq_base(picture), 'data', None) if picture_data is None: image_frame = image_node.getparent() image_frame.remove(image_node) return element_tree picture_type = picture.getContentType() picture_path = self._createOdfUniqueFileName(path=path, picture_type=picture_type) ooo_builder.addFileEntry(picture_path, media_type=picture_type, content=picture_data) picture_size = self._getPictureSize(picture, image_node) 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}height' % element_tree.nsmap['svg'], picture_size[1]) # set when using report section self._setUniqueElementName(base_name=image_field.id, iteration_index=iteration_index, xpath=image_xpath, element_tree=element_tree) return element_tree def _createOdfUniqueFileName(self, path='', picture_type=''): extension = guess_extension(picture_type) picture_path = 'Pictures/%s%s' % (quote_plus(path), extension) if picture_path not in self.odf_existent_name_list: return picture_path number = 0 while True: picture_path = 'Pictures/%s_%s%s' % (path, number, extension) if picture_path not in self.odf_existent_name_list: return picture_path number += 1 def _getPictureSize(self, picture=None, image_node=None): if picture is None or image_node is None: return ('0cm', '0cm') draw_frame_node = image_node.getparent() svg_width = draw_frame_node.attrib.get('{%s}width' % draw_frame_node.nsmap['svg']) svg_height = draw_frame_node.attrib.get('{%s}height' % draw_frame_node.nsmap['svg']) if svg_width is None or svg_height is None: return ('0cm', '0cm') # if not match causes exception width_tuple = re.match("(\d[\d\.]*)(.*)", svg_width).groups() height_tuple = re.match("(\d[\d\.]*)(.*)", svg_height).groups() unit = width_tuple[1] w = Decimal(width_tuple[0]) h = Decimal(height_tuple[0]) aspect_ratio = 1 try: # try image properties aspect_ratio = Decimal(picture.width) / Decimal(picture.height) except (TypeError, ZeroDivisionError): try: # try ERP5.Document.Image API height = Decimal(picture.getHeight()) if height: aspect_ratio = Decimal(picture.getWidth()) / height except AttributeError: # fallback to Photo API height = float(picture.height()) if height: aspect_ratio = Decimal(picture.width()) / height resize_w = h * aspect_ratio resize_h = w / aspect_ratio if resize_w < w: w = resize_w elif resize_h < h: h = resize_h return (str(w) + unit, str(h) + unit) def _appendTableByListbox(self, element_tree=None, listbox=None, REQUEST=None, iteration_index=0): table_id = listbox.id table_xpath = '//table:table[@table:name="%s"]' % table_id # this list should be one item list target_table_list = element_tree.xpath(table_xpath, namespaces=element_tree.nsmap) if len(target_table_list) is 0: return element_tree target_table = target_table_list[0] newtable = deepcopy(target_table) 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=element_tree.nsmap) table_row_list = newtable.xpath(table_row_xpath, namespaces=element_tree.nsmap) # copy row styles from ODF Document has_header_rows = len(table_header_rows_list) > 0 (row_top, row_middle, row_bottom) = self._copyRowStyle(table_row_list, has_header_rows=has_header_rows) # create style-name and table-row dictionary if a reference name is set style_name_row_dictionary = self._createStyleNameRowDictionary(table_row_list) # clear original table parent_paragraph = target_table.getparent() target_index = parent_paragraph.index(target_table) parent_paragraph.remove(target_table) # clear rows for table_row in table_row_list: newtable.remove(table_row) listboxline_list = listbox.get_value('default', render_format='list', REQUEST=REQUEST, render_prefix=None) # 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 last_index = len(listboxline_list) - 1 for (index, listboxline) in enumerate(listboxline_list): listbox_column_list = listboxline.getColumnItemList() row_style_name = listboxline.getRowCSSClassName() if listboxline.isTitleLine() and not has_header_rows: row = deepcopy(row_top) row = self._updateColumnValue(row, listbox_column_list) newtable.append(row) is_top = False elif listboxline.isDataLine() and is_top: if style_name_row_dictionary.has_key(row_style_name): row = deepcopy(style_name_row_dictionary[row_style_name]) else: row = deepcopy(row_top) row = self._updateColumnValue(row, listbox_column_list) newtable.append(row) is_top = False elif listboxline.isStatLine() or (index is last_index and listboxline.isDataLine()): row = deepcopy(row_bottom) row = self._updateColumnStatValue(row, listbox_column_list, row_middle) newtable.append(row) elif index > 0 and listboxline.isDataLine(): if style_name_row_dictionary.has_key(row_style_name): row = deepcopy(style_name_row_dictionary[row_style_name]) else: row = deepcopy(row_middle) row = self._updateColumnValue(row, listbox_column_list) newtable.append(row) self._setUniqueElementName(base_name=table_id, iteration_index=iteration_index, xpath=table_xpath, element_tree=newtable) parent_paragraph.insert(target_index, newtable) return element_tree def _copyRowStyle(self, table_row_list=[], has_header_rows=False): def removeOfficeAttribute(row): if row is None or has_header_rows: return odf_cell_list = row.findall("{%s}table-cell" % row.nsmap['table']) for odf_cell in odf_cell_list: self._removeColumnValue(odf_cell) row_top = None row_middle = None row_bottom = None if len(table_row_list) > 0: if len(table_row_list) is 1: row_top = deepcopy(table_row_list[0]) row_middle = deepcopy(table_row_list[0]) row_bottom = deepcopy(table_row_list[0]) elif len(table_row_list) is 2 and has_header_rows: row_top = deepcopy(table_row_list[0]) row_middle = deepcopy(table_row_list[0]) row_bottom = deepcopy(table_row_list[-1]) elif len(table_row_list) is 2 and not has_header_rows: row_top = deepcopy(table_row_list[0]) row_middle = deepcopy(table_row_list[1]) row_bottom = deepcopy(table_row_list[-1]) elif len(table_row_list) >= 2: row_top = deepcopy(table_row_list[0]) row_middle = deepcopy(table_row_list[1]) row_bottom = deepcopy(table_row_list[-1]) # remove office attribute if create a new header row removeOfficeAttribute(row_top) return (row_top, row_middle, row_bottom) def _createStyleNameRowDictionary(self, table_row_list=[]): """create stylename and table row dictionary if a style name reference is set""" style_name_row_dictionary = {} for table_row in table_row_list: reference_element = table_row.find('.//*[@{%s}name]' % table_row.nsmap['text']) if reference_element is not None: name = reference_element.attrib['{%s}name' % table_row.nsmap['text']] style_name_row_dictionary[name] = deepcopy(table_row) return style_name_row_dictionary def _updateColumnValue(self, row=None, listbox_column_list=[]): odf_cell_list = row.findall("{%s}table-cell" % row.nsmap['table']) odf_cell_list_size = len(odf_cell_list) listbox_column_size = len(listbox_column_list) for (column_index, column) in enumerate(odf_cell_list): if column_index >= listbox_column_size: break value = listbox_column_list[column_index][1] self._setColumnValue(column, value) return row def _updateColumnStatValue(self, row=None, listbox_column_list=[], row_middle=None): """stat line is capable of column span setting""" if row_middle is None: return row odf_cell_list = row.findall("{%s}table-cell" % row.nsmap['table']) odf_column_span_list = self._getOdfColumnSpanList(row_middle) listbox_column_size = len(listbox_column_list) listbox_column_index = 0 for (column_index, column) in enumerate(odf_cell_list): if listbox_column_index >= listbox_column_size: break value = listbox_column_list[listbox_column_index][1] self._setColumnValue(column, value) column_span = self._getColumnSpanSize(column) listbox_column_index = self._nextListboxColumnIndex(column_span, listbox_column_index, odf_column_span_list) return row def _setColumnValue(self, column, value): self._clearColumnValue(column) if value is None: self._removeColumnValue(column) column_value, table_content = self._translateValueIntoColumnContent(value, column) for child in column.getchildren(): column.remove(child) if table_content is not None: column.append(table_content) value_attribute = self._getColumnValueAttribute(column) if value_attribute is not None and column_value is not None: column.set(value_attribute, column_value) def _translateValueIntoColumnContent(self, value, column): """translate a value as a table content""" table_content = None column_children = column.getchildren() if len(column_children) > 0: table_content = deepcopy(column_children[0]) # create a tempolaly etree object to generate a content paragraph fragment = self._valueAsOdfXmlElement(value=value, element_tree=column) column_value = None if table_content is not None: table_content.text = fragment.text for element in fragment.getchildren(): table_content.append(element) column_value = " ".join([x for x in table_content.itertext()]) return (column_value, table_content) def _valueAsOdfXmlElement(self, value=None, element_tree=None): """values as ODF XML element replacing: \t -> tabs \n -> line-breaks DateTime -> Y-m-d """ if value is None: value = '' translated_value = str(value) if isinstance(value, DateTime): translated_value = value.strftime('%Y-%m-%d') translated_value = escape(translated_value) text_namespace = element_tree.nsmap['text'] tab_element_str = '<text:tab xmlns:text="%s"/>' % text_namespace line_break_element_str ='<text:line-break xmlns:text="%s"/>' % text_namespace translated_value = translated_value.replace('\t', tab_element_str) translated_value = translated_value.replace('\r', '') translated_value = translated_value.replace('\n', line_break_element_str) translated_value = unicode(str(translated_value),'utf-8') # create a paragraph template = '<text:p xmlns:text="%s">%s</text:p>' fragment_element_tree = etree.XML(template % (text_namespace, translated_value)) return fragment_element_tree def _removeColumnValue(self, column): # to eliminate a default value, remove "office:*" attributes. # if remaining these attribetes, the column shows its default value, # such as '0.0', '$0' attrib = column.attrib for key in attrib.keys(): if key.startswith("{%s}" % column.nsmap['office']): del attrib[key] column.text = '' column_children = column.getchildren() for child in column_children: column.remove(child) def _clearColumnValue(self, column): attrib = column.attrib for key in attrib.keys(): value_attribute = self._getColumnValueAttribute(column) if value_attribute is not None: column.set(value_attribute, '') column.text = '' column_children = column.getchildren() for child in column_children: # clear data except style style_attribute_tuple = self._getStyleAttributeTuple(child) child.clear() if style_attribute_tuple is not None: child.set(style_attribute_tuple[0], style_attribute_tuple[1]) def _getStyleAttributeTuple(self, element): attrib = element.attrib for key in attrib.keys(): if key.endswith('style-name'): return (key, attrib[key]) return None def _getColumnValueAttribute(self, column): attrib = column.attrib for key in attrib.keys(): if key.endswith("value"): return key return None def _getColumnSpanSize(self, column=None): span_attribute = "{%s}number-columns-spanned" % column.nsmap['table'] column_span = 1 if column.attrib.has_key(span_attribute): column_span = int(column.attrib[span_attribute]) return column_span def _nextListboxColumnIndex(self, span=0, current_index=0, column_span_list=[]): hops = 0 index = current_index while hops < span: column_span = column_span_list[index] hops += column_span index += 1 return index def _getOdfColumnSpanList(self, row_middle=None): if row_middle is None: return [] odf_cell_list = row_middle.findall("{%s}table-cell" % row_middle.nsmap['table']) column_span_list = [] for column in odf_cell_list: column_span = self._getColumnSpanSize(column) column_span_list.append(column_span) return column_span_list def _toUnicodeString(self, field_value = None): value = '' if isinstance(field_value, unicode): value = field_value elif field_value is not None: value = unicode(str(field_value), 'utf-8') return value class ODTStrategy(ODFStrategy): """ODTStrategy create a ODT Document from a form and a ODT template""" pass