Attachment 'CreatePdfDocument2_3_4.py'

Download

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