Attachment 'CreatePdfDocument2_3_4+p1.py'

Download

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

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.
  • [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.