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