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

You are not allowed to attach a file to this page.