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