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