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