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