1
2
3
4
5
6
7
8
9 """Creating simple PDF reports using reportlab
10 """
11
12 __docformat__ = 'restructuredtext'
13
14
15 import os
16 from datetime import datetime
17
18 import mvpa
19 from mvpa.base import externals, verbose
20
21 if __debug__:
22 from mvpa.base import debug
23
24 if externals.exists('reportlab', raiseException=True):
25 import reportlab as rl
26 from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image
27 from reportlab.lib.styles import getSampleStyleSheet
28 from reportlab.lib.units import inch
29
30
31
32 if externals.versions['reportlab'] >= '1112.2':
33 _fig_ext_default = 'pdf'
34 else:
35 _fig_ext_default = 'png'
36
37
38 __all__ = [ 'rl', 'Report', 'escapeXML' ]
42 s = s.replace('&', '&')
43 s = s.replace('<', '<')
44 s = s.replace('>', '>')
45 return s
46
48 """Simple PDF reports using reportlab
49
50 Named report 'report' generates 'report.pdf' and directory 'report/' with
51 images which were requested to be included in the report
52
53 You can attach report to the existing 'verbose' with
54
55 report = Report()
56 verbose.handlers += [report]
57
58 and then all verbose messages present on the screen will also be recorded
59 in the report. Use
60 report.text("string") to add arbitrary text
61 report.xml("<H1>skajdsf</H1>") to add XML snippet
62 or
63 report.figure() to add the current figure to the report.
64 report.figures() to add existing figures to the report, but they
65 might not be properly interleaved with verbose messages if there were
66 any between the creations of the figures.
67
68 Inspired by Andy Connolly
69 """
70
71 - def __init__(self, name='report', title=None, path=None,
72 author=None, style="Normal",
73 fig_ext=None, font='Helvetica',
74 pagesize=None):
75 """Initialize report
76
77 :Parameters:
78 name : string
79 Name of the report
80 title : string or None
81 Title to start the report, if None, name will be used
82 path : string
83 Top directory where named report will be stored. Has to be
84 set now to have correct path for storing image renderings.
85 Default: current directory
86 author : string or None
87 Optional author identity to be printed
88 style : string
89 Default Paragraph to be used. Must be the one of the known
90 to reportlab styles, e.g. Normal
91 fig_ext : string
92 What extension to use for figures by default. If None, a default
93 will be used. Since versions prior 2.2 of reportlab might do not
94 support pdf, 'png' is default for those, 'pdf' otherwise
95 font : string
96 Name of the font to use
97 pagesize : tuple of floats
98 Optional page size if not to be default
99 """
100
101 if pagesize is None:
102 pagesize = rl.rl_config.defaultPageSize
103 self.pagesize = pagesize
104
105 self.name = name
106 self.author = author
107 self.font = font
108 self.title = title
109 if fig_ext is None:
110 self.fig_ext = _fig_ext_default
111 else:
112 self.fig_ext = fig_ext
113
114 if path is None:
115 self._filename = name
116 else:
117 self._filename = os.path.join(path, name)
118
119 self.__nfigures = 0
120
121 try:
122 styles = getSampleStyleSheet()
123 self.style = styles.byName[style]
124 except KeyError:
125 raise ValueError, \
126 "Style %s is not know to reportlab. Known are %s" \
127 % (styles.keys())
128
129 self._story = []
130
131
132 @property
134 """Compose the beginning of the report
135 """
136 date = datetime.today().isoformat(' ')
137
138 owner = 'PyMVPA v. %s' % mvpa.__version__
139 if self.author is not None:
140 owner += ' Author: %s' % self.author
141
142 return [ Spacer(1, 0.8*inch),
143 Paragraph("Generated on " + date, self.style),
144 Paragraph(owner, self.style)] + self.__flowbreak
145
146
148 """Clear the report
149 """
150 self._story = []
151
152
153 - def xml(self, line, style=None):
154 """Adding XML string to the report
155 """
156 if __debug__ and not self in debug.handlers:
157 debug("REP", "Adding xml '%s'" % line.strip())
158 if style is None:
159 style = self.style
160 self._story.append(Paragraph(line, style=style))
161
162 - def text(self, line, **kwargs):
163 """Add a text string to the report
164 """
165 if __debug__ and not self in debug.handlers:
166 debug("REP_", "Adding text '%s'" % line.strip())
167
168
169 line = escapeXML(line)
170 self.xml(line, **kwargs)
171
172 write = text
173 """Just an alias for .text, so we could simply provide report
174 as a handler for verbose
175 """
176
177
178
244
245
260
261 @property
263 return [Spacer(1, 0.2*inch),
264 Paragraph("-" * 150, self.style),
265 Spacer(1, 0.2*inch)]
266
268 """Just a marker for the break of the flow
269 """
270 if __debug__ and not self in debug.handlers:
271 debug("REP", "Adding flowbreak")
272
273 self._story.append(self.__flowbreak)
274
275
276
277
278
279
280
281
282
283
284 - def save(self, add_preamble=True):
285 """Saves PDF
286
287 :Parameters:
288 add_preamble : bool
289 Either to add preamble containing title/date/author information
290 """
291
292 if self.title is None:
293 title = self.name + " report"
294 else:
295 title = self.title
296
297 pageinfo = self.name + " data"
298
299 def myFirstPage(canvas, doc):
300 canvas.saveState()
301 canvas.setFont(self.font, 16)
302 canvas.drawCentredString(self.pagesize[0]/2.0,
303 self.pagesize[1]-108, title)
304 canvas.setFont(self.font, 9)
305 canvas.drawString(inch, 0.75 * inch,
306 "First Page / %s" % pageinfo)
307 canvas.restoreState()
308
309 def myLaterPages(canvas, doc):
310 canvas.saveState()
311 canvas.setFont(self.font, 9)
312 canvas.drawString(inch, 0.75 * inch,
313 "Page %d %s" % (doc.page, pageinfo))
314 canvas.restoreState()
315
316 filename = self._filename + ".pdf"
317 doc = SimpleDocTemplate(filename)
318
319 story = self._story
320 if add_preamble:
321 story = self.__preamble + story
322
323 if __debug__ and not self in debug.handlers:
324 debug("REP", "Saving the report into %s" % filename)
325
326 doc.build(story,
327 onFirstPage=myFirstPage,
328 onLaterPages=myLaterPages)
329