Attachment 'CreatePdfDocument2_4_0.py'
Download 1 # -*- coding: iso-8859-1 -*-
2 __doc__ = """
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 Copy this script in your's MoinMoin action script plugin directory.
10
11 Thanks goes to Pascal Bauermeister who initiated the implementaion.
12 Lot of things changes since then but the idear using HTMLDOC is the
13 main concept of this implementation.
14
15 Please visit the homepage for further informations:
16 http://moinmo.in/ActionMarket/PdfAction
17
18 @copyright: (C) 2006 Pascal Bauermeister
19 @copyright: (C) 2006-2010 Raphael Bossek
20 @license: GNU GPL, see COPYING for details
21 """
22
23 __version__ = u'2.4.0'
24
25 release_notes = """
26 2009-02-29 RaphaelBossek
27 * Release v2.4.0
28 * NEW: Initial support for MoinMoin 1.9 including 1.8, 1.7, 1.6, 1.5!
29 * NEW: htmldoc_options is an new configuration parameter. htmldoc_options is
30 an array of parameters which will by applied before calling htmldoc_cmd.
31 * WIP: ERR012: Unable to write document file - Permission denied
32 Define an alternative output directory with -d parameter using the
33 htmldoc_options configuration variable.
34 * FIX: Title image with MoinMoin 1.5.
35
36 2009-01-12 RaphaelBossek
37 * Releae v2.3.6
38 * FIX: Error message, e.b. ERR017: Unknown style property, are surpressed
39 as long PAGES: and BYTES: lines exists.
40
41 2009-01-11 RaphaelBossek
42 * Release v2.3.5
43 * Supports MoinMoin 1.8.
44 * FIX: Fixed support for anonymous users in CGI servers where
45 no cookies exists for this page on browser site.
46 * FIX: JPEG compression level.
47 * FIX: In case where extra-headingastitle is checked but no
48 heading is defined at the page, we use the form entry.
49
50 2008-09-07 RaphaelBossek
51 * Release v2.3.4
52 * FIX: Value of expert's form field `extra-dynamiccodeblock-break` will be considered.
53
54 2008-07-09 RaphaelBossek
55 * Release v2.3.3
56 * FIX: Recognission of newline character on non-UNIX systems.
57
58 2008-07-02 RaphaelBossek
59 * Release v2.3.2
60 * NEW: Added support for HTTP PROXY server (set createpdfdocument_defaultvalues['proxy'] = u'http://myproxy:3128')
61 * FIX: Access to ACL protected attachments for MoinMoin 1.6 and newer (MOIN_SESSION).
62 * FIX: Table borders.
63 * FIX: Do not show top/bottom links in headings (show_topbottom = False).
64
65 2008-06-25 RaphaelBossek
66 * Release v2.3.1
67 * FIX: Remove interwiki part of author name.
68 * FIX: RedirectOutputRequest is able to catch error messages while initialisation, e.g. if forbidden.
69 * FIX: Suppress of line numbers in code blocks.
70 * FIX: Processing of form values (checkbox).
71 * NEW: Added debugging support in expert mode.
72 * NEW: Possibility to set in expert tab if line breaks and middle dot character is used in dynamic blocks.
73
74 2008-06-04 RaphaelBossek
75 * Release v2.3.0
76 * Supports MoinMoin v1.7.0, v1.6.3, v1.5.8
77 * Preselect document style to webpage if no heading and table of contents is found.
78 * FIX: Missing table borders where '<table style=' was used.
79 * FIX: Cut off blocks.
80
81 2008-01-27 RaphaelBossek
82 * Release v2.2.1
83 * Remove page information for MoinMoin v1.5 (interwiki).
84 * Added support to set page title using first heading.
85 * Fix for htmldoc_cmd parameter in remember form.
86 * Style of document will be set to book if TableOfContents is found by default (if
87 not overwritten).
88
89 2007-09-25 RaphaelBossek
90 * Release v2.2.0
91 * Fixed debug information for Windows.
92 * Added support for Windows where HTMLDOC_NOCGI environment variable was missing.
93 * Added preview mode added. It's now possible to see what HTMLDOC will process.
94
95 2007-09-23 RaphaelBossek
96 * Release v2.1.5
97 * Added support for line numbers in code blocks.
98 * Remove page information so it's not printed for webpage style.
99 * Added table cell padding to 5 pixels.
100
101 2007-09-21 RaphaelBossek
102 * Release v2.1.4
103 * HTMLDOC supports only HTML 4.1 (http://www.htmldoc.org/documentation.php/Elements.html)
104 * Fix for table borders and cell background.
105 * Added support for code blocks (break on page boundries).
106
107 2007-09-18 RaphaelBossek
108 * Release v2.1.3
109 * Added support for scaling images (--browserwidth).
110
111 2007-08-21 RaphaelBossek
112 * Release v2.1.2
113 * Fixed support for python 2.3.
114
115 2007-08-21 RaphaelBossek
116 * Release v2.1.1
117 * Applied speed improvemtn patch from BrianDickman.
118 * Fixed font size values where x.9 was missing.
119 * Some cosmetic changes in the form.
120
121 2007-08-20 RaphaelBossek
122 * Release v2.1.0
123 * Configuration is seperated by tabbs.
124 * Added support for font style and colors.
125 * Fixed save/load of configuration variables within page (may be empty).
126
127 2007-08-17 RaphaelBossek
128 * Release v2.0.11
129 * Added support for alternative title page and title page logo.
130 * Added support for additional fields for the title page author, docnumber
131 and copyright.
132
133 2006-12-18 RaphaelBossek
134 * Release v2.0.10
135 * Improved support for webpage/book styles if the HTML documents does not.
136
137 2006-12-17 RaphaelBossek
138 * Release v2.0.9
139 * Added AUTH_TYPE for RedirectOutputRequest()
140
141 2006-11-16 RaphaelBossek
142 * Release v2.0.8
143 * Fixed another missing configuration for RedirectOutputRequest()
144
145 2006-11-15 RaphaelBossek
146 * Release v2.0.7
147 * Fixed support for MoinMoin 1.5.3
148 * Fixed SSL support.
149
150 2006-11-14 RaphaelBossek
151 * Release v2.0.6
152 * MoinMoin 1.6 support added.
153 * Fixed Windows(TM) platform support.
154 * Added support for alternative title text.
155
156 2006-09-13 RaphaelBossek
157 * Release v2.0.5
158 * Fixed RedirectOutputRequest class where definition of script_name
159 was missing.
160
161 2006-09-12 RaphaelBossek
162 * Release v2.0.4
163 * Fixed RedirectOutputRequest class where function was redifined
164 to boolean value.
165
166 2006-09-06 RaphaelBossek
167 * Release v2.0.3
168 * Fixed FastCGI support by removing stdout redirect output code. The
169 same functionality will be done by the RedirectOutputRequest class.
170 * Renamed to CreatePdfDocument (for better translation possibilities).
171 * Fixed encoding of title page.
172 * Added charset set option.
173 * Fixed waiting for HTMLDOC.
174
175 2006-09-02 RaphaelBossek
176 * Release v2.0.2
177 * Added createpdfdocument_validoptions and createpdfdocument_defaultvalues
178 configuration parameter support. You are not able to preset available
179 options and which defaults are used in your configuration file.
180
181 2006-08-30 RaphaelBossek
182 * Release v2.0.1
183 * Fixed issue with page revision number forwarding (traceback).
184
185 2006-08-30 RaphaelBossek
186 * Release v2.0.0
187 * Feature enchanced and bug fixed version.
188
189 2006-05-26 PascalBauermeister
190 * Release v1.0.2
191 * Relative image URLs turned absolute was bogus. It is less bogus now.
192
193 2006-05-26 PascalBauermeister
194 * Release v1.0.1
195 * Set env var HTMLDOC_NOCGI to solve CGI issue
196
197 2006-05-24 PascalBauermeister
198 * Initial release v1.0.0
199 """
200
201 import os
202 import sys
203 import stat
204 import re
205 import copy
206 import shutil
207 import StringIO
208 import array
209
210 from MoinMoin import config
211 from MoinMoin import util
212 from MoinMoin import wikiutil
213 from MoinMoin import packages
214 from MoinMoin import error
215 from MoinMoin.Page import Page
216 from MoinMoin.widget.dialog import Dialog
217 from MoinMoin.version import release as moinmoin_release
218 from MoinMoin.action import ActionBase
219 from MoinMoin.action import AttachFile
220
221 from MoinMoin.version import release as moinmoin_release
222 if moinmoin_release[:4] in ['1.5.', '1.6.', '1.7.', '1.8.']:
223 class Version:
224 def __init__(self, version):
225 VERSION_RE = re.compile(r"(?P<major>\d+)\.(?P<minor>\d+)\.(?P<release>\d+)(-(?P<additional>.+))?", re.VERBOSE)
226 match = VERSION_RE.match(version)
227 v = match.groupdict()
228 self.major = int(v['major'])
229 self.minor = int(v['minor'])
230 self.release = int(v['release'])
231 moinmoin_version = Version(moinmoin_release)
232 else:
233 # Since MoinMoin 1.9
234 moinmoin_version = wikiutil.Version(version=moinmoin_release)
235
236 def is_moinmoin_version_eqless(major, minor):
237 return moinmoin_version.major <= major and moinmoin_version.minor <= minor
238
239 if is_moinmoin_version_eqless(1,8):
240 from MoinMoin.request import RequestBase
241 else:
242 # Since MoinMoin 1.9
243 from MoinMoin.web.request import Request as RequestBase
244
245 # http://www.barelyfitz.com/projects/tabber/
246 tabber_minimized = '''
247 // Version 1.9 stripped by Creativyst SS & JavaScript Compressor v2.2c (http://www.creativyst.com/Prod/3/)
248 function tabberObj(argsObj)
249 { var arg; this.div = null; this.classMain = "tabber"; this.classMainLive = "tabberlive"; this.classTab = "tabbertab"; this.classTabDefault = "tabbertabdefault"; this.classNav = "tabbernav"; this.classTabHide = "tabbertabhide"; this.classNavActive = "tabberactive"; this.titleElements = ['h2','h3','h4','h5','h6']; this.titleElementsStripHTML = true; this.removeTitle = true; this.addLinkId = false; this.linkIdFormat = '<tabberid>nav<tabnumberone>'; for (arg in argsObj) { this[arg] = argsObj[arg];}
250 this.REclassMain = new RegExp('\\\\b' + this.classMain + '\\\\b', 'gi'); this.REclassMainLive = new RegExp('\\\\b' + this.classMainLive + '\\\\b', 'gi'); this.REclassTab = new RegExp('\\\\b' + this.classTab + '\\\\b', 'gi'); this.REclassTabDefault = new RegExp('\\\\b' + this.classTabDefault + '\\\\b', 'gi'); this.REclassTabHide = new RegExp('\\\\b' + this.classTabHide + '\\\\b', 'gi'); this.tabs = new Array(); if (this.div) { this.init(this.div); this.div = null;}
251 }
252 tabberObj.prototype.init = function(e)
253 { var
254 childNodes, i, i2, t, defaultTab=0, DOM_ul, DOM_li, DOM_a, aId, headingElement; if (!document.getElementsByTagName) { return false;}
255 if (e.id) { this.id = e.id;}
256 this.tabs.length = 0; childNodes = e.childNodes; for(i=0; i < childNodes.length; i++) { if(childNodes[i].className &&
257 childNodes[i].className.match(this.REclassTab)) { t = new Object(); t.div = childNodes[i]; this.tabs[this.tabs.length] = t; if (childNodes[i].className.match(this.REclassTabDefault)) { defaultTab = this.tabs.length-1;}
258 }
259 }
260 DOM_ul = document.createElement("ul"); DOM_ul.className = this.classNav; for (i=0; i < this.tabs.length; i++) { t = this.tabs[i]; t.headingText = t.div.title; if (this.removeTitle) { t.div.title = '';}
261 if (!t.headingText) { for (i2=0; i2<this.titleElements.length; i2++) { headingElement = t.div.getElementsByTagName(this.titleElements[i2])[0]; if (headingElement) { t.headingText = headingElement.innerHTML; if (this.titleElementsStripHTML) { t.headingText.replace(/<br>/gi," "); t.headingText = t.headingText.replace(/<[^>]+>/g,"");}
262 break;}
263 }
264 }
265 if (!t.headingText) { t.headingText = i + 1;}
266 DOM_li = document.createElement("li"); t.li = DOM_li; DOM_a = document.createElement("a"); DOM_a.appendChild(document.createTextNode(t.headingText)); DOM_a.href = "javascript:void(null);"; DOM_a.title = t.headingText; DOM_a.onclick = this.navClick; DOM_a.tabber = this; DOM_a.tabberIndex = i; if (this.addLinkId && this.linkIdFormat) { aId = this.linkIdFormat; aId = aId.replace(/<tabberid>/gi, this.id); aId = aId.replace(/<tabnumberzero>/gi, i); aId = aId.replace(/<tabnumberone>/gi, i+1); aId = aId.replace(/<tabtitle>/gi, t.headingText.replace(/[^a-zA-Z0-9\-]/gi, '')); DOM_a.id = aId;}
267 DOM_li.appendChild(DOM_a); DOM_ul.appendChild(DOM_li);}
268 e.insertBefore(DOM_ul, e.firstChild); e.className = e.className.replace(this.REclassMain, this.classMainLive); this.tabShow(defaultTab); if (typeof this.onLoad == 'function') { this.onLoad({tabber:this});}
269 return this;}; tabberObj.prototype.navClick = function(event)
270 { var
271 rVal, a, self, tabberIndex, onClickArgs; a = this; if (!a.tabber) { return false;}
272 self = a.tabber; tabberIndex = a.tabberIndex; a.blur(); if (typeof self.onClick == 'function') { onClickArgs = {'tabber':self, 'index':tabberIndex, 'event':event}; if (!event) { onClickArgs.event = window.event;}
273 rVal = self.onClick(onClickArgs); if (rVal === false) { return false;}
274 }
275 self.tabShow(tabberIndex); return false;}; tabberObj.prototype.tabHideAll = function()
276 { var i; for (i = 0; i < this.tabs.length; i++) { this.tabHide(i);}
277 }; tabberObj.prototype.tabHide = function(tabberIndex)
278 { var div; if (!this.tabs[tabberIndex]) { return false;}
279 div = this.tabs[tabberIndex].div; if (!div.className.match(this.REclassTabHide)) { div.className += ' ' + this.classTabHide;}
280 this.navClearActive(tabberIndex); return this;}; tabberObj.prototype.tabShow = function(tabberIndex)
281 { var div; if (!this.tabs[tabberIndex]) { return false;}
282 this.tabHideAll(); div = this.tabs[tabberIndex].div; div.className = div.className.replace(this.REclassTabHide, ''); this.navSetActive(tabberIndex); if (typeof this.onTabDisplay == 'function') { this.onTabDisplay({'tabber':this, 'index':tabberIndex});}
283 return this;}; tabberObj.prototype.navSetActive = function(tabberIndex)
284 { this.tabs[tabberIndex].li.className = this.classNavActive; return this;}; tabberObj.prototype.navClearActive = function(tabberIndex)
285 { this.tabs[tabberIndex].li.className = ''; return this;}; function tabberAutomatic(tabberArgs)
286 { var
287 tempObj, divs, i; if (!tabberArgs) { tabberArgs = {};}
288 tempObj = new tabberObj(tabberArgs); divs = document.getElementsByTagName("div"); for (i=0; i < divs.length; i++) { if (divs[i].className &&
289 divs[i].className.match(tempObj.REclassMain)) { tabberArgs.div = divs[i]; divs[i].tabber = new tabberObj(tabberArgs);}
290 }
291 return this;}
292 function tabberAutomaticOnLoad(tabberArgs)
293 { var oldOnLoad; if (!tabberArgs) { tabberArgs = {};}
294 oldOnLoad = window.onload; if (typeof window.onload != 'function') { window.onload = function() { tabberAutomatic(tabberArgs);};} else { window.onload = function() { oldOnLoad(); tabberAutomatic(tabberArgs);};}
295 }
296 if (typeof tabberOptions == 'undefined') { tabberAutomaticOnLoad();} else { if (!tabberOptions['manualStartup']) { tabberAutomaticOnLoad(tabberOptions);}
297 }
298 '''
299
300 tabber_minimized_css = '''
301 <style type="text/css">
302 /*--------------------------------------------------
303 REQUIRED to hide the non-active tab content.
304 But do not hide them in the print stylesheet!
305 --------------------------------------------------*/
306 .tabberlive .tabbertabhide {
307 display:none;
308 }
309
310 /*--------------------------------------------------
311 .tabber = before the tabber interface is set up
312 .tabberlive = after the tabber interface is set up
313 --------------------------------------------------*/
314 .tabber {
315 }
316 .tabberlive {
317 margin-top:1em;
318 }
319
320 /*--------------------------------------------------
321 ul.tabbernav = the tab navigation list
322 li.tabberactive = the active tab
323 --------------------------------------------------*/
324 ul.tabbernav
325 {
326 margin:0;
327 padding: 3px 0;
328 border-bottom: 1px solid #778;
329 font: bold 12px Verdana, sans-serif;
330 }
331
332 ul.tabbernav li
333 {
334 list-style: none;
335 margin: 0;
336 display: inline;
337 }
338
339 ul.tabbernav li a
340 {
341 padding: 3px 0.5em;
342 margin-left: 3px;
343 border: 1px solid #778;
344 border-bottom: none;
345 background: #DDE;
346 text-decoration: none;
347 }
348
349 ul.tabbernav li a:link { color: #448; }
350 ul.tabbernav li a:visited { color: #667; }
351
352 ul.tabbernav li a:hover
353 {
354 color: #000;
355 background: #AAE;
356 border-color: #227;
357 }
358
359 ul.tabbernav li.tabberactive a
360 {
361 background-color: #fff;
362 border-bottom: 1px solid #fff;
363 }
364
365 ul.tabbernav li.tabberactive a:hover
366 {
367 color: #000;
368 background: white;
369 border-bottom: 1px solid white;
370 }
371
372 /*--------------------------------------------------
373 .tabbertab = the tab content
374 Add style only after the tabber interface is set up (.tabberlive)
375 --------------------------------------------------*/
376 .tabberlive .tabbertab {
377 padding:5px;
378 border:1px solid #aaa;
379 border-top:0;
380
381 /* If you don't want the tab size changing whenever a tab is changed
382 you can set a fixed height */
383
384 /* height:200px; */
385
386 /* If you set a fix height set overflow to auto and you will get a
387 scrollbar when necessary */
388
389 /* overflow:auto; */
390 }
391
392 /* If desired, hide the heading since a heading is provided by the tab */
393 .tabberlive .tabbertab h2 {
394 display:none;
395 }
396 .tabberlive .tabbertab h3 {
397 display:none;
398 }
399
400 /* Example of using an ID to set different styles for the tabs on the page */
401 .tabberlive#tab1 {
402 }
403 .tabberlive#tab2 {
404 }
405 .tabberlive#tab2 .tabbertab {
406 height:200px;
407 overflow:auto;
408 }
409 </style>
410 '''
411
412 def attachment_fsname(attachment, page, request):
413 """Return location of attament on the file system. current_page is the relative location
414 where attachment is used.
415 """
416 fname = None
417 pagename, filename = AttachFile.absoluteName(attachment, page.page_name)
418 #self.request.log("attachment_link: url %s pagename %s filename %s" % (url, pagename, filename))
419 fname = wikiutil.taintfilename(filename)
420 fname = AttachFile.getFilename(request, pagename, fname)
421 if not os.path.exists(fname):
422 fname = None
423 return fname
424
425
426 def shell_quote(parameter):
427 o = parameter
428 for c in [u' ', u'(', u')']:
429 o = o.replace(c, u'\\' + c)
430 return o
431
432
433 def getEditorName(request):
434 """Return name of the last editor."""
435 editorname = u''
436 if 'edit_info' in dir(request.page):
437 editorname = request.page.edit_info().get('editor', u':').split(u':')[-1]
438 else:
439 # Backward compatibility before MoinMoin 1.7.0
440 log = request.page._last_edited(request)
441 if log:
442 if request.cfg.show_hosts:
443 title = " @ %s[%s]" % (log.hostname, log.addr)
444 else:
445 title = ""
446 kind, info = log.getInterwikiEditorData(request)
447 if kind in ['interwiki', 'email']:
448 if log._usercache[log.userid].__dict__.get('aliasname', u''):
449 editorname = log._usercache[log.userid].aliasname
450 else:
451 editorname = log._usercache[log.userid].name
452 elif kind == 'ip':
453 try:
454 idx = info.index('.')
455 except ValueError:
456 idx = len(info)
457 editorname = wikiutil.escape(info[:idx])
458 return editorname
459
460
461 def pipeCommand(cmdstr, input=None):
462 child_stdin, child_stdout, child_stderr = os.popen3(cmdstr, u'b')
463 try:
464 if input:
465 child_stdin.write(input)
466 child_stdin.close()
467 except:
468 pass
469
470 child_output = child_stdout.read()
471 child_stdout.close()
472
473 child_error = child_stderr.read()
474 child_stderr.close()
475
476 if os.name in ['posix', 'mac']:
477 try:
478 # REMARK: Otherwise we get <defunct> processes.
479 os.wait()
480 except OSError, e:
481 # 10: No child processes.
482 if e.errno != 10:
483 raise
484 return (child_output, child_error)
485
486
487 class CreatePdfDocument(ActionBase):
488 """Implementation of the PDF document generator."""
489
490 def __init__(self, pagename, request):
491 ActionBase.__init__(self, pagename, request)
492 self.action_name = self.__class__.__name__
493 self.debug = False
494 self.msg = None
495 self.errormsgsent = False
496 self.default_values = {
497 #'style': u'webpage',
498 'style': None,
499 'format': u'pdf13',
500 'titlefileimage': u'',
501 'linkstyle': u'underline',
502 'headerleft': u't',
503 'headermiddle': u'.',
504 'headerright': u'D',
505 'footerleft': u'.',
506 'footermiddle': u'/',
507 'footerright': u'.',
508 'tocheaderleft': u'.',
509 'tocheadermiddle': u't',
510 'tocheaderright': u'.',
511 'tocfooterleft': u'.',
512 'tocfootermiddle': u'.',
513 'tocfooterright': u'i',
514 'bodycolor': u'FFFFFF',
515 'bodyimage': u'',
516 'textcolor': u'000000',
517 'linkcolor': u'0000E0',
518 'size': u'legal',
519 'user-password': u'',
520 'owner-password': u'',
521 'toclevels': u'3',
522 'grayscale': u'unchecked',
523 'title': u'checked',
524 'duplex': u'unchecked',
525 'landscape': u'unchecked',
526 'usersize': u'',
527 'margintop': u'0.50in',
528 'marginbottom': u'0.50in',
529 'marginleft': u'1.00in',
530 'marginright': u'0.50in',
531 'no-toc': u'checked',
532 'no-links': u'checked',
533 'firstpage': u'p1',
534 'jpeg': u'0',
535 'compression': u'0',
536 'pagemode': u'outline',
537 'pagelayout': u'single',
538 'firstpage': u'c1',
539 'numbered': u'checked',
540 'encryption': u'unchecked',
541 'permissioncopy': u'checked',
542 'permissionprint': u'checked',
543 'permissionannotate': u'checked',
544 'permissionmodify': u'checked',
545 'charset': u'iso-8859-1',
546 'debug': u'',
547 'rev': 0,
548 'extra-titledocnumber': u'',
549 'extra-titleauthor': u'',
550 'extra-titlecopyright': u'',
551 'pageinfo': u'unchecked',
552 'bodyfont': u'times',
553 'headingfont': u'helvetica',
554 'headfootfont': u'helvetica',
555 'fontsize': u'11.0',
556 'headfootsize': u'11.0',
557 'fontspacing': u'1.2',
558 'embedfonts': u'checked',
559 'browserwidth': u'680',
560 'extra-dynamiccodeblock': u'checked',
561 'extra-codeblocklinenumbers': u'checked',
562 'htmldoc_cmd': u'htmldoc',
563 'htmldoc_options': [],
564 'extra-headingastitle': u'unchecked',
565 'extra-dynamiccodeblock-middot': u'checked',
566 'extra-dynamiccodeblock-middotchar': u'·',
567 'extra-dynamiccodeblock-break': u'checked',
568 'extra-dynamiccodeblock-breakchar': u'¶',
569 }
570 # We have to know which values are checkboxes within the form. If a key does
571 # not exists wihtin the form the corresponding checkbox is not checked.
572 self.form_checkbox = []
573 for key, value in self.default_values.items():
574 if value in [u'checked', u'unchecked']:
575 self.form_checkbox += [key]
576 self.contenttype = u'application/pdf'
577
578 # This dict contains all possible values.
579 self.valid_options = {}
580
581 self.valid_options[u'tocformats'] = {
582 u'/': self._(u'1/N,2/N Arabic page numbers'),
583 u':': self._(u'1/C,2/C Arabic chapter page numbers'),
584 u'1': self._(u'1,2,3,...'),
585 u'a': self._(u'a,b,c,...'),
586 u'A': self._(u'A,B,C,...'),
587 u'c': self._(u'Chapter title'),
588 u'C': self._(u'Chapter page number'),
589 u'd': self._(u'Date'),
590 u'D': self._(u'Date + Time'),
591 u'h': self._(u'Heading'),
592 u'i': self._(u'i,ii,iii,iv,...'),
593 u'I': self._(u'I,II,III,IV,...'),
594 u't': self._(u'Title'),
595 u'T': self._(u'Time'),
596 u'.': self._(u'Blank'),
597 # TODO: Not supported yet; u'l': self._(u'Logo image'),
598 }
599 self.valid_options[u'style'] = {
600 u'webpage': self._(u'webpage'),
601 u'book': self._(u'book'),
602 u'continuous': self._(u'continuous'),
603 }
604 self.valid_options[u'size'] = {
605 u'legal': self._(u'Legal (8.5x14in)'),
606 u'a4': self._(u'A4 (210x297mm)'),
607 u'letter': self._(u'Letter (8.5x11in)'),
608 u'universal': self._(u'Universal (8.27x11in)'),
609 u'': self._(u'User defined'),
610 }
611 self.valid_options[u'format'] = {
612 u'pdf11': self._(u'PDF 1.1 (Acrobat 2.0)'),
613 u'pdf12': self._(u'PDF 1.2 (Acrobat 3.0)'),
614 u'pdf13': self._(u'PDF 1.3 (Acrobat 4.0)'),
615 u'pdf14': self._(u'PDF 1.4 (Acrobat 5.0)'),
616 # TODO: Not supported yet:
617 #u'ps1': self._(u'PostScript Level 1'),
618 #u'ps2': self._(u'PostScript Level 2'),
619 #u'ps3': self._(u'PostScript Level 3'),
620 }
621 self.valid_options[u'linkstyle'] = {
622 u'underline': self._(u'Underline'),
623 u'plain': self._(u'Plain'),
624 }
625 self.valid_options[u'firstpage'] = {
626 u'c1': self._(u'1st chapter'),
627 u'p1': self._(u'1st page'),
628 u'toc': self._(u'Contents'),
629 }
630 self.valid_options[u'jpeg'] = {
631 u'0': self._(u'None'),
632 u'50': self._(u' 50% (Good)'),
633 u'55': u'55%', u'60': u' 60%', u'65': ' 65%', u'70': ' 70%', u'75': ' 75%',
634 u'80': ' 80%', u'85': ' 85%', u'90': ' 90%', u'95': ' 95%',
635 u'100': self._(u'100% (Best)'),
636 }
637 self.valid_options[u'compression'] = {
638 u'0': self._(u'None'),
639 u'1': self._(u'1 (Fast)'),
640 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',
641 u'9': self._(u'9 (Best)'),
642 }
643 self.valid_options[u'toclevels'] = {
644 u'0': self._(u'None'),
645 u'1': u'1', u'2': '2', u'3': '3', u'4': '4'
646 }
647 self.valid_options[u'pagemode'] = {
648 u'outline': self._(u'Outline'),
649 u'document': self._(u'Document'),
650 u'fullscreen': self._(u'Full-screen'),
651 }
652 self.valid_options[u'pagelayout'] = {
653 u'single': self._(u'Single'),
654 u'one': self._(u'One column'),
655 u'twoleft': self._(u'Two column left'),
656 u'tworight': self._(u'Two column right'),
657 }
658 self.valid_options[u'charset'] = {
659 u'iso-8859-1': self._(u'ISO 8859-1'),
660 u'iso-8859-2': self._(u'ISO 8859-2'),
661 u'iso-8859-3': self._(u'ISO 8859-3'),
662 u'iso-8859-4': self._(u'ISO 8859-4'),
663 u'iso-8859-5': self._(u'ISO 8859-5'),
664 u'iso-8859-6': self._(u'ISO 8859-6'),
665 u'iso-8859-7': self._(u'ISO 8859-7'),
666 u'iso-8859-8': self._(u'ISO 8859-8'),
667 u'iso-8859-9': self._(u'ISO 8859-9'),
668 u'iso-8859-14': self._(u'ISO 8859-14'),
669 u'iso-8859-15': self._(u'ISO 8859-15'),
670 u'cp-874': self._(u'cp-847'),
671 u'cp-1250': self._(u'cp-1250'),
672 u'cp-1251': self._(u'cp-1251'),
673 u'cp-1252': self._(u'cp-1252'),
674 u'cp-1253': self._(u'cp-1253'),
675 u'cp-1254': self._(u'cp-1254'),
676 u'cp-1255': self._(u'cp-1255'),
677 u'cp-1256': self._(u'cp-1256'),
678 u'cp-1257': self._(u'cp-1257'),
679 u'cp-1258': self._(u'cp-1258'),
680 u'koi-8r': self._(u'koi-8r'),
681 }
682 self.valid_options[u'bodyfont'] = {
683 u'courier': self._(u'Courier'),
684 u'helvetica': self._(u'Helvetica'),
685 u'monospace': self._(u'Monospace'),
686 u'sans': self._(u'Sans'),
687 u'serif': self._(u'Serif'),
688 u'times': self._(u'Times'),
689 }
690 self.valid_options[u'headingfont'] = self.valid_options[u'bodyfont']
691 self.valid_options[u'headfootfont'] = self.valid_options[u'bodyfont']
692 # Go through all font types and create fontname{-bold,-oblique,-boldoblique} entries.
693 for fontname in self.valid_options[u'bodyfont'].keys():
694 for fontstyle in [u'bold', u'oblique', u'boldoblique']:
695 self.valid_options[u'headfootfont'][fontname + u'-' + fontstyle] = u'%s (%s)' % (self.valid_options[u'headfootfont'][fontname], self._(fontstyle),)
696 # Set possible font sizes.
697 self._set_fontsize(u'headfootsize', 6, 24, 5)
698 self._set_fontsize(u'fontsize', 4, 24, 5)
699 self._set_fontsize(u'fontspacing', 1, 3, 1)
700
701 # Set translated name of table of contents as default.
702 self.default_values[u'toctitle'] = self._(u'Contents')
703
704 self.default_values[u'titletext'] = self.pagename
705 self.default_values[u'extra-titledocnumber'] = u'%d' % self.request.page.get_rev()[1]
706 page_editor = self.request.page.lastEditInfo().get(u'editor', u'')
707 self.default_values[u'extra-titleauthor'] = wikiutil.escape(getEditorName(self.request))
708
709 # Make sure we create date and time strings in right format.
710 if self.request.current_lang:
711 self.default_values[u'language'] = self.request.current_lang
712 elif self.request.page.language:
713 self.default_values[u'language'] = self.request.page.language
714 else:
715 #self.cfg.language_default or "en"
716 self.default_values[u'language'] = self.make_isolang(self.cfg.__dict__.get(u'default_language', u'en'))
717
718 self.values = {}
719
720 # If the configuration variable 'createpdfdocument_validoptions' exists we update our
721 # self.valid_options dict with these values.
722 if getattr (self.request.cfg, u'createpdfdocument_validoptions', None):
723 self.valid_options.update (self.request.cfg.createpdfdocument_validoptions)
724
725 # If the configuration variable 'createpdfdocument_defaultvalues' exists we update our
726 # self.default_values dict with these values.
727 if getattr (self.request.cfg, u'createpdfdocument_defaultvalues', None):
728 for key, value in self.request.cfg.createpdfdocument_defaultvalues.items():
729 self.default_values[key] = value
730
731 # Scan page to extract default values.
732 self.set_page_default_values()
733 self.set_page_values()
734 self.update_values(useform=False)
735
736 self.fields = {
737 'pagename': wikiutil.escape(self.pagename),
738 'action': self.action_name,
739 'version': __version__,
740 'moinmoin_release': moinmoin_release,
741
742 'label_input': self._(u'Input'),
743 'label_output': self._(u'Output'),
744 'label_page': self._(u'Page'),
745 'label_tableofcontents': self._(u'Contents'),
746 'label_pdf': self._(u'PDF'),
747 'label_security': self._(u'Security'),
748
749 'label_choose_style': self._(u'Choose style'),
750 'help_choose_style': self._(u'book: Create a structured PDF document with headings, chapters, etc.') + u'<br />' + \
751 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/>' + \
752 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.'),
753
754 'help_titletext': self._(u'Title of the document for the front page.'),
755
756 'label_extra-headingastitle': self._(u'Heading 1 as title'),
757 'help_extra-headingastitle': self._(u'Extract the first heading of the document and use it as title. If checked the title field has no effect.'),
758
759 'label_titlefileimage': self._(u'Title file/image'),
760 'help_titlefileimage': self._(u'The title image or HTML page. These file has to be an attachments!'),
761
762 'label_extra-titledocnumber': self._(u'Version'),
763 'help_extra-titledocnumber': self._(u'Specify document version to be displayed on the title page.'),
764
765 'label_extra-titleauthor': self._(u'Author'),
766 'help_extra-titleauthor': self._(u'Intellectual property owner of this document.'),
767
768 'label_extra-titlecopyright': self._(u'Copyright'),
769 'help_extra-titlecopyright': self._(u'Copyright notice for this document.'),
770
771 'label_pageinfo': self._(u'Apply page information'),
772 'help_pageinfo': self._(u'Information about who and when modified the document are applied at the end.'),
773
774 'label_format': self._(u'Output format'),
775 'help_format': self._(u'Specifies the output format.'),
776
777 'label_outputoptions': self._(u'Output options'),
778 'label_grayscale': self._(u'Grayscale document'),
779 'label_titlepage': self._(u'Title page'),
780 'label_titletext': self._(u'Title'),
781 'label_jpeg': self._(u'JPEG big images'),
782 'label_compression': self._(u'Compression'),
783
784 'label_no-toc': self._(u'Generate a table of contents'),
785 'help_no-toc': self._(u''),
786
787 'label_toclevels': self._(u'Limit the number of levels in the table-of-contents'),
788 'help_toclevels': self._(u'Sets the number of levels in the table-of-contents.') + u' ' + self._(u'Empty for unlimited levels.'),
789
790 'label_numbered': self._(u'Numbered headings'),
791 'help_numbered': self._(u'Check to number all of the headings in the document.'),
792
793 'label_toctitle': self._(u'Table-of-contents title'),
794 'help_toctitle': self._(u'Sets the title for the table-of-contents.') + u' ' + self._(u'Empty for default title.'),
795
796 'label_left': self._(u'Left'),
797 'label_middle': self._(u'Middle'),
798 'label_right': self._(u'Right'),
799
800 'label_tocheader': self._(u'Header of table-of-contantes page'),
801 'help_tocheader': self._(u'Sets the page header to use on table-of-contents pages.'),
802
803 'label_tocfooter': self._(u'Footer of table-of-contantes page'),
804 'help_tocfooter': self._(u'Sets the page footer to use on table-of-contents pages.'),
805
806 'label_header': self._(u'Page header'),
807 'help_header': self._(u'Sets the page header to use on body pages.'),
808
809 'label_footer': self._(u'Page footer'),
810 'help_footer': self._(u'Sets the page footer to use on body pages.'),
811
812 'label_colors': self._(u'Colors'),
813 'label_no-links': self._(u'Create HTTP links'),
814 'help_no-links': self._(u'Enables generation of links in PDF files.'),
815
816 'label_linkstyle': self._(u'Style of HTTP links'),
817 'help_linkstyle': self._(u''),
818
819 'label_linkcolor': self._(u'HTTP links color'),
820 'help_linkcolor': self._(u'Sets the color of links.'),
821
822 'label_bodycolor': self._(u'Body color'),
823 'help_bodycolor': self._(u'Enter the HTML color for the body (background).'),
824
825 'label_bodyimage': self._(u'Body image'),
826 'help_bodyimage': self._(u'Enter the image file for the body (background). These file has to be an attachments!'),
827
828 'label_textcolor': self._(u'Text color'),
829 'help_textcolor': self._(u'Enter the HTML color for the text.'),
830
831 'label_duplex': self._(u'2-Sided'),
832 'help_duplex': self._(u'Specifies that the output should be formatted for double-sided printing.'),
833
834 'label_landscape': self._(u'Landscape'),
835
836 'label_choose_size': self._(u'Choose page size'),
837 'help_choose_size': self._(u'Choose one of the predefined standard sizes or select user defined.'),
838
839 'label_usersize': self._(u'User defined page size'),
840 '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).'),
841
842 'label_browserwidth': self._(u'Browser width'),
843 'help_browserwidth': self._(u'Set the target browser width in pixels (400-1200). This determines the page scaling of images.'),
844
845 'label_margin': self._(u'User defined margin'),
846 'label_margintop': self._(u'Top'),
847 'label_marginbottom': self._(u'Bottom'),
848 'label_marginleft': self._(u'Left'),
849 'label_marginright': self._(u'Right'),
850 '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.'),
851
852 'label_pagemode': self._(u'Page mode'),
853 '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.'),
854
855 'label_pagelayout': self._(u'Page layout'),
856 '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.'),
857
858 'label_firstpage': self._(u'First page'),
859 'help_firstpage': self._(u'Choose the initial page that will be shown.'),
860
861 'label_encryption': self._(u'Encryption'),
862 'help_encryptin': self._(u'Enables encryption and security features for PDF output.'),
863 'label_permissions': self._(u'Permissions'),
864 'help_permissions': self._(u'Specifies the document permissions.'),
865
866 'label_permissionannotate': self._(u'Annotate'),
867 'label_permissionprint': self._(u'Print'),
868 'label_permissionmodify': self._(u'Modify'),
869 'label_permissioncopy': self._(u'Copy'),
870
871 'label_owner-password': self._(u'Owner password'),
872 '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.'),
873
874 'label_user-password': self._(u'User password'),
875 'help_user-password': self._(u'Specifies the user password to restrict viewing permissions on this PDF document.') + u' ' + self._(u'Empty for no encryption.'),
876
877 'label_expert': self._(u'Expert'),
878 'label_language': self._(u'Language translation'),
879 'help_language': self._(u'Specify language to use for date and time format.'),
880
881 'label_extra-dynamiccodeblock': self._(u'Dynamic code block'),
882 'help_extra-dynamiccodeblock': self._(u'Shrink code blocks on page.'),
883
884 'label_extra-codeblocklinenumbers': self._(u'Lines in code block'),
885 'help_extra-codeblocklinenumbers': self._(u'Show line numbers for code blocks.'),
886
887 'label_extra-dynamiccodeblock-middot': self._(u'Use dots instead of spaces in code blocks'),
888 'help_extra-dynamiccodeblock-middot': self._(u'Make spaces visable by dots (%s) instead of white spaces.') % self.values['extra-dynamiccodeblock-middotchar'],
889
890 'label_extra-dynamiccodeblock-break': self._(u'Use a para character for line breaks'),
891 'help_extra-dynamiccodeblock-break': self._(u'Make line breaks visable by a extra character (%s) at the end.') % self.values['extra-dynamiccodeblock-breakchar'],
892
893 'label_debug': self._(u'Enable debugging information'),
894 'help_debug': self._(u'Enable this feature if you searching for problems or intent to report a bug report'),
895
896 'label_fonts': self._(u'Fonts'),
897 'label_fontsize': self._(u'Base font size'),
898 'help_fontsize': self._(u'Set the default size of text.'),
899 'label_fontspacing': self._(u'Line spacing'),
900 'help_fontspacing': self._(u'Set the spacing between lines of text.'),
901 'label_bodyfont': self._(u'Body typeface'),
902 'help_bodyfont': self._(u'Choose the default typeface (font) of text.'),
903 'label_headingfont': self._(u'Heading typeface'),
904 'help_headingfont': self._(u'Choose the default typeface (font) of headings.'),
905 'label_headfootsize': self._(u'Header/Footer size'),
906 'help_headfootsize': self._(u'Set the size of header and footer text.'),
907 'label_headfootfont': self._(u'Header/Footer font'),
908 'help_headfootfont': self._(u'Choose the font for header and footer text.'),
909 'label_charset': self._(u'Charset set'),
910 'help_charset': self._(u'Change the encoding of the text in document.'),
911 'label_embedfonts': self._(u'Embed fonts'),
912 'help_embedfonts': self._(u'Check to embed font in the output file.'),
913
914 'label_about': self._(u'About'),
915 'copyright': u'',
916 'version': self._(u'Version') + u' ' + __version__,
917
918 'button_generate': self._(u'Generate PDF'),
919 'button_preview': self._(u'Preview'),
920 'button_remember': self._(u'Remember form'),
921 'button_cancel': self._(u'Cancel'),
922 'button_reset': self._(u'Reset'),
923 }
924 self.fields['copyright'] = u"<br/>\n".join(wikiutil.escape(__doc__).split(u"\n"))
925 self.fields.update(self.values)
926
927 # Status of debug.
928 if self.debug:
929 self.fields[u'debug'] = u'1'
930 else:
931 self.fields[u'debug'] = u'0'
932
933 # Go through all format strings.
934 for name in [u'tocheader', u'tocfooter', u'header', u'footer']:
935 self.fields[u'choose_' + name] = self._chooseformat(name)
936
937 self.fields[u'select_style'] = self._select(u'style')
938 self.fields[u'select_format'] = self._select(u'format')
939 self.fields[u'select_linkstyle'] = self._select(u'linkstyle')
940 self.fields[u'select_size'] = self._select(u'size')
941 self.fields[u'select_jpeg'] = self._select(u'jpeg')
942 self.fields[u'select_compression'] = self._select(u'compression')
943 self.fields[u'select_toclevels'] = self._select(u'toclevels')
944 self.fields[u'select_pagemode'] = self._select(u'pagemode')
945 self.fields[u'select_pagelayout'] = self._select(u'pagelayout')
946 self.fields[u'select_firstpage'] = self._select(u'firstpage')
947 self.fields[u'select_charset'] = self._select(u'charset')
948 self.fields[u'select_fontsize'] = self._select(u'fontsize')
949 self.fields[u'select_bodyfont'] = self._select(u'bodyfont')
950 self.fields[u'select_headingfont'] = self._select(u'headingfont')
951 self.fields[u'select_headfootsize'] = self._select(u'headfootsize')
952 self.fields[u'select_headfootfont'] = self._select(u'headfootfont')
953 self.fields[u'select_fontspacing'] = self._select(u'fontspacing')
954
955 # Add tabber implementation.
956 self.request.cfg.html_head += """
957 <script type="text/javascript">
958 <!-- //
959 %s
960 //-->
961 </script>
962
963 %s
964 """ % (tabber_minimized, tabber_minimized_css,)
965
966
967 def error_msg(self, msg):
968 """Display error message."""
969 self.error = msg
970
971
972 def fixhtmlstr(self, str):
973 """Convert utf-8 encoded multi-byte sequences into &#XXXX; format."""
974 htmlstr = array.array('c')
975 for c in str:
976 if ord(c) >= 128:
977 htmlstr.fromstring('&#%d;' % ord(c))
978 else:
979 htmlstr.fromstring(c)
980 return htmlstr.tostring()
981
982
983 def set_page_values(self):
984 """Scan raw page for additional information relating PDF generation.
985 """
986 #pdflines = False
987 for line in self.request.page.get_raw_body().split(u'\n'):
988 if line[:6] == u'##pdf ' and len(line[6:]):
989 line = line[6:]
990 key = line.split()[0]
991 value = line[len(key) + 1:]
992 # Only accept known values/settings.
993 if key in self.default_values:
994 # Check if there are any restrictions for key.
995 if key in self.valid_options:
996 # Set only the value if the restrictions are confirmed.
997 valid_values = self.valid_options[key].keys()
998 if value in valid_values:
999 self.values[key] = value
1000 else:
1001 # There are no restrictions for value.
1002 self.values[key] = value
1003 elif not line:
1004 break
1005
1006
1007 def set_page_default_values(self):
1008 """Collect as mutch as possible information about this page to assume some defaults.
1009 """
1010 # We are not able to recognise if this string is part of a verbatim area.
1011 # Support for MoinMoin v1.6 [[TableOfContentes]] and v1.7 <<TableOfContents>> syntax.
1012 matchtoclvl = re.compile(r'^[\[<]{2}TableOfContents\(\s*(\d+)\s*\)[\]>]{2}')
1013 matchtoc = re.compile(r'^[\[<]{2}TableOfContents\(*\)*[\]>]{2}')
1014 matchheading = re.compile(r'^[=]+ .*[=]+$')
1015 toc_found = False
1016 heading_found = False
1017 for line in self.request.page.get_raw_body().split(u'\n'):
1018 if line[:10] == u'#language ' and not u'language' in self.values:
1019 lang = self.make_isolang(line[10:])
1020 if lang:
1021 self.default_values[u'language'] = lang
1022 elif not u'toclevels' in self.values and not toc_found:
1023 result = matchtoclvl.match(line)
1024 if result:
1025 toclevels = int(result.group(1).strip())
1026 if toclevels > 4:
1027 toclevels = 4
1028 self.default_values[u'toclevels'] = str(toclevels)
1029 toc_found = True
1030 elif matchtoc.match(line):
1031 toc_found = True
1032 elif matchheading.match(line):
1033 heading_found = True
1034 # We assume if table-of-contents is used we intent to generate a book.
1035 if toc_found and heading_found:
1036 # Do not change style if set manually or by configuration.
1037 if self.default_values.get(u'style', None) == None:
1038 self.default_values[u'style'] = u'book'
1039 else:
1040 self.default_values[u'style'] = u'webpage'
1041 # Do not generate a table of contents page.
1042 if self.default_values[u'style'] != u'book':
1043 # Do not change style if set manually or by configuration.
1044 self.default_values[u'no-toc'] = self.default_values.get(u'no-toc', u'unchecked')
1045
1046
1047 def _select (self, name, description=None):
1048 """Helper function to create a selection control."""
1049 str = u'<select name="%s" size="1">' % (name,)
1050 if not description:
1051 description = self.valid_options[name]
1052 keys = description.keys()
1053 keys.sort()
1054 for value in keys:
1055 if value == self.values[name]:
1056 selected = u'selected'
1057 else:
1058 selected = u''
1059 str += u'<option value="%s" %s>%s</option>' % (value, selected, description[value],)
1060 str += u'</select>'
1061 return str
1062
1063 def _chooseformat (self, name):
1064 """Helper function to create left/middle/right selection controls."""
1065 str = u""" <tr>
1066 <td class="label"><label>%s</label></td>
1067 <td><table>
1068 <tr>
1069 <td>%s</td>
1070 <td>%s</td>
1071 </tr>
1072 <tr>
1073 <td>%s</td>
1074 <td>%s</td>
1075 </tr>
1076 <tr>
1077 <td>%s</td>
1078 <td>%s</td>
1079 </tr>
1080 </table>
1081 </td>
1082 <td>%s</td>
1083 </tr>""" % (self.fields[u'label_' + name],
1084 self.fields[u'label_left'], self._select(name + u'left', self.valid_options[u'tocformats']),
1085 self.fields[u'label_middle'], self._select(name + u'middle', self.valid_options[u'tocformats']),
1086 self.fields[u'label_right'], self._select(name + u'right', self.valid_options[u'tocformats']),
1087 self.fields[u'help_' + name],)
1088 return str
1089
1090 def get_form_html(self, buttons_html):
1091 """MoinMoin.action.ActionBase interface function
1092 """
1093 form = u''
1094 if self.debug:
1095 form += u'<p class="warning">' + self._('Debug mode activated.') + u'</p>'
1096 if not is_moinmoin_version_eqless(1,9):
1097 form += u'<p class="warning">' + self._(u'This plugin was not verified with MoinMoin %(version)s.') % {u'version': moinmoin_release} + u'</p>'
1098 form += """
1099 <form method="post" action="">
1100 <input type="hidden" name="action" value="%(action)s"/>
1101 <div class="tabber">
1102 <div class="tabbertab">
1103 <h3>%(label_input)s</h3>
1104 <table>
1105 <tr>
1106 <td class="label"><label>%(label_choose_style)s</label></td>
1107 <td class="content">%(select_style)s</td>
1108 <td>%(help_choose_style)s</td>
1109 </tr>
1110 <tr>
1111 <td class="label"><label>%(label_titletext)s</label></td>
1112 <td class="content"><input type="text" size="30" name="titletext" value="%(titletext)s" /></td>
1113 <td>%(help_titletext)s</td>
1114 </tr>
1115 <tr>
1116 <td class="label"><label>%(label_extra-headingastitle)s</label></td>
1117 <td><input type="checkbox" name="extra-headingastitle" value="checked" %(extra-headingastitle)s /></td>
1118 <td>%(help_extra-headingastitle)s</td>
1119 </tr>
1120 <tr>
1121 <td class="label"><label>%(label_titlefileimage)s</label></td>
1122 <td class="content"><input type="text" size="30" name="titlefileimage" value="%(titlefileimage)s" /></td>
1123 <td>%(help_titlefileimage)s</td>
1124 </tr>
1125 <tr>
1126 <td class="label"><label>%(label_extra-titledocnumber)s</label></td>
1127 <td class="content"><input type="text" size="30" name="extra-titledocnumber" value="%(extra-titledocnumber)s" /></td>
1128 <td>%(help_extra-titledocnumber)s</td>
1129 </tr>
1130 <tr>
1131 <td class="label"><label>%(label_extra-titleauthor)s</label></td>
1132 <td class="content"><input type="text" size="30" name="extra-titleauthor" value="%(extra-titleauthor)s" /></td>
1133 <td>%(help_extra-titleauthor)s</td>
1134 </tr>
1135 <tr>
1136 <td class="label"><label>%(label_extra-titlecopyright)s</label></td>
1137 <td class="content"><input type="text" size="30" name="extra-titlecopyright" value="%(extra-titlecopyright)s" /></td>
1138 <td>%(help_extra-titlecopyright)s</td>
1139 </tr>
1140 <tr>
1141 <td class="label"><label>%(label_pageinfo)s</label></td>
1142 <td class="checkbox"><input type="checkbox" name="pageinfo" value="checked" %(pageinfo)s /></td>
1143 <td>%(help_pageinfo)s</td>
1144 </tr>
1145 </table>
1146 </div>
1147 <div class="tabbertab">
1148 <h3>%(label_output)s</h3>
1149 <table>
1150 <tr>
1151 <td class="label"><label>%(label_format)s</label></td>
1152 <td class="content">%(select_format)s</td>
1153 <td>%(help_format)s</td>
1154 </tr>
1155 <tr>
1156 <td class="label"><label>%(label_outputoptions)s</label></td>
1157 <td colspan="2"><input type="checkbox" name="grayscale" value="checked" %(grayscale)s />%(label_grayscale)s
1158 <input type="checkbox" name="title" value="checked" %(title)s />%(label_titlepage)s<br />
1159 %(label_compression)s : %(select_compression)s
1160 %(label_jpeg)s %(select_jpeg)s</td>
1161 </tr>
1162 </table>
1163 </div>
1164 <div class="tabbertab">
1165 <h3>%(label_page)s</h3>
1166 <table>
1167 <tr>
1168 <td class="label"><label>%(label_choose_size)s</label></td>
1169 <td>%(select_size)s <br /><nobr>%(label_usersize)s <input type="text" size="15" name="usersize" value="%(usersize)s" /></nobr></td>
1170 <td>%(help_choose_size)s<br />%(help_usersize)s</td>
1171 </tr>
1172 <tr>
1173 <td class="label"><label>%(label_browserwidth)s</label></td>
1174 <td class="content"><input type="text" size="30" name="browserwidth" value="%(browserwidth)s" /></td>
1175 <td>%(help_browserwidth)s</td>
1176 </tr>
1177 <tr>
1178 <td> </td>
1179 <td colspan="2"><input type="checkbox" name="duplex" value="checked" %(duplex)s /> %(label_duplex)s
1180 <input type="checkbox" name="landscape" value="checked" %(landscape)s /> %(label_landscape)s</td>
1181 </tr>
1182 <tr>
1183 <td class="label"><label>%(label_margin)s</label></td>
1184 <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>
1185 <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>
1186 <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>
1187 <td>%(help_margin)s</td>
1188 </tr>
1189 %(choose_header)s
1190 %(choose_footer)s
1191 </table>
1192 </div>
1193 <div class="tabbertab">
1194 <h3>%(label_tableofcontents)s</h3>
1195 <table>
1196 <tr>
1197 <td class="label"><label>%(label_no-toc)s</label></td>
1198 <td class="checkbox"><input type="checkbox" name="no-toc" value="checked" %(no-toc)s /></td>
1199 <td>%(help_no-toc)s</td>
1200 </tr>
1201 <tr>
1202 <td class="label"><label>%(label_toclevels)s</label></td>
1203 <td class="content">%(select_toclevels)s</td>
1204 <td>%(help_toclevels)s</td>
1205 </tr>
1206 <tr>
1207 <td> </td>
1208 <td><input type="checkbox" name="numbered" value="checked" %(numbered)s /> %(label_numbered)s</td>
1209 <td>%(help_numbered)s</td>
1210 </tr>
1211 <tr>
1212 <td class="label"><label>%(label_toctitle)s</label></td>
1213 <td class="content"><input type="text" size="30" name="toctitle" value="%(toctitle)s" /></td>
1214 <td>%(help_toctitle)s</td>
1215 </tr>
1216 %(choose_tocheader)s
1217 %(choose_tocfooter)s
1218 </table>
1219 </div>
1220 <div class="tabbertab">
1221 <h3>%(label_colors)s</h3>
1222 <table>
1223 <tr>
1224 <td class="label"><label>%(label_bodycolor)s</label></td>
1225 <td class="content"><input type="text" size="6" name="bodycolor" value="%(bodycolor)s" /></td>
1226 <td>%(help_bodycolor)s</td>
1227 </tr>
1228 <tr>
1229 <td class="label"><label>%(label_bodyimage)s</label></td>
1230 <td class="content"><input type="text" size="30" name="bodyimage" value="%(bodyimage)s" /></td>
1231 <td>%(help_bodyimage)s</td>
1232 </tr>
1233 <tr>
1234 <td class="label"><label>%(label_textcolor)s</label></td>
1235 <td class="content"><input type="text" size="6" name="textcolor" value="%(textcolor)s" /></td>
1236 <td>%(help_textcolor)s</td>
1237 </tr>
1238 <tr>
1239 <td class="label"><label>%(label_linkcolor)s</label></td>
1240 <td class="content"><input type="text" size="6" name="linkcolor" value="%(linkcolor)s" /></td>
1241 <td>%(help_linkcolor)s</td>
1242 </tr>
1243 <tr>
1244 <td class="label"><label>%(label_linkstyle)s</label></td>
1245 <td class="content">%(select_linkstyle)s</td>
1246 <td>%(help_linkstyle)s</td>
1247 </tr>
1248 <tr>
1249 <td class="label"><label>%(label_no-links)s</label></td>
1250 <td><input type="checkbox" name="no-links" value="checked" %(no-links)s /></td>
1251 <td>%(help_no-links)s</td>
1252 </tr>
1253 </table>
1254 </div>
1255 <div class="tabbertab">
1256 <h3>%(label_fonts)s</h3>
1257 <table>
1258 <tr>
1259 <td class="label"><label>%(label_fontsize)s</label></td>
1260 <td class="content">%(select_fontsize)s</td>
1261 <td>%(help_fontsize)s</td>
1262 </tr>
1263 <tr>
1264 <td class="label"><label>%(label_fontspacing)s</label></td>
1265 <td class="content">%(select_fontspacing)s</td>
1266 <td>%(help_fontspacing)s</td>
1267 </tr>
1268 <tr>
1269 <td class="label"><label>%(label_bodyfont)s</label></td>
1270 <td class="content">%(select_bodyfont)s</td>
1271 <td>%(help_bodyfont)s</td>
1272 </tr>
1273 <tr>
1274 <td class="label"><label>%(label_headingfont)s</label></td>
1275 <td class="content">%(select_headingfont)s</td>
1276 <td>%(help_headingfont)s</td>
1277 </tr>
1278 <tr>
1279 <td class="label"><label>%(label_headfootsize)s</label></td>
1280 <td class="content">%(select_headfootsize)s</td>
1281 <td>%(help_headfootsize)s</td>
1282 </tr>
1283 <tr>
1284 <td class="label"><label>%(label_headfootfont)s</label></td>
1285 <td class="content">%(select_headfootfont)s</td>
1286 <td>%(help_headfootfont)s</td>
1287 </tr>
1288 <tr>
1289 <td class="label"><label>%(label_charset)s</label></td>
1290 <td class="content">%(select_charset)s</td>
1291 <td>%(help_charset)s</td>
1292 </tr>
1293 <tr>
1294 <td class="label"><label>%(label_embedfonts)s</label></td>
1295 <td class="checkbox"><input type="checkbox" name="embedfonts" value="checked" %(embedfonts)s /></td>
1296 <td>%(help_embedfonts)s</td>
1297 </tr>
1298 </table>
1299 </div>
1300 <div class="tabbertab">
1301 <h3>%(label_pdf)s</h3>
1302 <table>
1303 <tr>
1304 <td class="label"><label>%(label_pagemode)s</label></td>
1305 <td class="content">%(select_pagemode)s</td>
1306 <td>%(help_pagemode)s</td>
1307 </tr>
1308 <tr>
1309 <td class="label"><label>%(label_pagelayout)s</label></td>
1310 <td class="content">%(select_pagelayout)s</td>
1311 <td>%(help_pagelayout)s</td>
1312 </tr>
1313 <tr>
1314 <td class="label"><label>%(label_firstpage)s</label></td>
1315 <td class="content">%(select_firstpage)s</td>
1316 <td>%(help_firstpage)s</td>
1317 </tr>
1318 </table>
1319 </div>
1320 <div class="tabbertab">
1321 <h3>%(label_security)s</h3>
1322 <table>
1323 <tr>
1324 <td class="label"><label>%(label_encryption)s</label></td>
1325 <td><input type="checkbox" name="encryption" value="checked" %(encryption)s /></td>
1326 <td>%(help_numbered)s</td>
1327 </tr>
1328 <tr>
1329 <td class="label"><label>%(label_permissions)s</label></td>
1330 <td><nobr><input type="checkbox" name="permissionprint" value="checked" %(permissionprint)s /> %(label_permissionprint)s</nobr>
1331 <nobr><input type="checkbox" name="permissionmodify" value="checked" %(permissionmodify)s /> %(label_permissionmodify)s</nobr><br />
1332 <nobr><input type="checkbox" name="permissioncopy" value="checked" %(permissioncopy)s /> %(label_permissioncopy)s</nobr>
1333 <nobr><input type="checkbox" name="permissionannotate" value="checked" %(permissionannotate)s /> %(label_permissionannotate)s</nobr></td>
1334 <td>%(help_permissions)s</td>
1335 </tr>
1336 <tr>
1337 <td class="label"><label>%(label_user-password)s</label></td>
1338 <td class="content"><input type="password" size="30" name="user-password" value="%(user-password)s" /></td>
1339 <td>%(help_user-password)s</td>
1340 </tr>
1341 <tr>
1342 <td class="label"><label>%(label_owner-password)s</label></td>
1343 <td class="content"><input type="password" size="30" name="owner-password" value="%(owner-password)s" /></td>
1344 <td>%(help_owner-password)s</td>
1345 </tr>
1346 </table>
1347 </div>
1348 <div class="tabbertab">
1349 <h3>%(label_expert)s</h3>
1350 <table>
1351 <tr>
1352 <td class="label"><label>%(label_language)s</label></td>
1353 <td class="content"><input type="text" size="6" name="language" value="%(language)s" /></td>
1354 <td>%(help_language)s</td>
1355 </tr>
1356 <tr>
1357 <td class="label"><label>%(label_extra-dynamiccodeblock)s</label></td>
1358 <td class="checkbox"><input type="checkbox" name="extra-dynamiccodeblock" value="checked" %(extra-dynamiccodeblock)s /></td>
1359 <td>%(help_extra-dynamiccodeblock)s</td>
1360 </tr>
1361 <tr>
1362 <td class="label"><label>%(label_extra-codeblocklinenumbers)s</label></td>
1363 <td class="checkbox"><input type="checkbox" name="extra-codeblocklinenumbers" value="checked" %(extra-codeblocklinenumbers)s /></td>
1364 <td>%(help_extra-codeblocklinenumbers)s</td>
1365 </tr>
1366 <tr>
1367 <td class="label"><label>%(label_extra-dynamiccodeblock-middot)s</label></td>
1368 <td class="checkbox"><input type="checkbox" name="extra-dynamiccodeblock-middot" value="checked" %(extra-dynamiccodeblock-middot)s /></td>
1369 <td>%(help_extra-dynamiccodeblock-middot)s</td>
1370 </tr>
1371 <tr>
1372 <td class="label"><label>%(label_extra-dynamiccodeblock-break)s</label></td>
1373 <td class="checkbox"><input type="checkbox" name="extra-dynamiccodeblock-break" value="checked" %(extra-dynamiccodeblock-break)s /></td>
1374 <td>%(help_extra-dynamiccodeblock-break)s</td>
1375 </tr>
1376 <tr>
1377 <td class="label"><label>%(label_debug)s</label></td>
1378 <td class="checkbox"><input type="checkbox" name="debug" value="checked" %(debug)s /></td>
1379 <td>%(help_debug)s</td>
1380 </tr>
1381 </table>
1382 </div>
1383 <div class="tabbertab">
1384 <h3>%(label_about)s</h3>
1385 <p>%(version)s (MoinMoin %(moinmoin_release)s)</p>
1386 <p>%(copyright)s</p>
1387 </div>
1388 </div>
1389 <p>
1390 <div class="buttons">
1391 <input type="hidden" name="debug" value="%(debug)s" /><input type="hidden" name="rev" value="%(rev)s" />
1392 <input type="submit" name="generate_from_form" value="%(button_generate)s" />
1393 <input type="submit" name="preview" value="%(button_preview)s" />
1394 <input type="submit" name="remember" value="%(button_remember)s" />
1395 <input type="submit" name="cancel" value="%(button_cancel)s" />
1396 </div>
1397 </p>
1398 </form>
1399 """ % self.fields
1400 return form
1401
1402
1403 def render(self):
1404 """MoinMoin.action.ActionBase: Extend support of multipple action buttons
1405 """
1406 for buttonname in ['generate', 'generate_from_form', 'preview', 'remember']:
1407 # We have multipple buttons which do not match the default
1408 # MoinMoin.action.ActionBase.form_trigger = 'doit'. We rename the default
1409 # to one of the pressed buttions.
1410 if buttonname in self.form:
1411 self.form_trigger = buttonname
1412 break
1413 # Continue with super function
1414 ActionBase.render(self)
1415
1416
1417 def do_action(self):
1418 """MoinMoin.action.ActionBase: Main dispatcher for the action.
1419 """
1420 # Determine calling parameters.
1421 if is_moinmoin_version_eqless(1,8):
1422 if self.form.get('debug', [u'0'])[0] != u'0':
1423 self.debug = True
1424 else:
1425 if self.form.get('debug', u'0') != u'0':
1426 self.debug = True
1427
1428 # Create a PDF document direct without any user iteraction from default and page settings.
1429 if self.form.has_key('generate'):
1430 self.set_page_values()
1431 self.update_values(useform=False)
1432 return self.do_action_generate()
1433
1434 # Create a PDF document from form settings.
1435 if self.form.has_key('generate_from_form') or self.form.has_key(u'preview'):
1436 self.update_values()
1437 return self.do_action_generate()
1438
1439 # Display a message with instructions.
1440 if self.form.has_key('remember'):
1441 self.update_values()
1442 return self.do_action_remember()
1443
1444 return (True, None)
1445
1446
1447 def do_action_finish(self, success):
1448 """Overwrite default processing in case of success where application/pdf mime document is send instead of default page."""
1449 if self.error:
1450 ActionBase.do_action_finish(self, False)
1451
1452
1453 def update_values(self, useform=True):
1454 """Preset values with they form values or defaults."""
1455 if useform:
1456 pass
1457 for key, default in self.default_values.items():
1458 # Skip settings which can't be modified by form.
1459 if useform and key in ['htmldoc_cmd', 'htmldoc_options', 'extra-dynamiccodeblock-middotchar', 'extra-dynamiccodeblock-breakchar']:
1460 continue
1461 # Modify value only if not already set.
1462 if not key in self.values or useform:
1463 # If the form does not contain the value (e.g. for checkboxes) set the
1464 # default value (e.g. for checkboxes unset by default).
1465 if not key in self.form:
1466 # Special processing for checkboxes in forms. If the key does not exists
1467 # within the form it is not checked.
1468 if key in self.form_checkbox and useform:
1469 self.values[key] = u'unchecked'
1470 elif useform:
1471 # Edit fields are missing if they are empty.
1472 self.values[key] = u''
1473 else:
1474 self.values[key] = default
1475 else:
1476 f = self.form[key]
1477 # Before MoinMoin 1.9 form values are lists where first item contains the data.
1478 if isinstance(f, list):
1479 f = f[0]
1480 self.values[key] = f
1481 # Check if revision is an integer value.
1482 try:
1483 self.values[u'rev'] = int(self.values.get(u'rev', self.request.page.rev))
1484 except:
1485 self.values[u'rev'] = self.request.page.rev
1486 # Check if page revision exists.
1487 (pagefname, realrev, exists) = self.request.page.get_rev(rev=self.values[u'rev'])
1488 if exists:
1489 self.values[u'rev'] = realrev
1490 else:
1491 # Determine latest revision number.
1492 (pagefname, self.values[u'rev'], exists) = self.request.page.get_rev()
1493 # Check valid value range.
1494 try:
1495 self.values[u'browserwidth'] = int(self.values[u'browserwidth'])
1496 if self.values[u'browserwidth'] < 400 or self.values[u'browserwidth'] > 1200:
1497 self.values[u'browserwidth'] = self.default_values[u'browserwidth']
1498 except:
1499 self.values[u'browserwidth'] = self.default_values[u'browserwidth']
1500 # We need a string.
1501 self.values[u'browserwidth'] = u'%d' % self.values[u'browserwidth']
1502
1503
1504 def do_action_generate(self):
1505 """Create PDF document."""
1506 # Generate the HTML page using MoinMoin wiki engine.
1507 html = self.get_html()
1508 if html:
1509 if self.form.has_key('preview'):
1510 self.wrapper_emit_http_headers()
1511 self.request.write(html)
1512 else:
1513 pdfdata = self.html2pdf(html)
1514 if pdfdata:
1515 # Send as application/pdf the generated file by HTMLDOC
1516 self.send_pdf(pdfdata)
1517 return (True, None)
1518
1519 return (False, self.error)
1520
1521
1522 def do_action_remember(self):
1523 """Create a message containing information about how to save the form values for future reuse."""
1524 save = u''
1525 for key, value in self.values.items():
1526 if key in ['user-password', 'owner-password', 'rev', 'debug', 'htmldoc_cmd', 'htmldoc_options']:
1527 continue
1528 if key in self.default_values and value == self.default_values[key]:
1529 continue
1530 save += u'##pdf %s %s' % (key, value,)
1531 if self.debug:
1532 if key in self.default_values:
1533 save += u' <-- default value is "%s" (without quotes)' % self.default_values[key]
1534 else:
1535 save += u' <-- keyword missing in defaults'
1536 save += u'\n'
1537 if save:
1538 msg = self._(u'Add follwing lines at the beginning of your page:') + u'<br/><pre>' + save + u'</pre>'
1539 else:
1540 msg = self._(u'All values correspond to they default. Nothing have to be saved.')
1541
1542 return (True, msg)
1543
1544
1545 def wrapper_emit_http_headers(self, more_headers={}):
1546 if is_moinmoin_version_eqless(1,8):
1547 pre19 = []
1548 for k,v in more_headers.items():
1549 pre19.append("%s: %s" % (k, v))
1550 if is_moinmoin_version_eqless(1,5):
1551 self.request.http_headers(pre19)
1552 else:
1553 self.request.emit_http_headers(pre19)
1554 else:
1555 for k,v in more_headers.items():
1556 self.request.headers.add(k, v)
1557
1558
1559 def send_pdf(self, data):
1560 """Send PDF file to HTTP server as inline (not attachment).
1561 Refer to Page.send_raw() for an example of implementation.
1562 """
1563 filename = self.pagename.replace (u'/', u'-') + u'-v' + str(self.values[u'rev']) + u'.pdf'
1564
1565 # Send HTTP header.
1566 self.wrapper_emit_http_headers({'Content-Type': '%s' % self.contenttype,
1567 'Content-Length': '%d' % len(data),
1568 # TODO: fix the encoding here, plain 8 bit is not allowed
1569 # according to the RFCs There is no solution that is
1570 # compatible to IE except stripping non-ascii chars
1571 'Content-Disposition': 'inline; filename="%s"' % filename.encode(config.charset),
1572 })
1573
1574 # Send binary data.
1575 sio = StringIO.StringIO(data)
1576 shutil.copyfileobj(sio, self.request, 8192)
1577
1578
1579 def get_html(self):
1580 """Generate the HTML body of this page."""
1581 newtitle = None
1582 # Find out what the right title is.
1583 if self.values[u'extra-headingastitle'] == u'checked':
1584 matchheading = re.compile(r'^= (.*) =$')
1585 for line in self.request.page.get_raw_body().split(u'\n'):
1586 result = matchheading.match(line)
1587 if result:
1588 newtitle = result.group(1).strip()
1589 break
1590 # In case where extra-headingastitle is checked but no heading is defined
1591 # at the page, we use the form entry.
1592 if newtitle == None:
1593 newtitle = self.values['titletext']
1594
1595 errmsg = None
1596 html = None
1597 if is_moinmoin_version_eqless(1,5):
1598 # Workarround: We need the pure HTML page without MoinMoin theme
1599 self.request.form['action'] = ['print']
1600 # Workarround: Do not sent HTTP header
1601 self.request.sent_headers = True
1602 html = self.request.redirectedOutput(self.request.page.send_page, request=self.request, do_cache=0)
1603 # Remove HTTP header
1604 i = html.find('<!DOCTYPE ')
1605 if i > 0:
1606 html = html[i:]
1607 # Revert workarround
1608 self.request.sent_headers = False
1609 else:
1610 # Since MoinMoin 1.6
1611 html = self.request.redirectedOutput(self.request.page.send_page, emit_headers=0, print_mode=1, do_cache=0)
1612 # In case where `self.request.page.send_page` is redirected we can not
1613 # emit HTTP headers for the same request twice.
1614 if is_moinmoin_version_eqless(1,8):
1615 if self.request.sent_headers:
1616 errmsg = self._('HTTP header already sent. We are not able to change the Content-Type to application/pdf.')
1617 i = html.find(u'<body ')
1618 if i >= 0:
1619 html = html[:i] + u"""
1620 <meta name="docnumber" content="%s">
1621 <meta name="author" content="%s">
1622 <meta name="copyright" content="%s">
1623 """ % (wikiutil.escape(self.values['extra-titledocnumber']), wikiutil.escape(self.values['extra-titleauthor']), wikiutil.escape(self.values['extra-titlecopyright']),) \
1624 + html[i:]
1625 else:
1626 errmsg = self._(u'Can not find the right place to insert the HTML header.')
1627
1628 if html:
1629 html = self.fixhtmlstr(html)
1630 # Make URLs absolute.
1631 # FIXME: Until MoinMoin is not XHTML compilant we can not use a XML parser
1632 # (e.g. expat) to transform the HTML document. In the meantime we try to
1633 # achive the same with regular expressions subtitution.
1634 base = self.request.getQualifiedURL()
1635 for htmlref in [u'src', u'href']:
1636 reurlref = r'(%s=[\'"])(/[^\'"]*)[\'"]' % (htmlref,)
1637 urlref = re.compile (reurlref, re.I)
1638 for match in urlref.finditer(html):
1639 foundref = match.groups()
1640 html = html.replace (foundref[0] + foundref[1], foundref[0] + base + foundref[1])
1641
1642 # Rename title of the document.
1643 titletext_html = self.fixhtmlstr(wikiutil.escape(newtitle))
1644 html = re.compile(r'<title>[^<]+</title>').sub(u'<title>%s</title>' % titletext_html, html)
1645
1646 if self.values['pageinfo'] == u'unchecked':
1647 # Remove pageinfo by regex. There is no standard way to do that yet.
1648 # Read the comment in ThemeBase.shouldShowPageinfo().
1649 html = re.compile(r'<p[^>]+id="pageinfo".*</p>').sub(u'', html)
1650 html = re.compile(r'<div id="interwiki">.*?</div>').sub(u'', html)
1651
1652 # HTMLDOC workarround: Add borders to tables. HTMLDOC assume border="0" if not defined.
1653 html = re.compile(r'<table').sub(u'<table border="1" cellpadding="2"', html)
1654
1655 # Display line numbers for code blocks without ·.
1656 if self.values[u'extra-dynamiccodeblock'] == u'checked':
1657 if self.values[u'extra-codeblocklinenumbers'] == u'checked':
1658 codeblocknr = re.compile(r'<span[^>]+class="LineNumber">([^<]+)</span>', re.IGNORECASE)
1659 for match in codeblocknr.finditer(html):
1660 newlinenr = u'<font color="%s">%s</font>' % (self.values[u'linkcolor'], match.group(1).replace(u' ', u' '),)
1661 html = html.replace(match.group(0), newlinenr, 1)
1662 else:
1663 html = re.compile(r'<span[^>]+class="LineNumber">[^<]+</span>', re.IGNORECASE).sub(u'', html)
1664
1665 # HTMLDOC: Does not support <span> so we remove them.
1666 for spanmatch in [r'<span[^>]*>', r'</span>']:
1667 html = re.compile(spanmatch).sub(u'', html)
1668
1669 # HTMLDOC: Does not support JavaScript.
1670 html = re.compile(r'<script type="text/javascript".*?</script>', re.IGNORECASE | re.DOTALL).sub(u'', html)
1671
1672 # HTMLDOC: Does not support CSS.
1673 html = re.compile(r'<style type="text/css".*?</style>', re.IGNORECASE | re.DOTALL).sub(u'', html)
1674
1675 # HTMLDOC does not support stylesheets.
1676 html = re.compile(r'<link rel="stylesheet".*?>', re.IGNORECASE | re.DOTALL).sub(u'', html)
1677
1678 # Remove page location added by &action=print.
1679 html = re.compile(r'<ul id="pagelocation">.*?</ul>', re.IGNORECASE | re.DOTALL).sub(u'', html)
1680
1681 # HTMLDOC workarround: There is no CSS support in HTMLDOC.
1682 tablecolor = re.compile(r'<td.*?background-color: (#......).*?>')
1683 for match in tablecolor.finditer(html):
1684 html = html.replace(match.group(0), u'<td bgcolor="%s">' % match.group(1), 1)
1685
1686 # HTMLDOC workarround: Handle <pre> sections over page boundries.
1687 if self.values[u'extra-dynamiccodeblock'] == u'checked':
1688 multiplespaces = re.compile(r'( {2,})')
1689 # Which character should be used to fill out spaces.
1690 if self.values[u'extra-dynamiccodeblock-middot'] == u'checked':
1691 fillchar = self.values[u'extra-dynamiccodeblock-middotchar']
1692 else:
1693 fillchar = u' '
1694 for regexstr in [r'(<pre>)(.*?)(</pre>)', r'(<div class="codearea"[^>]*>.*?<pre[^>]*>)(.*?)(</pre>.*?</div>)']:
1695 codesections = re.compile(regexstr, re.IGNORECASE | re.DOTALL)
1696 for match in codesections.finditer(html):
1697 foundref = match.groups()
1698 presection = foundref[1]
1699 # Search for multiple spaces and replace them by `fillchar` except the last space.
1700 for spaces in multiplespaces.finditer(presection):
1701 newspaces = fillchar * (len(spaces.group(1)) - 1) + u' '
1702 presection = presection.replace (spaces.group(1), newspaces, 1)
1703 # Go through lines and add a ¶ sign at the end of eatch line.
1704 newprelines = []
1705 prelines = presection.split(u"\n")
1706 if len(prelines) > 1:
1707 breakchar = self.values[u'extra-dynamiccodeblock-break'] == u'checked' and self.values[u'extra-dynamiccodeblock-breakchar'] or u''
1708 for preline in prelines:
1709 preline = preline + breakchar + u'<br />'
1710 newprelines.append(preline)
1711 else:
1712 newprelines = prelines
1713 # Create a table arround an multi-line block.
1714 if newprelines:
1715 tablestart = u'<table border="1" bgcolor="#F3F5F7" cellpadding="5"><tr><td>'
1716 tableend = u'</td></tr></table><br />'
1717 else:
1718 newprelines.append(preline)
1719 tablestart = u''
1720 tableend = u''
1721 # Replace the <pre> block with new dynamic text.
1722 html = html.replace(u''.join(foundref), u'%s<font face="Courier,Monospace">%s</font>%s' % (tablestart, u"\n".join(newprelines), tableend,), 1)
1723 # Do not suppress error messages.
1724 if errmsg:
1725 html = u'<pre>' + errmsg + '</pre>' + html
1726 else:
1727 self.error_msg(self._(u'Could not redirect HTML output for further processing:') + errmsg)
1728 return html
1729
1730
1731 def make_isolang (self, language):
1732 return language + u'_' + language.upper()
1733
1734
1735 def html2pdf(self, html):
1736 """Create a PDF document based on the current parameters."""
1737 # Set environment variables for HTMLDOC
1738 os.environ['LANG'] = self.values[u'language']
1739 os.environ['HTMLDOC_NOCGI'] = '1'
1740 # Determine UID to access ACL protected sites too (mandatory to download attached images).
1741 htmldocopts = [self.default_values['htmldoc_cmd'], "--cookies", "MOIN_ID=" + self.request.user.id, u'--no-duplex'] + self.default_values['htmldoc_options']
1742
1743 # For MoinMoin 1.5 the MOIN_SESSION cookie is not required. For MoinMoin 1.6 and newer MOIN_SESSION is mandatory
1744 # to get access to ACL protected attachments. The ongoing session id will be used for HTMLDOC request.
1745 cookie = ""
1746 if hasattr(self.request, 'cookie') and self.request.cookie.get("MOIN_SESSION", False):
1747 cookie = "MOIN_SESSION=" + self.request.cookie.get("MOIN_SESSION").value
1748 # For the CGI request method there is no `headers` attribute for an site where
1749 # no cookies exists on browser site and the user is anonymous.
1750 elif hasattr(self.request, 'headers') and hasattr(self.request.headers, 'cookie'):
1751 # Backward compatibility before MoinMoin 1.7
1752 for cookie in self.request.headers.get('cookie', ';').split(';'):
1753 if cookie[:13] == "MOIN_SESSION=":
1754 break
1755
1756 # If anonymous sessions are allowed the MOIN_SESSION is not required. Otherwise we stop with an error.
1757 if cookie[:13] == "MOIN_SESSION=":
1758 htmldocopts += ["--cookies", cookie]
1759
1760 for key in [u'header', u'footer', u'tocheader', u'tocfooter']:
1761 self.values[key] = self.values.get(key + u'left', u'.') + self.values.get(key + u'middle', u'.') + self.values.get(key + u'right', u'.')
1762
1763 permissions = []
1764 for opt, value in self.values.items():
1765 # Skip alle non-HTMLDOC configuration parameters.
1766 if opt in ['language', 'debug', 'rev', 'titletext', 'pageinfo', 'htmldoc_cmd', 'htmldoc_options'] or opt[:6] == u'extra-':
1767 continue
1768
1769 # Skip options without values.
1770 value = value.strip()
1771 if not value:
1772 continue
1773
1774 # Skip options for header/footer configuration which differenciate between position (e.g. footerright or tocheadermiddle)
1775 if opt[:6] in [u'header', u'footer'] and opt[6:] or opt[:9] in [u'tocheader', u'tocfooter'] and opt[9:]:
1776 continue
1777
1778 if u'proxy' in self.default_values:
1779 htmldocopts += [u'--proxy', self.default_values[u'proxy']]
1780
1781 if opt == u'titlefileimage':
1782 # Check if we have a --titlefile or --titleimage option.
1783 lower_value = value.lower()
1784 dotpos = lower_value.rfind(u'.')
1785 if lower_value[dotpos:] in [u'.gif', u'.jpg', u'.jpeg', u'.png']:
1786 opt = u'titleimage'
1787 else:
1788 opt = u'titlefile'
1789 value = attachment_fsname(value, self.request.page, self.request)
1790 elif opt == u'bodyimage':
1791 value = attachment_fsname(value, self.request.page, self.request)
1792
1793 if opt in [u'style']:
1794 htmldocopts += [u'--' + value]
1795 elif opt in self.form_checkbox:
1796 if value == u'checked':
1797 if opt[:10] == u'permission':
1798 permissions += [opt[10:]]
1799 # Reverse meaning of 'no-' options.
1800 elif opt[:3] != u'no-':
1801 htmldocopts += [u'--' + opt]
1802 elif opt[:3] == u'no-':
1803 htmldocopts += [u'--' + opt]
1804 elif opt[:6] == u'margin' and value:
1805 htmldocopts += [u'--' + opt[6:], value]
1806 elif opt in [u'jpeg']:
1807 htmldocopts += [u'--' + opt + '=' + value]
1808 elif value:
1809 htmldocopts += [u'--' + opt, value]
1810 if permissions:
1811 htmldocopts += [u'--permission', u','.join (permissions)]
1812 htmldocopts += [u'-']
1813 # Do not forget to escape all spaces!
1814 eschtmldocopts = [shell_quote(arg) for arg in htmldocopts]
1815 cmdstr = u' '.join(eschtmldocopts)
1816 errmsg = None
1817
1818 pdf = None
1819 os.environ['HTMLDOC_NOCGI'] = '1'
1820 if self.debug:
1821 self.wrapper_emit_http_headers()
1822 errmsg = self._(u'HTMLDOC command:') + u'<pre>' + wikiutil.escape(cmdstr) + u'</pre>'
1823 cmdstr = self.default_values['htmldoc_cmd'] + u' --help'
1824 (htmldoc_help, htmldoc_err) = pipeCommand(cmdstr)
1825 errmsg += u'<p>Execute <tt>%s</tt><br /><pre>%s</pre></p>' % (wikiutil.escape(cmdstr), wikiutil.escape(htmldoc_help),)
1826 if 'env' in self.request.__dict__:
1827 reqenv = u'%s' % wikiutil.escape(self.request.env)
1828 else:
1829 reqenv = u'None'
1830 errmsg += u'<p>Python release %s<br />MoinMoin release <tt>%s</tt><br />CreatePdfDocument release <tt>%s</tt><br />self.request = <tt>%s</tt><br />self.request.env = <tt>%s</tt><br />os.environ = <tt>%s</tt></p>' % (wikiutil.escape(sys.version), wikiutil.escape(moinmoin_release), wikiutil.escape(__version__), wikiutil.escape(type(self.request)), wikiutil.escape(reqenv), wikiutil.escape(u"%s" % os.environ),)
1831 else:
1832 (pdf, htmldoc_err) = pipeCommand(cmdstr, html)
1833
1834 # Check for error message on STDOUT.
1835 if pdf[:8] == u'HTMLDOC ':
1836 htmldoc_err += pdf
1837 pdf = None
1838 # The PAGES: and BYTES: indicate a successful generation of the PDF document.
1839 if htmldoc_err.find('PAGES: ') != -1 and htmldoc_err.find('BYTES: ') != -1:
1840 htmldoc_err = None
1841 else:
1842 pdf = None
1843 if htmldoc_err:
1844 errmsg = self._(u'Command:') + u'<pre>' + wikiutil.escape(cmdstr) + u'</pre>' + self._('returned:') + u'<pre>' + \
1845 wikiutil.escape(htmldoc_err).replace(u'\n', u'<br />') + u'</pre>'
1846
1847 # As it is difficult to get the htmldoc return code, we check for
1848 # error by checking the produced pdf length
1849 if not pdf and errmsg:
1850 if self.debug:
1851 self.request.write(u'<html><body>%s</body></hftml>' % errmsg)
1852 self.request.write(html)
1853 else:
1854 self.error_msg(errmsg)
1855 elif pdf[:4] != '%PDF':
1856 self.error_msg(self._(u'Invalid PDF document generated.') + u' ' + self._(u'Length: %(size)d Byte(s)') % {u'size': u'%d' % len(pdf)} + (len(pdf) or u'' and u' <pre>' + wikiutil.escape(pdf[:80]) + u'</pre>'))
1857 pdf = None
1858
1859 return pdf
1860
1861
1862 def _set_fontsize(self, key, min, max, smallstep):
1863 self.valid_options[key] = {}
1864 for fontsize_big in range(min, max):
1865 for fontsize_step in range(0, 10, smallstep):
1866 fontsize = u'%d.%d' % (fontsize_big, fontsize_step,)
1867 self.valid_options[key][fontsize] = self._(fontsize)
1868 self.valid_options[key][u'%d.0' % max] = self._(u'%d.0' % max)
1869
1870
1871 def execute(pagename, request):
1872 CreatePdfDocument(pagename, request).render()
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.