Attachment 'CreatePdfDocument2_3_5.py'

Download

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

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.
  • [get | view] (2011-04-14 07:28:03, 4.7 KB) [[attachment:CreateNewPage.py]]
  • [get | view] (2011-04-14 07:26:24, 4.2 KB) [[attachment:CreateNewPage1.6.py]]
  • [get | view] (2006-09-10 21:29:29, 40.4 KB) [[attachment:CreatePdfDocument2_0_3.py]]
  • [get | view] (2006-09-12 06:05:06, 40.5 KB) [[attachment:CreatePdfDocument2_0_4.py]]
  • [get | view] (2006-09-12 12:00:09, 40.6 KB) [[attachment:CreatePdfDocument2_0_5.py]]
  • [get | view] (2006-11-14 21:56:11, 43.5 KB) [[attachment:CreatePdfDocument2_0_6.py]]
  • [get | view] (2006-11-15 17:00:47, 43.8 KB) [[attachment:CreatePdfDocument2_0_7.py]]
  • [get | view] (2006-11-16 22:06:18, 43.8 KB) [[attachment:CreatePdfDocument2_0_8.py]]
  • [get | view] (2006-12-17 15:54:21, 43.6 KB) [[attachment:CreatePdfDocument2_0_9.py]]
  • [get | view] (2007-08-20 09:10:23, 67.2 KB) [[attachment:CreatePdfDocument2_1_0.py]]
  • [get | view] (2007-08-21 07:39:49, 67.1 KB) [[attachment:CreatePdfDocument2_1_1.py]]
  • [get | view] (2007-09-11 19:16:37, 67.3 KB) [[attachment:CreatePdfDocument2_1_2.py]]
  • [get | view] (2007-09-18 20:17:58, 68.1 KB) [[attachment:CreatePdfDocument2_1_3.py]]
  • [get | view] (2007-09-21 13:32:54, 71.1 KB) [[attachment:CreatePdfDocument2_1_4.py]]
  • [get | view] (2007-09-23 20:56:30, 73.4 KB) [[attachment:CreatePdfDocument2_1_5.py]]
  • [get | view] (2007-09-25 20:54:48, 74.5 KB) [[attachment:CreatePdfDocument2_2_0.py]]
  • [get | view] (2008-06-23 21:08:49, 77.0 KB) [[attachment:CreatePdfDocument2_3_0.py]]
  • [get | view] (2008-06-26 19:25:07, 81.0 KB) [[attachment:CreatePdfDocument2_3_1.py]]
  • [get | view] (2008-07-06 05:50:38, 83.1 KB) [[attachment:CreatePdfDocument2_3_2.py]]
  • [get | view] (2008-07-09 17:42:02, 83.3 KB) [[attachment:CreatePdfDocument2_3_3.py]]
  • [get | view] (2008-09-07 11:11:01, 83.5 KB) [[attachment:CreatePdfDocument2_3_4.py]]
  • [get | view] (2009-01-11 15:53:09, 84.3 KB) [[attachment:CreatePdfDocument2_3_5.py]]
  • [get | view] (2009-02-16 06:52:06, 84.2 KB) [[attachment:CreatePdfDocument2_3_6.py]]
  • [get | view] (2010-01-29 11:53:21, 82.8 KB) [[attachment:CreatePdfDocument2_4_0.py]]
  • [get | view] (2010-01-31 14:10:03, 84.6 KB) [[attachment:CreatePdfDocument2_4_1.py]]
  • [get | view] (2010-09-18 16:23:23, 85.6 KB) [[attachment:CreatePdfDocument2_4_2.py]]
  • [get | view] (2006-06-16 20:56:53, 4.9 KB) [[attachment:FlashManager.py-1.5.3-1]]
  • [get | view] (2003-12-07 18:15:53, 3.9 KB) [[attachment:HTML2MoinMoin.py]]
  • [get | view] (2005-10-16 08:24:35, 4.9 KB) [[attachment:HelpOn-1.3.5-4.py]]
  • [get | view] (2006-02-03 19:21:04, 4.9 KB) [[attachment:HelpOn-1.5.1-5.py]]
  • [get | view] (2006-07-04 10:45:22, 4.8 KB) [[attachment:HelpOn-1.5.4-6.py]]
  • [get | view] (2006-07-04 22:39:14, 4.8 KB) [[attachment:HelpOn-1.6.0-7.py]]
  • [get | view] (2006-07-06 13:50:17, 4.0 KB) [[attachment:HelpOn-1.6.0-8.py]]
  • [get | view] (2008-01-10 17:43:24, 4.8 KB) [[attachment:HelpOn-1.6.0-9.py]]
  • [get | view] (2008-08-19 14:44:54, 5.0 KB) [[attachment:HelpOn-1.7.1-10.py]]
  • [get | view] (2005-02-20 18:28:34, 10.8 KB) [[attachment:IRSS.py]]
  • [get | view] (2005-03-09 22:46:23, 2.9 KB) [[attachment:ImportHtml-1.2.py]]
  • [get | view] (2003-12-07 18:15:53, 2.8 KB) [[attachment:ImportHtml.py]]
  • [get | view] (2003-12-07 18:15:53, 1.8 KB) [[attachment:IrcChat.py]]
  • [get | view] (2008-06-09 11:27:20, 4.4 KB) [[attachment:MoinCrypt.py]]
  • [get | view] (2010-11-29 12:08:27, 7.5 KB) [[attachment:PageActions.py]]
  • [get | view] (2006-08-07 15:12:19, 0.5 KB) [[attachment:PermanentLink.py]]
  • [get | view] (2003-12-07 18:15:53, 6.3 KB) [[attachment:PhoneDial.py]]
  • [get | view] (2005-04-17 14:21:47, 3.6 KB) [[attachment:RecommendPage-1.3.4-1.py]]
  • [get | view] (2005-04-19 18:21:52, 5.5 KB) [[attachment:RecommendPage-1.3.4-2.py]]
  • [get | view] (2005-05-02 19:53:09, 5.6 KB) [[attachment:RecommendPage-1.3.4-3.py]]
  • [get | view] (2005-09-03 07:33:35, 6.3 KB) [[attachment:RecommendPage-1.3.4-4.py]]
  • [get | view] (2005-09-05 17:44:03, 6.9 KB) [[attachment:RecommendPage-1.3.5-5.py]]
  • [get | view] (2005-09-07 16:42:26, 7.5 KB) [[attachment:RecommendPage-1.3.5-6.py]]
  • [get | view] (2005-09-08 16:06:28, 7.7 KB) [[attachment:RecommendPage-1.3.5-7.py]]
  • [get | view] (2005-11-01 11:31:51, 9.0 KB) [[attachment:RecommendPage-1.3.5-8.py]]
  • [get | view] (2006-02-03 19:40:51, 8.3 KB) [[attachment:RecommendPage-1.5.1-9.py]]
  • [get | view] (2008-01-11 09:14:35, 6.8 KB) [[attachment:RecommendPage-1.6.0-10.py]]
  • [get | view] (2008-08-19 14:44:59, 6.9 KB) [[attachment:RecommendPage-1.7.1-11.py]]
  • [get | view] (2008-06-09 11:27:40, 1.7 KB) [[attachment:ShowActions.py]]
  • [get | view] (2008-06-09 10:34:02, 5.3 KB) [[attachment:ShowDecrypted.py]]
  • [get | view] (2005-03-30 21:17:28, 7.7 KB) [[attachment:Slideshow.py]]
  • [get | view] (2004-02-02 20:48:31, 2.0 KB) [[attachment:SubscribeUser.py]]
  • [get | view] (2007-01-26 17:08:30, 2.2 KB) [[attachment:Subscribers-1.6.0.py]]
  • [get | view] (2003-12-07 18:15:53, 1.8 KB) [[attachment:Subscribers.py]]
  • [get | view] (2006-03-18 23:16:51, 0.8 KB) [[attachment:UserPreferences.py]]
  • [get | view] (2004-01-05 09:56:25, 8.1 KB) [[attachment:VisualSiteMap.py]]
  • [get | view] (2015-08-30 21:04:23, 11.1 KB) [[attachment:VisualSiteMap_1.10.py]]
  • [get | view] (2004-10-08 10:59:16, 9.3 KB) [[attachment:VisualSiteMap_1.2.py]]
  • [get | view] (2005-03-16 01:30:09, 9.8 KB) [[attachment:VisualSiteMap_1.3.py]]
  • [get | view] (2014-08-19 01:34:10, 10.8 KB) [[attachment:VisualSiteMap_1.9.py]]
  • [get | view] (2007-08-18 18:52:55, 1.0 KB) [[attachment:backlink.py]]
  • [get | view] (2007-03-15 05:53:49, 23.5 KB) [[attachment:findandreplace0.1Beta.py]]
  • [get | view] (2005-03-27 20:32:10, 3.6 KB) [[attachment:gallery2image-1.3.3-1.py]]
  • [get | view] (2005-08-03 20:14:56, 4.0 KB) [[attachment:gallery2image-1.3.3-2.py]]
  • [get | view] (2005-11-13 18:10:26, 20.7 KB) [[attachment:gallery2image-1.3.5-10.py]]
  • [get | view] (2005-11-25 22:03:50, 20.8 KB) [[attachment:gallery2image-1.3.5-11.py]]
  • [get | view] (2005-08-08 17:23:43, 8.4 KB) [[attachment:gallery2image-1.3.5-4.py]]
  • [get | view] (2005-08-13 15:15:45, 13.7 KB) [[attachment:gallery2image-1.3.5-5.py]]
  • [get | view] (2005-08-31 22:05:22, 15.5 KB) [[attachment:gallery2image-1.3.5-6.py]]
  • [get | view] (2005-10-29 20:23:50, 15.9 KB) [[attachment:gallery2image-1.3.5-8.py]]
  • [get | view] (2005-11-01 11:31:24, 17.6 KB) [[attachment:gallery2image-1.3.5-9.py]]
  • [get | view] (2006-01-27 20:52:32, 20.9 KB) [[attachment:gallery2image-1.5.1-12.py]]
  • [get | view] (2006-08-06 09:01:01, 22.1 KB) [[attachment:gallery2image-1.5.4-13.py]]
  • [get | view] (2006-08-11 18:21:40, 22.2 KB) [[attachment:gallery2image-1.5.4-14.py]]
  • [get | view] (2006-11-16 20:23:27, 22.6 KB) [[attachment:gallery2image-1.5.6-16.py]]
  • [get | view] (2006-08-11 18:30:22, 22.2 KB) [[attachment:gallery2image-1.6.0-15.py]]
  • [get | view] (2008-02-06 10:13:45, 22.3 KB) [[attachment:gallery2image-1.6.0-16.py]]
  • [get | view] (2008-05-20 15:51:09, 22.4 KB) [[attachment:gallery2image-1.6.3-17.py]]
  • [get | view] (2006-09-06 06:19:48, 1.3 KB) [[attachment:getmmap.py]]
  • [get | view] (2004-07-18 09:48:00, 1.5 KB) [[attachment:localnames.py]]
  • [get | view] (2005-03-25 15:02:31, 2.6 KB) [[attachment:newpageonly.py]]
  • [get | view] (2005-03-30 09:02:00, 3.5 KB) [[attachment:newpageonly_20050330.py]]
  • [get | view] (2006-06-06 19:12:27, 9.7 KB) [[attachment:pdf.py]]
  • [get | view] (2006-08-30 10:51:51, 36.0 KB) [[attachment:pdf2_0_0.py]]
  • [get | view] (2006-08-30 13:57:36, 36.5 KB) [[attachment:pdf2_0_1.py]]
  • [get | view] (2006-02-04 04:25:29, 1.0 KB) [[attachment:sisterindex.py]]
  • [get | view] (2004-10-28 07:33:10, 0.7 KB) [[attachment:xml.py]]
 All files | Selected Files: delete move to page copy to page

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