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