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

Attached Files

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