# HG changeset patch # User Aurelien Campeas # Date 1250786253 -7200 # Node ID e5cef8ff5857c2a68430eaa22858b2ea063355f1 # Parent 17224e90a1c40fccb3c8ed756121e9d8883b02b0# Parent 36b11249014ef2b6a89bbfc7415183347ad4ca57 merge diff -r 17224e90a1c4 -r e5cef8ff5857 common/test/unittest_uilib.py --- a/common/test/unittest_uilib.py Thu Aug 20 17:57:56 2009 +0200 +++ b/common/test/unittest_uilib.py Thu Aug 20 18:37:33 2009 +0200 @@ -81,45 +81,6 @@ got = uilib.text_cut(text, 30) self.assertEquals(got, expected) -tree = ('root', ( - ('child_1_1', ( - ('child_2_1', ()), ('child_2_2', ( - ('child_3_1', ()), - ('child_3_2', ()), - ('child_3_3', ()), - )))), - ('child_1_2', (('child_2_3', ()),)))) - -generated_html = """\ - - - - - - - - - - - -
root
  
child_1_1
  
child_2_1
   
      
      
child_2_2
  
child_3_1
      
         
child_3_2
      
         
child_3_3
      
   
child_1_2
  
child_2_3
   
      
\ -""" - -def make_tree(tuple): - n = Node(tuple[0]) - for child in tuple[1]: - n.append(make_tree(child)) - return n - -class UIlibHTMLGenerationTC(TestCase): - """ a basic tree node, caracterised by an id""" - def setUp(self): - """ called before each test from this class """ - self.o = make_tree(tree) - - def test_generated_html(self): - s = uilib.render_HTML_tree(self.o, selected_node="child_2_2") - self.assertTextEqual(s, generated_html) if __name__ == '__main__': diff -r 17224e90a1c4 -r e5cef8ff5857 common/uilib.py --- a/common/uilib.py Thu Aug 20 17:57:56 2009 +0200 +++ b/common/uilib.py Thu Aug 20 18:37:33 2009 +0200 @@ -12,7 +12,6 @@ import csv import re -from urllib import quote as urlquote from StringIO import StringIO from logilab.mtconverter import xml_escape, html_unescape @@ -264,124 +263,6 @@ res = unicode(res, 'UTF8') return res -def render_HTML_tree(tree, selected_node=None, render_node=None, caption=None): - """ - Generate a pure HTML representation of a tree given as an instance - of a logilab.common.tree.Node - - selected_node is the currently selected node (if any) which will - have its surrounding
have id="selected" (which default - to a bold border libe with the default CSS). - - render_node is a function that should take a Node content (Node.id) - as parameter and should return a string (what will be displayed - in the cell). - - Warning: proper rendering of the generated html code depends on html_tree.css - """ - tree_depth = tree.depth_down() - if render_node is None: - render_node = str - - # helper function that build a matrix from the tree, like: - # +------+-----------+-----------+ - # | root | child_1_1 | child_2_1 | - # | root | child_1_1 | child_2_2 | - # | root | child_1_2 | | - # | root | child_1_3 | child_2_3 | - # | root | child_1_3 | child_2_4 | - # +------+-----------+-----------+ - # from: - # root -+- child_1_1 -+- child_2_1 - # | | - # | +- child_2_2 - # +- child_1_2 - # | - # +- child1_3 -+- child_2_3 - # | - # +- child_2_2 - def build_matrix(path, matrix): - if path[-1].is_leaf(): - matrix.append(path[:]) - else: - for child in path[-1].children: - build_matrix(path[:] + [child], matrix) - - matrix = [] - build_matrix([tree], matrix) - - # make all lines in the matrix have the same number of columns - for line in matrix: - line.extend([None]*(tree_depth-len(line))) - for i in range(len(matrix)-1, 0, -1): - prev_line, line = matrix[i-1:i+1] - for j in range(len(line)): - if line[j] == prev_line[j]: - line[j] = None - - # We build the matrix of link types (between 2 cells on a line of the matrix) - # link types are : - link_types = {(True, True, True ): 1, # T - (False, False, True ): 2, # | - (False, True, True ): 3, # + (actually, vert. bar with horiz. bar on the right) - (False, True, False): 4, # L - (True, True, False): 5, # - - } - links = [] - for i, line in enumerate(matrix): - links.append([]) - for j in range(tree_depth-1): - cell_11 = line[j] is not None - cell_12 = line[j+1] is not None - cell_21 = line[j+1] is not None and line[j+1].next_sibling() is not None - link_type = link_types.get((cell_11, cell_12, cell_21), 0) - if link_type == 0 and i > 0 and links[i-1][j] in (1, 2, 3): - link_type = 2 - links[-1].append(link_type) - - - # We can now generate the HTML code for the - s = u'
\n' - if caption: - s += '\n' % caption - - for i, link_line in enumerate(links): - line = matrix[i] - - s += '' - for j, link_cell in enumerate(link_line): - cell = line[j] - if cell: - if cell.id == selected_node: - s += '' % (render_node(cell.id)) - else: - s += '' % (render_node(cell.id)) - else: - s += '' - s += '' % link_cell - s += '' % link_cell - - cell = line[-1] - if cell: - if cell.id == selected_node: - s += '' % (render_node(cell.id)) - else: - s += '' % (render_node(cell.id)) - else: - s += '' - - s += '\n' - if link_line: - s += '' - for j, link_cell in enumerate(link_line): - s += '' % link_cell - s += '' % link_cell - s += '\n' - - s += '
%s
%s
%s
   
%s
%s
 
  
' - return s - - # traceback formatting ######################################################## diff -r 17224e90a1c4 -r e5cef8ff5857 debian/control --- a/debian/control Thu Aug 20 17:57:56 2009 +0200 +++ b/debian/control Thu Aug 20 18:37:33 2009 +0200 @@ -62,7 +62,7 @@ Architecture: all XB-Python-Version: ${python:Versions} Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), python-simplejson (>= 1.3), python-elementtree -Recommends: python-docutils, python-vobject, fckeditor, python-fyzz +Recommends: python-docutils, python-vobject, fckeditor, python-fyzz, python-pysixt, fop Description: web interface library for the CubicWeb framework CubicWeb is a semantic web application framework. . diff -r 17224e90a1c4 -r e5cef8ff5857 utils.py --- a/utils.py Thu Aug 20 17:57:56 2009 +0200 +++ b/utils.py Thu Aug 20 18:37:33 2009 +0200 @@ -320,9 +320,27 @@ self.body.getvalue()) -class AcceptMixIn(object): - """Mixin class for appobjects defining the 'accepts' attribute describing - a set of supported entity type (Any by default). +def can_do_pdf_conversion(__answer=[None]): + """pdf conversion depends on + * pyxmltrf (python package) + * fop 0.9x """ - # XXX deprecated, no more necessary - + if __answer[0] is not None: + return __answer[0] + try: + import pysixt + except ImportError: + __answer[0] = False + return False + from subprocess import Popen, STDOUT + import os + try: + Popen(['/usr/bin/fop', '-q'], + stdout=open(os.devnull, 'w'), + stderr=STDOUT) + except OSError, e: + print e + __answer[0] = False + return False + __answer[0] = True + return True diff -r 17224e90a1c4 -r e5cef8ff5857 web/__init__.py --- a/web/__init__.py Thu Aug 20 17:57:56 2009 +0200 +++ b/web/__init__.py Thu Aug 20 18:37:33 2009 +0200 @@ -16,7 +16,7 @@ from logilab.common.deprecation import deprecated -from cubicweb.common.uilib import urlquote +from urllib import quote as urlquote from cubicweb.web._exceptions import * diff -r 17224e90a1c4 -r e5cef8ff5857 web/data/cubicweb.css --- a/web/data/cubicweb.css Thu Aug 20 17:57:56 2009 +0200 +++ b/web/data/cubicweb.css Thu Aug 20 18:37:33 2009 +0200 @@ -838,4 +838,13 @@ border: 1px solid #edecd2; border-color:#edecd2 #cfceb7 #cfceb7 #edecd2; background: #fffff8 url("button.png") bottom left repeat-x; -} \ No newline at end of file +} + + +/********************************/ +/* placement of alt. view icons */ +/********************************/ + +.otherView { + float: right; +} diff -r 17224e90a1c4 -r e5cef8ff5857 web/data/pdf_icon.gif Binary file web/data/pdf_icon.gif has changed diff -r 17224e90a1c4 -r e5cef8ff5857 web/views/basecomponents.py --- a/web/views/basecomponents.py Thu Aug 20 17:57:56 2009 +0200 +++ b/web/views/basecomponents.py Thu Aug 20 18:37:33 2009 +0200 @@ -2,6 +2,7 @@ * the rql input form * the logged user link +* pdf view link :organization: Logilab :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. @@ -214,6 +215,23 @@ self.w(u' | '.join(html)) self.w(u'
') +class PdfViewComponent(component.Component): + id = 'pdfview' + __select__ = yes() + + context = 'header' + property_defs = { + _('visible'): dict(type='Boolean', default=True, + help=_('display the pdf icon or not')), + } + + def call(self, vid): + self.req.add_css('cubes.confman.css') + entity = self.entity(0,0) + self.w(u'' % + (xml_escape(entity.absolute_url() + '?vid=%s&__template=pdf-main-template' % vid))) + + def registration_callback(vreg): vreg.register_all(globals().values(), __name__, (SeeAlsoVComponent,)) diff -r 17224e90a1c4 -r e5cef8ff5857 web/views/basetemplates.py --- a/web/views/basetemplates.py Thu Aug 20 17:57:56 2009 +0200 +++ b/web/views/basetemplates.py Thu Aug 20 18:37:33 2009 +0200 @@ -13,7 +13,7 @@ from cubicweb.appobject import objectify_selector from cubicweb.selectors import match_kwargs from cubicweb.view import View, MainTemplate, NOINDEX, NOFOLLOW -from cubicweb.utils import make_uid, UStringIO +from cubicweb.utils import make_uid, UStringIO, can_do_pdf_conversion # main templates ############################################################## @@ -266,6 +266,44 @@ self.w(u'\n') self.w(u'\n') +if can_do_pdf_conversion(): + from xml.etree.cElementTree import ElementTree + from subprocess import Popen as sub + from StringIO import StringIO + from tempfile import NamedTemporaryFile + from cubicweb.web.xhtml2fo import ReportTransformer + + class PdfMainTemplate(TheMainTemplate): + id = 'pdf-main-template' + + def call(self, view): + """build the standard view, then when it's all done, convert xhtml to pdf + """ + super(PdfMainTemplate, self).call(view) + pdf = self.to_pdf(self._stream) + self.req.set_content_type('application/pdf', filename='report.pdf') + self.binary = True + self.w = None + self.set_stream() + # pylint needs help + self.w(pdf) + + def to_pdf(self, stream, section='contentmain'): + # XXX see ticket/345282 + stream = stream.getvalue().replace(' ', ' ').encode('utf-8') + xmltree = ElementTree() + xmltree.parse(StringIO(stream)) + foptree = ReportTransformer(section).transform(xmltree) + foptmp = NamedTemporaryFile() + pdftmp = NamedTemporaryFile() + foptree.write(foptmp) + foptmp.flush() + fopproc = sub(['/usr/bin/fop', foptmp.name, pdftmp.name]) + fopproc.wait() + pdftmp.seek(0) + pdf = pdftmp.read() + return pdf + # page parts templates ######################################################## class HTMLHeader(View): @@ -489,4 +527,4 @@ ## vregistry registration callback ############################################ def registration_callback(vreg): - vreg.register_all(globals().values(), modname=__name__) + vreg.register_all(globals().values(), __name__) diff -r 17224e90a1c4 -r e5cef8ff5857 web/xhtml2fo.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/xhtml2fo.py Thu Aug 20 18:37:33 2009 +0200 @@ -0,0 +1,142 @@ +from cubicweb.utils import can_do_pdf_conversion +assert can_do_pdf_conversion() + +from xml.etree.ElementTree import QName, fromstring +from pysixt.standard.xhtml_xslfo.transformer import XHTML2FOTransformer +from pysixt.utils.xslfo.standard import cm +from pysixt.utils.xslfo import SimplePageMaster +from pysixt.standard.xhtml_xslfo.default_styling import default_styles +from pysixt.standard.xhtml_xslfo import XHTML_NS + + +class ReportTransformer(XHTML2FOTransformer): + """ + Class transforming an XHTML input tree into a FO document + displaying reports (one report for each
+ element in the input tree. + """ + + def __init__(self, section, + page_width=21.0, page_height=29.7, + margin_top=1.0, margin_bottom=1.0, + margin_left=1.0, margin_right=1.0, + header_footer_height=0.75, + standard_font_size=11.0, default_lang=u"fr" ): + """ + Initializes a transformer turning an XHTML input tree + containing
elements representing + main content sections into a FO output tree displaying the + reports. + + page_width: float - width of the page (in cm) + page_height: float - height of the page (in cm) + margin_top: float - top margin of the page (in cm) + margin_bottom: float - bottom margin of the page (in cm) + margin_left: float - left margin of the page (in cm) + margin_right: float - right margin of the page (in cm) + header_footer_height: float - height of the header or the footer of the + page that the page number (if any) will be + inserted in. + standard_font_size: float - standard size of the font (in pt) + default_lang: u"" - default language (used for hyphenation) + """ + self.section = section + self.page_width = page_width + self.page_height = page_height + + self.page_tmargin = margin_top + self.page_bmargin = margin_bottom + self.page_lmargin = margin_left + self.page_rmargin = margin_right + + self.hf_height = header_footer_height + + self.font_size = standard_font_size + self.lang = default_lang + + XHTML2FOTransformer.__init__(self) + + + def define_pagemasters(self): + """ + Defines the page masters for the FO output document. + """ + pm = SimplePageMaster(u"page-report") + pm.set_page_dims( self.page_width*cm, self.page_height*cm ) + pm.set_page_margins({u'top' : self.page_tmargin*cm, + u'bottom': self.page_bmargin*cm, + u'left' : self.page_lmargin*cm, + u'right' : self.page_rmargin*cm }) + pm.add_peripheral_region(u"end",self.hf_height) + dims = {} + dims[u"bottom"] = self.hf_height + 0.25 + pm.set_main_region_margins(dims) + return [pm] + + def _visit_report(self, in_elt, _out_elt, params): + """ + Specific visit function for the input
elements whose class is + "report". The _root_visit method of this class selects these input + elements and asks the process of these elements with this specific + visit function. + """ + + ps = self.create_pagesequence(u"page-report") + props = { u"force-page-count": u"no-force", + u"initial-page-number": u"1", + u"format": u"1", } + self._output_properties(ps,props) + + sc = self.create_staticcontent(ps, u"end") + sc_bl = self.create_block(sc) + attrs = { u"hyphenate": u"false", } + attrs[u"font-size"] = u"%.1fpt" %(self.font_size*0.7) + attrs[u"language"] = self.lang + attrs[u"text-align"] = u"center" + self._output_properties(sc_bl,attrs) + sc_bl.text = u"Page" + u" " # ### Should be localised! + pn = self.create_pagenumber(sc_bl) + pn.tail = u"/" + lpn = self.create_pagenumbercitation( sc_bl, + u"last-block-of-report-%d" % params[u"context_pos"] + ) + + + fl = self.create_flow(ps,u"body") + bl = self.create_block(fl) + + # Sets on the highest block element the properties of the XHTML body + # element. These properties (at the least the inheritable ones) will + # be inherited by all the future FO elements. + bodies = list(self.in_tree.getiterator(QName(XHTML_NS,u"body"))) + if len(bodies) > 0: + attrs = self._extract_properties([bodies[0]]) + else: + attrs = default_styles[u"body"].copy() + attrs[u"font-size"] = u"%.1fpt" %self.font_size + attrs[u"language"] = self.lang + self._output_properties(bl,attrs) + + # Processes the report content + self._copy_text(in_elt,bl) + self._process_nodes(in_elt.getchildren(),bl) + + # Inserts an empty block at the end of the report in order to be able + # to compute the last page number of this report. + last_bl = self.create_block(bl) + props = { u"keep-with-previous": u"always", } + props[u"id"] = u"last-block-of-report-%d" % params[u"context_pos"] + self._output_properties(last_bl,props) + + + def _root_visit(self): + """ + Visit function called when starting the process of the input tree. + """ + content = [ d for d in self.in_tree.getiterator(QName(XHTML_NS,u"div")) + if d.get(u"id") == self.section ] + # Asks the process of the report elements with a specific visit + # function + self._process_nodes(content, self.fo_root, + with_function=self._visit_report) +