Attachment 'CreatePdfDocument2_0_4.py'
Download 1 # -*- coding: iso-8859-1 -*-
2 """
3 MoinMoin - Generate PDF document using HTMLDOC
4
5 This action script generate PDF documents from a Wiki site using
6 the HTMLDOC (http://www.htmldoc.org) software packages which has
7 to be preinstalled first.
8
9 To use this feature install this script in your's MoinMoin action
10 script plugin directory.
11
12 Thanks goes to Pascal Bauermeister who initiated the implementaion.
13 Lot of things changes since then but the idear using HTMLDOC is the
14 main concept of this implementation.
15
16 @copyright: (C) 2006 Pascal Bauermeister
17 @copyright: (C) 2006 Raphael Bossek <raphael.bossek@solutions4linux.de>
18 @license: GNU GPL, see COPYING for details
19
20 2006-09-12 RaphaelBossek
21 * Release v2.0.4
22 * Fixed RedirectOUtputRequest class where function was redifined
23 to boolean value.
24
25 2006-09-06 RaphaelBossek
26 * Release v2.0.3
27 * Fixed FastCGI support by removing stdout redirect output code. The
28 same functionality will be done by the RedirectOutputRequest class.
29 * Renamed to CreatePdfDocument (for better translation possibilities).
30 * Fixed encoding of title page.
31 * Added charset set option.
32 * Fixed waiting for HTMLDOC.
33
34 2006-09-02 RaphaelBossek
35 * Release v2.0.2
36 * Added createpdfdocument_validoptions and createpdfdocument_defaultvalues
37 configuration parameter support. You are not able to preset available
38 options and which defaults are used in your configuration file.
39
40 2006-08-30 RaphaelBossek
41 * Release v2.0.1
42 * Fixed issue with page revision number forwarding (traceback).
43
44 2006-08-30 RaphaelBossek
45 * Release v2.0.0
46 * Feature enchanced and bug fixed version.
47
48 2006-05-26 PascalBauermeister
49 * Release v1.0.2
50 * Relative image URLs turned absolute was bogus. It is less bogus now.
51
52 2006-05-26 PascalBauermeister
53 * Release v1.0.1
54 * Set env var HTMLDOC_NOCGI to solve CGI issue
55
56 2006-05-24 PascalBauermeister
57 * Initial release v1.0.0
58 """
59
60 import os, stat
61 import re, copy, sys, StringIO, tempfile, traceback
62 import shutil, StringIO
63 from MoinMoin import config, util, wikiutil, packages, error
64 from MoinMoin.Page import Page
65 from MoinMoin.util import MoinMoinNoFooter, filesys
66 from MoinMoin.request import RequestModPy, RequestBase
67 from MoinMoin.parser import wiki
68 from MoinMoin.widget.dialog import Dialog
69
70 class ActionError(Exception):
71 pass
72
73 class RedirectOutputRequest(RequestBase):
74 """Redirect output to string without HTTP headers."""
75 def __init__ (self, req):
76 self.path_info = req.path_info.encode(config.charset)
77 self.request_uri = req.request_uri
78 self.url = req.url
79 self.http_user_agent = req.http_user_agent
80 self.query_string = req.query_string
81 #self.request_method = req.request_method
82 self.request_method = None
83 self.saved_cookie = req.saved_cookie
84 self.remote_addr = req.remote_addr
85
86 # RequestModPy
87 if getattr (req, u'script_name', None):
88 self.script_name = req.script_name
89
90 self.req = req
91 RequestBase.__init__(self)
92
93 def run(self):
94 self.output_string = u''
95 self.error_string = u''
96 self.sent_headers = False
97 self.failed = 0
98 RequestBase.run(self)
99 return (self.fixhtmlstr(self.output_string), self.error_string)
100
101 def fixhtmlstr (self, str):
102 """Convert utf-8 encoded multi-byte sequences into &#XXXX; format."""
103 htmlstr = u''
104 for c in str:
105 if ord(c) >= 128:
106 htmlstr = htmlstr + '&#%d;' % ord(c)
107 else:
108 htmlstr = htmlstr + c
109 return htmlstr
110
111 def http_headers (self, more_headers=[]):
112 self.sent_headers = True
113
114 def fail (self, err):
115 RequestBase.fail (self, err)
116 if not self.error_string:
117 self.error_string = str(err)
118
119 def write (self, *data):
120 if self.sent_headers:
121 if self.failed:
122 self.error_string += data[0]
123 else:
124 self.output_string += data[0]
125
126 def flush(self):
127 pass
128
129 class CreatePdfDocument:
130 """Implementation of the PDF document generator."""
131
132 version = u'2.0.4'
133
134 def __init__(self):
135 self.action_name = self.__class__.__name__
136 self.pagename = None
137 self.request = None
138 self._ = None
139 self.debug = False
140 self.msg = None
141 self.errormsgsent = False
142 self.default_values = {
143 'style': u'webpage',
144 'format': u'pdf13',
145 'linkstyle': u'underline',
146 'headerleft': u't',
147 'headermiddle': u'.',
148 'headerright': u'D',
149 'footerleft': u'.',
150 'footermiddle': u'/',
151 'footerright': u'.',
152 'tocheaderleft': u'.',
153 'tocheadermiddle': u't',
154 'tocheaderright': u'.',
155 'tocfooterleft': u'.',
156 'tocfootermiddle': u'.',
157 'tocfooterright': u'i',
158 'linkcolor': u'0000E0',
159 'size': u'legal',
160 'user-password': u'',
161 'owner-password': u'',
162 'toclevels': u'3',
163 'grayscale': u'unchecked',
164 'title': u'checked',
165 'duplex': u'unchecked',
166 'landscape': u'unchecked',
167 'usersize': u'',
168 'margintop': u'0.50in',
169 'marginbottom': u'0.50in',
170 'marginleft': u'1.00in',
171 'marginright': u'0.50in',
172 'no-toc': u'checked',
173 'no-links': u'checked',
174 'firstpage': u'p1',
175 'jpeg': u'0',
176 'compression': u'0',
177 'pagemode': u'outline',
178 'pagelayout': u'single',
179 'firstpage': u'c1',
180 'numbered': u'checked',
181 'encryption': u'unchecked',
182 'permissioncopy': u'checked',
183 'permissionprint': u'checked',
184 'permissionannotate': u'checked',
185 'permissionmodify': u'checked',
186 'charset': u'iso-8859-1',
187 'debug': u'',
188 'rev': 0,
189 }
190 # We have to know which values are checkboxes within the form. If a key does
191 # not exists wihtin the form the corresponding checkbox is not checked.
192 self.form_checkbox = []
193 for key, value in self.default_values.items():
194 if value in [u'checked', u'unchecked']:
195 self.form_checkbox += [key]
196 self.contenttype = u'application/pdf'
197
198 def error_msg (self, msg):
199 """Display error message."""
200 if not self.errormsgsent:
201 Page (self.request, self.pagename).send_page (self.request, msg=msg)
202 self.errormsgsent = True
203
204 def set_page_values(self):
205 """Scan raw page for additional information relating PDF generation."""
206 #pdflines = False
207 for line in self.request.page.get_raw_body().split(u'\n'):
208 if line[:6] == u'##pdf ':
209 cols = line[6:].split()
210 # Only accept known values/settings.
211 if len(cols) > 1 and cols[0] in self.default_values:
212 self.values[cols[0]] = u' '.join(cols[1:])
213 #pdflines = True
214 continue
215 # Stop parsing with first non-pdf line (if detected at least one).
216 #elif pdflines and not line:
217 # break
218
219 def set_page_default_values(self):
220 # We are not able to recognise if this string is part of a verbatim area.
221 matchtoclvl = re.compile(r'^\[\[TableOfContents\(\s*(\d+)\s*\)\]\]')
222 matchtoc = re.compile(r'^\[\[TableOfContents\(*\)*\]\]')
223 toc = False
224 for line in self.request.page.get_raw_body().split(u'\n'):
225 if line[:10] == u'#language ' and not u'language' in self.values:
226 lang = self.make_isolang(line[10:])
227 if lang:
228 self.default_values[u'language'] = lang
229 elif not u'toclevels' in self.values and not toc:
230 result = matchtoclvl.match(line)
231 if result:
232 toclevels = int(result.group(1).strip())
233 if toclevels > 4:
234 toclevels = 4
235 self.default_values[u'toclevels'] = str(toclevels)
236 toc = True
237 elif matchtoc.match(line):
238 toc = True
239 # We assume if table-of-contents is used we intent to generate a book.
240 if toc:
241 self.default_values[u'style'] = u'book'
242 else:
243 # Do not generate a table of contents page.
244 self.default_values[u'no-toc'] = u'unchecked'
245
246 def escape (self, str):
247 return str.replace (u'&', u'&').replace (u'<', u'<').replace (u'>', u'>')
248
249 def _select (self, name, description=None):
250 """Helper function to create a selection control."""
251 str = u'<select name="%s" size="1">' % (name,)
252 if not description:
253 description = self.valid_options[name]
254 keys = description.keys()
255 keys.sort()
256 for value in keys:
257 if value == self.values[name]:
258 selected = u'selected'
259 else:
260 selected = u''
261 str += u'<option value="%s" %s>%s</option>' % (value, selected, description[value],)
262 str += u'</select>'
263 return str
264
265 def _chooseformat (self, name):
266 """Helper function to create left/middle/right selection controls."""
267 str = u""" <tr>
268 <td class="label"><label>%s :</label></td>
269 <td><table>
270 <tr>
271 <td>%s :</td>
272 <td>%s</td>
273 </tr>
274 <tr>
275 <td>%s :</td>
276 <td>%s</td>
277 </tr>
278 <tr>
279 <td>%s :</td>
280 <td>%s</td>
281 </tr>
282 </table>
283 </td>
284 <td>%s</td>
285 </tr>""" % (self.fields[u'label_' + name],
286 self.fields[u'label_left'], self._select(name + u'left', self.valid_options[u'tocformats']),
287 self.fields[u'label_middle'], self._select(name + u'middle', self.valid_options[u'tocformats']),
288 self.fields[u'label_right'], self._select(name + u'right', self.valid_options[u'tocformats']),
289 self.fields[u'help_' + name],)
290 return str
291
292 def makeform (self, errormsg=u''):
293 self.fields = {
294 'error': errormsg,
295 'pagename': wikiutil.escape(self.pagename),
296 'action': self.action_name,
297 'seperator': u'<tr><td colspan="3"><hr/></td></tr>',
298 'space': u'<tr><td colspan="3"> </td></tr>',
299
300 'label_input': self._(u'Input'),
301 'label_output': self._(u'Output'),
302 'label_page': self._(u'Page'),
303 'label_tableofcontents': self._(u'Contents'),
304 'label_pdf': self._(u'PDF'),
305 'label_security': self._(u'Security'),
306
307 'label_choose_style': self._(u'Choose style'),
308 'help_choose_style': self._(u'book: Create a structured PDF document with headings, chapters, etc.') + u'<br/>' + \
309 self._(u'webpage: Specifies that the HTML sources are unstructured (plain web pages.) A page break is inserted between each file or URL in the output.') + u'<br/>' + \
310 self._(u'continuous: Specifies that the HTML sources are unstructured (plain web pages.) No page breaks are inserted between each file or URL in the output.'),
311
312 'label_format': self._(u'Output format'),
313 'help_format': self._(u'Specifies the output format.'),
314
315 'label_outputoptions': self._(u'Output options'),
316 'label_grayscale': self._(u'Grayscale document'),
317 'label_title': self._(u'Title page'),
318 'label_jpeg': self._(u'JPEG big images'),
319 'label_compression': self._(u'Compression'),
320
321 'label_no-toc': self._(u'Generate a table of contents'),
322 'help_no-toc': self._(u''),
323
324 'label_toclevels': self._(u'Limit the number of levels in the table-of-contents'),
325 'help_toclevels': self._(u'Sets the number of levels in the table-of-contents.') + u' ' + self._(u'Empty for unlimited levels.'),
326
327 'label_numbered': self._(u'Numbered headings'),
328 'help_numbered': self._(u'Check to number all of the headings in the document.'),
329
330 'label_toctitle': self._(u'Table-of-contents title'),
331 'help_toctitle': self._(u'Sets the title for the table-of-contents.') + u' ' + self._(u'Empty for default title.'),
332
333 'label_left': self._(u'Left'),
334 'label_middle': self._(u'Middle'),
335 'label_right': self._(u'Right'),
336
337 'label_tocheader': self._(u'Header of table-of-contantes page'),
338 'help_tocheader': self._(u'Sets the page header to use on table-of-contents pages.'),
339
340 'label_tocfooter': self._(u'Footer of table-of-contantes page'),
341 'help_tocfooter': self._(u'Sets the page footer to use on table-of-contents pages.'),
342
343 'label_header': self._(u'Page header'),
344 'help_header': self._(u'Sets the page header to use on body pages.'),
345
346 'label_footer': self._(u'Page footer'),
347 'help_footer': self._(u'Sets the page footer to use on body pages.'),
348
349 'label_no-links': self._(u'Create HTTP links'),
350 'help_no-links': self._(u'Enables generation of links in PDF files.'),
351
352 'label_linkstyle': self._(u'Style of HTTP links'),
353 'help_linkstyle': self._(u''),
354
355 'label_linkcolor': self._(u'HTTP links color'),
356 'help_linkcolor': self._(u'Sets the color of links.'),
357
358 'label_duplex': self._(u'2-Sided'),
359 'help_duplex': self._(u'Specifies that the output should be formatted for double-sided printing.'),
360
361 'label_landscape': self._(u'Landscape'),
362
363 'label_choose_size': self._(u'Choose page size'),
364 'help_choose_size': self._(u'Choose one of the predefined standard sizes or select user defined.'),
365
366 'label_usersize': self._(u'User defined page size'),
367 'help_usersize': self._(u'Specifies the page size using a standard name or in points (no suffix or ##x##pt), inches (##x##in), centimeters (##x##cm), or millimeters (##x##mm).'),
368
369 'label_margin': self._(u'User defined margin'),
370 'label_margintop': self._(u'Top'),
371 'label_marginbottom': self._(u'Bottom'),
372 'label_marginleft': self._(u'Left'),
373 'label_marginright': self._(u'Right'),
374 'help_margin': self._(u'Specifies the margin size using points (no suffix or ##x##pt), inches (##x##in), centimeters (##x##cm), or millimeters (##x##mm).') + u' ' + self._(u'Keep empty for default value.'),
375
376 'label_pagemode': self._(u'Page mode'),
377 'help_pagemode': self._(u'Controls the initial viewing mode for the document.') + u'<br/>' + self._(u'Document: Displays only the docuemnt pages.') + u'<br/>' + self._(u'Outline: Display the table-of-contents outline as well as the document pages.') + u'<br/>' + self._(u'Full-screen: Displays pages on the whole screen; this mode is used primarily for presentations.'),
378
379 'label_pagelayout': self._(u'Page layout'),
380 'help_pagelayout': self._(u'Controls the initial layout of document pages on the screen.') + u'<br/>' + self._(u'Single: Displays a single page at a time.') + u'<br/>' + self._(u'One column: Displays a single column of pages at a time.') + u'<br/>' + self._(u'Two column left/right: Display two columns of pages at a time; the first page is displayed in the left or right column as selected.'),
381
382 'label_firstpage': self._(u'First page'),
383 'help_firstpage': self._(u'Choose the initial page that will be shown.'),
384
385 'label_encryption': self._(u'Encryption'),
386 'help_encryptin': self._(u'Enables encryption and security features for PDF output.'),
387
388 'label_permissions': self._(u'Permissions'),
389 'help_permissions': self._(u'Specifies the document permissions.'),
390
391 'label_permissionannotate': self._(u'Annotate'),
392 'label_permissionprint': self._(u'Print'),
393 'label_permissionmodify': self._(u'Modify'),
394 'label_permissioncopy': self._(u'Copy'),
395
396 'label_owner-password': self._(u'Owner password'),
397 'help_owner-password': self._(u'Specifies the owner password to control who can change document permissions etc.') + u' ' + self._(u'If this field is left blank, a random 32-character password is generated so that no one can change the document.'),
398
399 'label_user-password': self._(u'User password'),
400 'help_user-password': self._(u'Specifies the user password to restrict viewing permissions on this PDF document.') + u' ' + self._(u'Empty for no encryption.'),
401
402 'label_expert': self._(u'Expert'),
403 'label_language': self._(u'Language translation'),
404 'help_language': self._(u'Specify language to use for date and time format.'),
405
406 'label_charset': self._(u'Charset set'),
407 'help_charset': self._(u'Change the encoding of the text in document.'),
408
409 'button_generate': self._(u'Generate PDF'),
410 'button_remember': self._(u'Remember form'),
411 'button_cancel': self._(u'Cancel'),
412 'button_reset': self._(u'Reset'),
413 }
414 self.fields.update(self.values)
415
416 # Go through all format strings.
417 for name in [u'tocheader', u'tocfooter', u'header', u'footer']:
418 self.fields[u'choose_' + name] = self._chooseformat(name)
419
420 self.fields[u'select_style'] = self._select (u'style')
421 self.fields[u'select_format'] = self._select (u'format')
422 self.fields[u'select_linkstyle'] = self._select (u'linkstyle')
423 self.fields[u'select_size'] = self._select (u'size')
424 self.fields[u'select_jpeg'] = self._select (u'jpeg')
425 self.fields[u'select_compression'] = self._select (u'compression')
426 self.fields[u'select_toclevels'] = self._select (u'toclevels')
427 self.fields[u'select_pagemode'] = self._select (u'pagemode')
428 self.fields[u'select_pagelayout'] = self._select (u'pagelayout')
429 self.fields[u'select_firstpage'] = self._select (u'firstpage')
430 self.fields[u'select_charset'] = self._select (u'charset')
431
432 self.fields[u'pdfpydownloadlink'] = u'http://einstein.speech-design.de/~br/pdf.py'
433 self.fields[u'pdfpyversion'] = self.version
434
435 form = """<p class="error">%(error)s</p>
436 <form method="post" action="">
437 <input type="hidden" name="action" value="%(action)s"/>
438 <table>
439 <tr><td colspan="3">%(label_input)s</td></tr>
440 <tr>
441 <td class="label"><label>%(label_choose_style)s :</label></td>
442 <td class="content">%(select_style)s</td>
443 <td>%(help_choose_style)s</td>
444 </tr>
445 %(seperator)s
446 <tr><td colspan="3">%(label_output)s</td></tr>
447 <tr>
448 <td class="label"><label>%(label_format)s :</label></td>
449 <td class="content">%(select_format)s</td>
450 <td>%(help_format)s</td>
451 </tr>
452 <tr>
453 <td class="label"><label>%(label_outputoptions)s :</label></td>
454 <td colspan="2"><input type="checkbox" name="grayscale" value="checked" %(grayscale)s />%(label_grayscale)s
455 <input type="checkbox" name="title" value="checked" %(title)s />%(label_title)s<br />
456 %(label_compression)s : %(select_compression)s
457 %(label_jpeg)s : %(select_jpeg)s</td>
458 </tr>
459 %(seperator)s
460 <tr><td colspan="3">%(label_page)s</td></tr>
461 <tr>
462 <td class="label"><label>%(label_choose_size)s :</label></td>
463 <td>%(select_size)s <br /><nobr>%(label_usersize)s : <input type="text" size="15" name="usersize" value="%(usersize)s" /></nobr></td>
464 <td>%(help_choose_size)s<br />%(help_usersize)s</td>
465 </tr>
466 <tr>
467 <td> </td>
468 <td colspan="2"><input type="checkbox" name="duplex" value="checked" %(duplex)s /> %(label_duplex)s
469 <input type="checkbox" name="landscape" value="checked" %(landscape)s /> %(label_landscape)s</td>
470 </tr>
471 <tr>
472 <td class="label"><label>%(label_margin)s :</label></td>
473 <td><table><tr><td> </td><td><nobr><label>%(label_margintop)s :</label> <input type="text" name="margintop" value="%(margintop)s" size="7" /></nobr></td><td> </td></tr>
474 <tr><td><nobr><label>%(label_marginleft)s :</label> <input type="text" name="marginleft" value="%(marginleft)s" size="7" /></nobr></td><td> </td><td><nobr><label>%(label_marginright)s :</label> <input type="text" name="marginright" value="%(marginright)s" size="7" /></nobr></td></tr>
475 <tr><td> </td><td><nobr><label>%(label_marginbottom)s :</label> <input type="text" name="marginbottom" value="%(marginbottom)s" size="7" /></nobr></td><td> </td></tr></table>
476 <td>%(help_margin)s</td>
477 </tr>
478 %(choose_header)s
479 %(choose_footer)s
480 %(seperator)s
481 <tr><td colspan="3">%(label_tableofcontents)s</td></tr>
482 <tr>
483 <td class="label"><label>%(label_no-toc)s :</label></td>
484 <td class="checkbox"><input type="checkbox" name="no-toc" value="checked" %(no-toc)s /></td>
485 <td>%(help_no-toc)s</td>
486 </tr>
487 <tr>
488 <td class="label"><label>%(label_toclevels)s :</label></td>
489 <td class="content">%(select_toclevels)s</td>
490 <td>%(help_toclevels)s</td>
491 </tr>
492 <tr>
493 <td> </td>
494 <td><input type="checkbox" name="numbered" value="checked" %(numbered)s /> %(label_numbered)s</td>
495 <td>%(help_numbered)s</td>
496 </tr>
497 <tr>
498 <td class="label"><label>%(label_toctitle)s :</label></td>
499 <td class="content"><input type="text" size="30" name="toctitle" value="%(toctitle)s" /></td>
500 <td>%(help_toctitle)s</td>
501 </tr>
502 %(choose_tocheader)s
503 %(choose_tocfooter)s
504 %(seperator)s
505 <tr><td colspan="3">%(label_pdf)s</td></tr>
506 <tr>
507 <td class="label"><label>%(label_pagemode)s :</label></td>
508 <td class="content">%(select_pagemode)s</td>
509 <td>%(help_pagemode)s</td>
510 </tr>
511 <tr>
512 <td class="label"><label>%(label_pagelayout)s :</label></td>
513 <td class="content">%(select_pagelayout)s</td>
514 <td>%(help_pagelayout)s</td>
515 </tr>
516 <tr>
517 <td class="label"><label>%(label_firstpage)s :</label></td>
518 <td class="content">%(select_firstpage)s</td>
519 <td>%(help_firstpage)s</td>
520 </tr>
521 <tr>
522 <td class="label"><label>%(label_no-links)s :</label></td>
523 <td><input type="checkbox" name="no-links" value="checked" %(no-links)s /></td>
524 <td>%(help_no-links)s</td>
525 </tr>
526 <tr>
527 <td class="label"><label>%(label_linkstyle)s :</label></td>
528 <td class="content">%(select_linkstyle)s</td>
529 <td>%(help_linkstyle)s</td>
530 </tr>
531 <tr>
532 <td class="label"><label>%(label_linkcolor)s :</label></td>
533 <td class="content"><input type="text" size="6" name="linkcolor" value="%(linkcolor)s" /></td>
534 <td>%(help_linkcolor)s</td>
535 </tr>
536 %(seperator)s
537 <tr><td colspan="3">%(label_security)s</td></tr>
538 <tr>
539 <td class="label"><label>%(label_encryption)s :</label></td>
540 <td><input type="checkbox" name="encryption" value="checked" %(encryption)s /></td>
541 <td>%(help_numbered)s</td>
542 </tr>
543 <tr>
544 <td class="label"><label>%(label_permissions)s :</label></td>
545 <td><nobr><input type="checkbox" name="permissionprint" value="checked" %(permissionprint)s /> %(label_permissionprint)s</nobr>
546 <nobr><input type="checkbox" name="permissionmodify" value="checked" %(permissionmodify)s /> %(label_permissionmodify)s</nobr><br />
547 <nobr><input type="checkbox" name="permissioncopy" value="checked" %(permissioncopy)s /> %(label_permissioncopy)s</nobr>
548 <nobr><input type="checkbox" name="permissionannotate" value="checked" %(permissionannotate)s /> %(label_permissionannotate)s</nobr></td>
549 <td>%(help_permissions)s</td>
550 </tr>
551 <tr>
552 <td class="label"><label>%(label_user-password)s :</label></td>
553 <td class="content"><input type="password" size="30" name="user-password" value="%(user-password)s" /></td>
554 <td>%(help_user-password)s</td>
555 </tr>
556 <tr>
557 <td class="label"><label>%(label_owner-password)s :</label></td>
558 <td class="content"><input type="password" size="30" name="owner-password" value="%(owner-password)s" /></td>
559 <td>%(help_owner-password)s</td>
560 </tr>
561 %(seperator)s
562 <tr><td colspan="3">%(label_expert)s</td></tr>
563 <tr>
564 <td class="label"><label>%(label_language)s :</label></td>
565 <td class="content"><input type="text" size="6" name="language" value="%(language)s" /></td>
566 <td>%(help_language)s</td>
567 </tr>
568 <tr>
569 <td class="label"><label>%(label_charset)s :</label></td>
570 <td class="content">%(select_charset)s</td>
571 <td>%(help_charset)s</td>
572 </tr>
573 %(space)s
574 <tr>
575 <td><input type="hidden" name="debug" value="%(debug)s" /><input type="hidden" name="rev" value="%(rev)s" /></td>
576 <td class="buttons" colspan="2">
577 <input type="submit" name="generate_from_form" value="%(button_generate)s" />
578 <input type="submit" name="remember" value="%(button_remember)s" />
579 <input type="submit" name="cancel" value="%(button_cancel)s" />
580 </td>
581 </tr>
582 </table>
583 </form>""" % self.fields
584 return Dialog (self.request, content=form)
585
586 def run (self, pagename, request):
587 """ Main dispatcher for the action."""
588 self.pagename = pagename
589 self.request = request
590 self._ = self.request.getText
591 self.form = self.request.form
592 self.cfg = self.request.cfg
593
594 # Canceled by user.
595 if self.form.has_key(u'cancel'):
596 return self.request.page.send_page(self.request)
597
598 # Determine calling parameters.
599 if self.form.get(u'debug', [u'0']) == [u'1']:
600 self.debug = True
601
602 # This dict contains all possible values.
603 self.valid_options = {}
604
605 self.valid_options[u'tocformats'] = {
606 u'/': self._(u'1/N,2/N Arabic page numbers'),
607 u':': self._(u'1/C,2/C Arabic chapter page numbers'),
608 u'1': self._(u'1,2,3,...'),
609 u'a': self._(u'a,b,c,...'),
610 u'A': self._(u'A,B,C,...'),
611 u'c': self._(u'Chapter title'),
612 u'C': self._(u'Chapter page number'),
613 u'd': self._(u'Date'),
614 u'D': self._(u'Date + Time'),
615 u'h': self._(u'Heading'),
616 u'i': self._(u'i,ii,iii,iv,...'),
617 u'I': self._(u'I,II,III,IV,...'),
618 u't': self._(u'Title'),
619 u'T': self._(u'Time'),
620 u'.': self._(u'Blank'),
621 # TODO: Not supported yet; u'l': self._(u'Logo image'),
622 }
623 self.valid_options[u'style'] = {
624 u'webpage': self._(u'webpage'),
625 u'book': self._(u'book'),
626 u'continuous': self._(u'continuous'),
627 }
628 self.valid_options[u'size'] = {
629 u'legal': self._(u'Legal (8.5x14in)'),
630 u'a4': self._(u'A4 (210x297mm)'),
631 u'letter': self._(u'Letter (8.5x11in)'),
632 u'universal': self._(u'Universal (8.27x11in)'),
633 u'': self._(u'User defined'),
634 }
635 self.valid_options[u'format'] = {
636 u'pdf11': self._(u'PDF 1.1 (Acrobat 2.0)'),
637 u'pdf12': self._(u'PDF 1.2 (Acrobat 3.0)'),
638 u'pdf13': self._(u'PDF 1.3 (Acrobat 4.0)'),
639 u'pdf14': self._(u'PDF 1.4 (Acrobat 5.0)'),
640 # TODO: Not supported yet:
641 #u'ps1': self._(u'PostScript Level 1'),
642 #u'ps2': self._(u'PostScript Level 2'),
643 #u'ps3': self._(u'PostScript Level 3'),
644 }
645 self.valid_options[u'linkstyle'] = {
646 u'underline': self._(u'Underline'),
647 u'plain': self._(u'Plain'),
648 }
649 self.valid_options[u'firstpage'] = {
650 u'c1': self._(u'1st chapter'),
651 u'p1': self._(u'1st page'),
652 u'toc': self._(u'Contents'),
653 }
654 self.valid_options[u'jpeg'] = {
655 u'0': self._(u'None'),
656 u'50': self._(u' 50% (Good)'),
657 u'55': u'55%', u' 60': u' 60%', u' 65': ' 65%', u' 70': ' 70%', u' 75': ' 75%',
658 u'80': ' 80%', u' 85': ' 85%', u' 90': ' 90%', u' 95': ' 95%',
659 u'100': self._(u'100% (Best)'),
660 }
661 self.valid_options[u'compression'] = {
662 u'0': self._(u'None'),
663 u'1': self._(u'1 (Fast)'),
664 u'2': u'2', u'3': u'3', u'4': u'4', u'5': u'5', u'6': u'6', u'7': u'7', u'8': u'8',
665 u'9': self._(u'9 (Best)'),
666 }
667 self.valid_options[u'toclevels'] = {
668 u'0': self._(u'None'),
669 u'1': u'1', u'2': '2', u'3': '3', u'4': '4'
670 }
671 self.valid_options[u'pagemode'] = {
672 u'outline': self._(u'Outline'),
673 u'document': self._(u'Document'),
674 u'fullscreen': self._(u'Full-screen'),
675 }
676 self.valid_options[u'pagelayout'] = {
677 u'single': self._(u'Single'),
678 u'one': self._(u'One column'),
679 u'twoleft': self._(u'Two column left'),
680 u'tworight': self._(u'Two column right'),
681 }
682 self.valid_options[u'charset'] = {
683 u'iso-8859-1': self._(u'ISO 8859-1'),
684 u'iso-8859-2': self._(u'ISO 8859-2'),
685 u'iso-8859-3': self._(u'ISO 8859-3'),
686 u'iso-8859-4': self._(u'ISO 8859-4'),
687 u'iso-8859-5': self._(u'ISO 8859-5'),
688 u'iso-8859-6': self._(u'ISO 8859-6'),
689 u'iso-8859-7': self._(u'ISO 8859-7'),
690 u'iso-8859-8': self._(u'ISO 8859-8'),
691 u'iso-8859-9': self._(u'ISO 8859-9'),
692 u'iso-8859-14': self._(u'ISO 8859-14'),
693 u'iso-8859-15': self._(u'ISO 8859-15'),
694 u'cp-874': self._(u'cp-847'),
695 u'cp-1250': self._(u'cp-1250'),
696 u'cp-1251': self._(u'cp-1251'),
697 u'cp-1252': self._(u'cp-1252'),
698 u'cp-1253': self._(u'cp-1253'),
699 u'cp-1254': self._(u'cp-1254'),
700 u'cp-1255': self._(u'cp-1255'),
701 u'cp-1256': self._(u'cp-1256'),
702 u'cp-1257': self._(u'cp-1257'),
703 u'cp-1258': self._(u'cp-1258'),
704 u'koi-8r': self._(u'koi-8r'),
705 }
706
707 # Set translated name of table of contents as default.
708 self.default_values[u'toctitle'] = self._(u'Contents')
709
710 # Make sure we create date and time strings in right format.
711 if self.request.page.language:
712 self.default_values[u'language'] = self.request.page.language
713 else:
714 self.default_values[u'language'] = self.make_isolang(self.cfg.__dict__.get (u'default_language', u'en'))
715
716 self.values = {}
717
718 # If the configuration variable 'createpdfdocument_validoptions' exists we update our
719 # self.valid_options dict with these values.
720 if getattr (self.request.cfg, u'createpdfdocument_validoptions', None):
721 self.valid_options.update (self.request.cfg.createpdfdocument_validoptions)
722
723 # If the configuration variable 'createpdfdocument_defaultvalues' exists we update our
724 # self.default_values dict with these values.
725 if getattr (self.request.cfg, u'createpdfdocument_defaultvalues', None):
726 for key, value in self.request.cfg.createpdfdocument_defaultvalues.items():
727 self.default_values[key] = value
728
729 # Scan page to extract default values.
730 self.set_page_default_values()
731
732 # Create a PDF document direct without any user iteraction from default and page settings.
733 if self.form.has_key(u'generate'):
734 self.set_page_values()
735 self.update_values(useform=False)
736 return self.do_generate()
737
738 # Create a PDF document from form settings.
739 if self.form.has_key(u'generate_from_form'):
740 self.update_values()
741 return self.do_generate()
742
743 if self.form.has_key(u'remember'):
744 self.update_values()
745 return self.do_remember()
746
747 self.set_page_values()
748 self.update_values(useform=False)
749 return self.request.page.send_page (self.request, msg=self.makeform())
750
751 def update_values(self, useform=True):
752 """Preset values with they form values or defaults."""
753 for key, default in self.default_values.items():
754 # Modify value only if not already set.
755 if not key in self.values:
756 # If the form does not contain the value (e.g. for checkboxes) set the
757 # default value (e.g. for checkboxes unset by default).
758 if not key in self.form:
759 # Special processing for checkboxes in forms. If the key does not exists
760 # within the form it is not checked.
761 if key in self.form_checkbox and useform:
762 self.values[key] = u'unchecked'
763 else:
764 self.values[key] = default
765 else:
766 self.values[key] = self.form[key][0]
767 # Check if revision is an integer value.
768 try:
769 self.values[u'rev'] = int(self.values.get(u'rev', self.request.page.rev))
770 except:
771 self.values[u'rev'] = self.request.page.rev
772 # Check if page revision exists.
773 (pagefname, realrev, exists) = self.request.page.get_rev (rev=self.values[u'rev'])
774 if exists:
775 self.values[u'rev'] = realrev
776 else:
777 # Determine latest revision number.
778 (pagefname, self.values[u'rev'], exists) = self.request.page.get_rev()
779
780 def do_generate(self):
781 """Create PDF document."""
782 # Generate the HTML page using MoinMoin wiki engine.
783 html = self.get_html()
784 if html:
785 if self.debug:
786 self.request.http_headers()
787 self.request.write(html)
788 else:
789 pdfdata = self.html2pdf(html)
790 if pdfdata:
791 # Send as application/pdf the generated file by HTMLDOC
792 self.send_pdf(pdfdata)
793 raise MoinMoinNoFooter
794
795 def do_remember(self):
796 """Create a message containing information about how to save the form values for future reuse."""
797 save = u''
798 for key, value in self.values.items():
799 if key in [u'user-password', u'owner-password', u'rev', u'debug']:
800 continue
801 if key in self.default_values and value == self.default_values[key]:
802 continue
803 save += u'##pdf %s %s\n' % (key, value,)
804 if save:
805 msg = self._(u'Add follwing lines at the beginning of your page:') + u'<br/><pre>' + save + u'</pre>'
806 else:
807 msg = self._(u'All values correspond to they default. Nothing have to be saved.')
808 return self.request.page.send_page (self.request, msg)
809
810 def send_pdf (self, data):
811 filename = self.pagename.replace (u'/', u'-') + u'-v' + str(self.values[u'rev']) + u'.pdf'
812
813 # Send HTTP header.
814 self.request.http_headers([
815 'Content-Type: %s' % self.contenttype,
816 'Content-Length: %d' % len(data),
817 # TODO: fix the encoding here, plain 8 bit is not allowed
818 # according to the RFCs There is no solution that is
819 # compatible to IE except stripping non-ascii chars
820 'Content-Disposition: inline; filename="%s"' % filename.encode(config.charset),
821 ])
822
823 # Send binary data.
824 sio = StringIO.StringIO(data)
825 shutil.copyfileobj (sio, self.request, 8192)
826
827 def get_html(self):
828 """Generate the HTML body of this page."""
829 newreq = RedirectOutputRequest(self.request)
830
831 newreq.form[u'action'] = [u'print']
832 if self.values.get(u'rev', None):
833 newreq.form[u'rev'] = [self.values[u'rev']]
834
835 # Save page as HTML.
836 (html, errmsg) = newreq.run()
837 if html:
838 # Make URLs absolute.
839 # Until MoinMoin is not XHTML compilant we can not use a XML parser (e.g. expat)
840 # to transform the HTML document. In the meantime we try to achive the same with
841 # regular expressions subtitution.
842 base = self.request.getQualifiedURL()
843 for htmlref in [u'src', u'href']:
844 reurlref = r'(%s=[\'"])(/[^\'"]*)[\'"]' % (htmlref,)
845 urlref = re.compile (reurlref, re.I)
846 for match in urlref.finditer(html):
847 foundref = match.groups()
848 html = html.replace (foundref[0] + foundref[1], foundref[0] + base + foundref[1])
849 else:
850 self.error_msg(self._(u'Could not redirect HTML output for further processing:') + errmsg)
851
852 return html
853
854 def make_isolang (self, language):
855 return language + u'_' + language.upper()
856
857 def html2pdf (self, html):
858 """Create a PDF document based on the current parameters."""
859 # Determine UID to access ACL protected sites too (mandatory to download attached images).
860 htmldocopts = [u'LANG=' + self.values[u'language'], u'HTMLDOC_NOCGI=1', u'htmldoc', "--cookies", "MOIN_ID=" + self.request.user.id, u'--no-duplex']
861
862 for key in [u'header', u'footer', u'tocheader', u'tocfooter']:
863 self.values[key] = self.values.get (key + u'left', u'.') + self.values.get (key + u'middle', u'.') + self.values.get (key + u'right', u'.')
864
865 permissions = []
866 for opt, value in self.values.items():
867 if opt in [u'language', u'debug', u'rev']:
868 continue
869 value = value.strip()
870 if not value:
871 continue
872 # Skip options for header/footer configuration which differenciate between position (e.g. footerright or tocheadermiddle)
873 if opt[:6] in [u'header', u'footer'] and opt[6:] or opt[:9] in [u'tocheader', u'tocfooter'] and opt[9:]:
874 continue
875 if opt in [u'style']:
876 htmldocopts += [u'--' + value]
877 elif opt in self.form_checkbox:
878 if value == u'checked':
879 if opt[:10] == u'permission':
880 permissions += [opt[10:]]
881 # Reverse meaning of 'no-' options.
882 elif opt[:3] != u'no-':
883 htmldocopts += [u'--' + opt]
884 elif opt[:3] == u'no-':
885 htmldocopts += [u'--' + opt]
886 elif opt[:6] == u'margin':
887 htmldocopts += [u'--' + opt[6:], value]
888 else:
889 htmldocopts += [u'--' + opt, value]
890 if permissions:
891 htmldocopts += [u'--permission', u','.join (permissions)]
892 htmldocopts += [u'-']
893 # Do not forget to escape all spaces!
894 eschtmldocopts = [arg.replace(u' ', u'\\ ') for arg in htmldocopts]
895 cmdstr = u' '.join(eschtmldocopts)
896 errmsg = None
897
898 pdf = None
899 if self.debug:
900 errmsg = self._(u'Command:') + u'<pre>' + self.escape(cmdstr) + u'</pre>'
901 else:
902 inp, out, err = os.popen3 (cmdstr, u'b')
903 try:
904 inp.write(html)
905 inp.close()
906 except:
907 pass
908
909 pdf = out.read()
910 out.close()
911
912 htmldocmsg = err.read()
913 err.close()
914
915 try:
916 # REMARK: Otherwise we get <defunct> processes.
917 os.wait()
918 except OSError, e:
919 # 10: No child processes.
920 if e.errno != 10:
921 raise
922
923 # Check for error message on STDOUT.
924 if pdf[:8] == u'HTMLDOC ':
925 htmldocmsg += pdf
926 pdf = None
927
928 if htmldocmsg:
929 errmsg = self._(u'Command:') + u'<pre>' + self.escape(cmdstr) + u'</pre>' + self._('returned:') + u'<pre>' + \
930 self.escape(htmldocmsg).replace(u'\n', u'<br/>') + u'</pre>'
931
932 # As it is difficult to get the htmldoc return code, we check for
933 # error by checking the produced pdf length
934 if not pdf and errmsg:
935 self.error_msg(errmsg)
936 return None
937 elif pdf[:4] != '%PDF':
938 self.error_msg(self._(u'Invalid PDF document generated') + self.escape(pdf[:80]))
939 return None
940
941 return pdf
942
943 def execute (pagename, request):
944 CreatePdfDocument().run (pagename = pagename, request = request)
945
946 # vim:ts=4:et:sw=4:nu
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.