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