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