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