--- 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 = """\
-<table class="tree">
-<tr><td class="tree_cell" rowspan="2"><div class="tree_cell">root</div></td><td class="tree_cell_1_1"> </td><td class="tree_cell_1_2"> </td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_1_1</div></td><td class="tree_cell_1_1"> </td><td class="tree_cell_1_2"> </td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_2_1</div></td><td class="tree_cell_0_1"> </td><td class="tree_cell_0_2"> </td><td rowspan="2"> </td></tr>
-<tr><td class="tree_cell_1_3"> </td><td class="tree_cell_1_4"> </td><td class="tree_cell_1_3"> </td><td class="tree_cell_1_4"> </td><td class="tree_cell_0_3"> </td><td class="tree_cell_0_4"> </td></tr>
-<tr><td rowspan="2"> </td><td class="tree_cell_2_1"> </td><td class="tree_cell_2_2"> </td><td rowspan="2"> </td><td class="tree_cell_4_1"> </td><td class="tree_cell_4_2"> </td><td class="tree_cell" rowspan="2"><div id="selected" class="tree_cell">child_2_2</div></td><td class="tree_cell_1_1"> </td><td class="tree_cell_1_2"> </td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_3_1</div></td></tr>
-<tr><td class="tree_cell_2_3"> </td><td class="tree_cell_2_4"> </td><td class="tree_cell_4_3"> </td><td class="tree_cell_4_4"> </td><td class="tree_cell_1_3"> </td><td class="tree_cell_1_4"> </td></tr>
-<tr><td rowspan="2"> </td><td class="tree_cell_2_1"> </td><td class="tree_cell_2_2"> </td><td rowspan="2"> </td><td class="tree_cell_0_1"> </td><td class="tree_cell_0_2"> </td><td rowspan="2"> </td><td class="tree_cell_3_1"> </td><td class="tree_cell_3_2"> </td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_3_2</div></td></tr>
-<tr><td class="tree_cell_2_3"> </td><td class="tree_cell_2_4"> </td><td class="tree_cell_0_3"> </td><td class="tree_cell_0_4"> </td><td class="tree_cell_3_3"> </td><td class="tree_cell_3_4"> </td></tr>
-<tr><td rowspan="2"> </td><td class="tree_cell_2_1"> </td><td class="tree_cell_2_2"> </td><td rowspan="2"> </td><td class="tree_cell_0_1"> </td><td class="tree_cell_0_2"> </td><td rowspan="2"> </td><td class="tree_cell_4_1"> </td><td class="tree_cell_4_2"> </td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_3_3</div></td></tr>
-<tr><td class="tree_cell_2_3"> </td><td class="tree_cell_2_4"> </td><td class="tree_cell_0_3"> </td><td class="tree_cell_0_4"> </td><td class="tree_cell_4_3"> </td><td class="tree_cell_4_4"> </td></tr>
-<tr><td rowspan="2"> </td><td class="tree_cell_4_1"> </td><td class="tree_cell_4_2"> </td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_1_2</div></td><td class="tree_cell_5_1"> </td><td class="tree_cell_5_2"> </td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_2_3</div></td><td class="tree_cell_0_1"> </td><td class="tree_cell_0_2"> </td><td rowspan="2"> </td></tr>
-<tr><td class="tree_cell_4_3"> </td><td class="tree_cell_4_4"> </td><td class="tree_cell_5_3"> </td><td class="tree_cell_5_4"> </td><td class="tree_cell_0_3"> </td><td class="tree_cell_0_4"> </td></tr>
-</table>\
-"""
-
-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__':
--- 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 <div> 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 <table>
- s = u'<table class="tree">\n'
- if caption:
- s += '<caption>%s</caption>\n' % caption
-
- for i, link_line in enumerate(links):
- line = matrix[i]
-
- s += '<tr>'
- for j, link_cell in enumerate(link_line):
- cell = line[j]
- if cell:
- if cell.id == selected_node:
- s += '<td class="tree_cell" rowspan="2"><div id="selected" class="tree_cell">%s</div></td>' % (render_node(cell.id))
- else:
- s += '<td class="tree_cell" rowspan="2"><div class="tree_cell">%s</div></td>' % (render_node(cell.id))
- else:
- s += '<td rowspan="2"> </td>'
- s += '<td class="tree_cell_%d_1"> </td>' % link_cell
- s += '<td class="tree_cell_%d_2"> </td>' % link_cell
-
- cell = line[-1]
- if cell:
- if cell.id == selected_node:
- s += '<td class="tree_cell" rowspan="2"><div id="selected" class="tree_cell">%s</div></td>' % (render_node(cell.id))
- else:
- s += '<td class="tree_cell" rowspan="2"><div class="tree_cell">%s</div></td>' % (render_node(cell.id))
- else:
- s += '<td rowspan="2"> </td>'
-
- s += '</tr>\n'
- if link_line:
- s += '<tr>'
- for j, link_cell in enumerate(link_line):
- s += '<td class="tree_cell_%d_3"> </td>' % link_cell
- s += '<td class="tree_cell_%d_4"> </td>' % link_cell
- s += '</tr>\n'
-
- s += '</table>'
- return s
-
-
# traceback formatting ########################################################
--- 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.
.
--- 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
--- 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 *
--- 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;
+}
Binary file web/data/pdf_icon.gif has changed
--- 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'</div>')
+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'<a href="%s" class="otherView"><img src="data/pdf_icon.gif"/></a>' %
+ (xml_escape(entity.absolute_url() + '?vid=%s&__template=pdf-main-template' % vid)))
+
+
def registration_callback(vreg):
vreg.register_all(globals().values(), __name__, (SeeAlsoVComponent,))
--- 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'</td>\n')
self.w(u'</tr></table>\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__)
--- /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 <div class="contentmain">
+ 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 <div class="contentmain"> 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 <div> 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)
+