Attachment 'CreatePdfDocument2_0_4.py'

Download

   1 # -*- coding: iso-8859-1 -*-
   2 """
   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     @copyright: (C) 2006  Pascal Bauermeister
  17     @copyright: (C) 2006  Raphael Bossek <raphael.bossek@solutions4linux.de>
  18     @license: GNU GPL, see COPYING for details
  19 
  20     2006-09-12  RaphaelBossek
  21     * Release v2.0.4
  22     * Fixed RedirectOUtputRequest class where function was redifined
  23       to boolean value.
  24 
  25     2006-09-06  RaphaelBossek
  26     * Release v2.0.3
  27     * Fixed FastCGI support by removing stdout redirect output code. The
  28       same functionality will be done by the RedirectOutputRequest class.
  29     * Renamed to CreatePdfDocument (for better translation possibilities).
  30     * Fixed encoding of title page.
  31     * Added charset set option.
  32     * Fixed waiting for HTMLDOC.
  33 
  34     2006-09-02  RaphaelBossek
  35     * Release v2.0.2
  36     * Added createpdfdocument_validoptions and createpdfdocument_defaultvalues
  37       configuration parameter support. You are not able to preset available
  38       options and which defaults are used in your configuration file.
  39 
  40     2006-08-30  RaphaelBossek
  41     * Release v2.0.1
  42     * Fixed issue with page revision number forwarding (traceback).
  43 
  44     2006-08-30  RaphaelBossek
  45     * Release v2.0.0
  46     * Feature enchanced and bug fixed version.
  47 
  48     2006-05-26  PascalBauermeister
  49     * Release v1.0.2
  50     * Relative image URLs turned absolute was bogus. It is less bogus now.
  51 
  52     2006-05-26  PascalBauermeister
  53     * Release v1.0.1
  54     * Set env var HTMLDOC_NOCGI to solve CGI issue
  55 
  56     2006-05-24  PascalBauermeister
  57     * Initial release v1.0.0
  58 """
  59 
  60 import os, stat
  61 import re, copy, sys, StringIO, tempfile, traceback
  62 import shutil, StringIO
  63 from MoinMoin import config, util, wikiutil, packages, error
  64 from MoinMoin.Page import Page
  65 from MoinMoin.util import MoinMoinNoFooter, filesys
  66 from MoinMoin.request import RequestModPy, RequestBase
  67 from MoinMoin.parser import wiki
  68 from MoinMoin.widget.dialog import Dialog
  69 
  70 class ActionError(Exception):
  71     pass
  72 
  73 class RedirectOutputRequest(RequestBase):
  74     """Redirect output to string without HTTP headers."""
  75     def __init__ (self, req):
  76         self.path_info = req.path_info.encode(config.charset)
  77         self.request_uri = req.request_uri
  78         self.url = req.url
  79         self.http_user_agent = req.http_user_agent
  80         self.query_string = req.query_string
  81         #self.request_method = req.request_method
  82         self.request_method = None
  83         self.saved_cookie = req.saved_cookie
  84         self.remote_addr = req.remote_addr
  85 
  86         # RequestModPy
  87         if getattr (req, u'script_name', None):
  88             self.script_name = req.script_name
  89 
  90         self.req = req
  91         RequestBase.__init__(self)
  92 
  93     def run(self):
  94         self.output_string = u''
  95         self.error_string = u''
  96         self.sent_headers = False
  97         self.failed = 0
  98         RequestBase.run(self)
  99         return (self.fixhtmlstr(self.output_string), self.error_string)
 100 
 101     def fixhtmlstr (self, str):
 102         """Convert utf-8 encoded multi-byte sequences into &#XXXX; format."""
 103         htmlstr = u''
 104         for c in str:
 105             if ord(c) >= 128:
 106                 htmlstr = htmlstr + '&#%d;' % ord(c)
 107             else:
 108                 htmlstr = htmlstr + c
 109         return htmlstr
 110 
 111     def http_headers (self, more_headers=[]):
 112         self.sent_headers = True
 113 
 114     def fail (self, err):
 115         RequestBase.fail (self, err)
 116         if not self.error_string:
 117             self.error_string = str(err)
 118 
 119     def write (self, *data):
 120         if self.sent_headers:
 121             if self.failed:
 122                 self.error_string += data[0]
 123             else:
 124                 self.output_string += data[0]
 125 
 126     def flush(self):
 127         pass
 128 
 129 class CreatePdfDocument:
 130     """Implementation of the PDF document generator."""
 131 
 132     version = u'2.0.4'
 133 
 134     def __init__(self):
 135         self.action_name = self.__class__.__name__
 136         self.pagename = None
 137         self.request = None
 138         self._ = None
 139         self.debug = False
 140         self.msg = None
 141         self.errormsgsent = False
 142         self.default_values = {
 143             'style': u'webpage',
 144             'format': u'pdf13',
 145             'linkstyle': u'underline',
 146             'headerleft': u't',
 147             'headermiddle': u'.',
 148             'headerright': u'D',
 149             'footerleft': u'.',
 150             'footermiddle': u'/',
 151             'footerright': u'.',
 152             'tocheaderleft': u'.',
 153             'tocheadermiddle': u't',
 154             'tocheaderright': u'.',
 155             'tocfooterleft': u'.',
 156             'tocfootermiddle': u'.',
 157             'tocfooterright': u'i',
 158             'linkcolor': u'0000E0',
 159             'size': u'legal',
 160             'user-password': u'',
 161             'owner-password': u'',
 162             'toclevels': u'3',
 163             'grayscale': u'unchecked',
 164             'title': u'checked',
 165             'duplex': u'unchecked',
 166             'landscape': u'unchecked',
 167             'usersize': u'',
 168             'margintop': u'0.50in',
 169             'marginbottom': u'0.50in',
 170             'marginleft': u'1.00in',
 171             'marginright': u'0.50in',
 172             'no-toc': u'checked',
 173             'no-links': u'checked',
 174             'firstpage': u'p1',
 175             'jpeg': u'0',
 176             'compression': u'0',
 177             'pagemode': u'outline',
 178             'pagelayout': u'single',
 179             'firstpage': u'c1',
 180             'numbered': u'checked',
 181             'encryption': u'unchecked',
 182             'permissioncopy': u'checked',
 183             'permissionprint': u'checked',
 184             'permissionannotate': u'checked',
 185             'permissionmodify': u'checked',
 186             'charset': u'iso-8859-1',
 187             'debug': u'',
 188             'rev': 0,
 189         }
 190         # We have to know which values are checkboxes within the form. If a key does
 191         # not exists wihtin the form the corresponding checkbox is not checked.
 192         self.form_checkbox = []
 193         for key, value in self.default_values.items():
 194             if value in [u'checked', u'unchecked']:
 195                 self.form_checkbox += [key]
 196         self.contenttype = u'application/pdf'
 197 
 198     def error_msg (self, msg):
 199         """Display error message."""
 200         if not self.errormsgsent:
 201             Page (self.request, self.pagename).send_page (self.request, msg=msg)
 202             self.errormsgsent = True
 203 
 204     def set_page_values(self):
 205         """Scan raw page for additional information relating PDF generation."""
 206         #pdflines = False
 207         for line in self.request.page.get_raw_body().split(u'\n'):
 208             if line[:6] == u'##pdf ':
 209                 cols = line[6:].split()
 210                 # Only accept known values/settings.
 211                 if len(cols) > 1 and cols[0] in self.default_values:
 212                     self.values[cols[0]] = u' '.join(cols[1:])
 213                     #pdflines = True
 214                 continue
 215             # Stop parsing with first non-pdf line (if detected at least one).
 216             #elif pdflines and not line:
 217             #    break
 218 
 219     def set_page_default_values(self):
 220         # We are not able to recognise if this string is part of a verbatim area.
 221         matchtoclvl = re.compile(r'^\[\[TableOfContents\(\s*(\d+)\s*\)\]\]')
 222         matchtoc = re.compile(r'^\[\[TableOfContents\(*\)*\]\]')
 223         toc = False
 224         for line in self.request.page.get_raw_body().split(u'\n'):
 225             if line[:10] == u'#language ' and not u'language' in self.values:
 226                 lang = self.make_isolang(line[10:])
 227                 if lang:
 228                     self.default_values[u'language'] = lang
 229             elif not u'toclevels' in self.values and not toc:
 230                 result = matchtoclvl.match(line)
 231                 if result:
 232                     toclevels = int(result.group(1).strip())
 233                     if toclevels > 4:
 234                         toclevels = 4
 235                     self.default_values[u'toclevels'] = str(toclevels)
 236                     toc = True
 237                 elif matchtoc.match(line):
 238                     toc = True
 239         # We assume if table-of-contents is used we intent to generate a book.
 240         if toc:
 241             self.default_values[u'style'] = u'book'
 242         else:
 243             # Do not generate a table of contents page.
 244             self.default_values[u'no-toc'] = u'unchecked'
 245  
 246     def escape (self, str):
 247         return str.replace (u'&', u'&amp;').replace (u'<', u'&lt;').replace (u'>', u'&gt;')
 248 
 249     def _select (self, name, description=None):
 250         """Helper function to create a selection control."""
 251         str = u'<select name="%s" size="1">' % (name,)
 252         if not description:
 253             description = self.valid_options[name]
 254         keys = description.keys()
 255         keys.sort()
 256         for value in keys:
 257             if value == self.values[name]:
 258                 selected = u'selected'
 259             else:
 260                 selected = u''
 261             str += u'<option value="%s" %s>%s</option>' % (value, selected, description[value],)
 262         str += u'</select>'
 263         return str
 264 
 265     def _chooseformat (self, name):
 266         """Helper function to create left/middle/right selection controls."""
 267         str = u"""    <tr>
 268         <td class="label"><label>%s&nbsp;:</label></td>
 269         <td><table>
 270                 <tr>
 271                     <td>%s&nbsp;:</td>
 272                     <td>%s</td>
 273                 </tr>
 274                 <tr>
 275                     <td>%s&nbsp;:</td>
 276                     <td>%s</td>
 277                 </tr>
 278                 <tr>
 279                     <td>%s&nbsp;:</td>
 280                     <td>%s</td>
 281                 </tr>
 282             </table>
 283         </td>
 284         <td>%s</td>
 285     </tr>""" % (self.fields[u'label_' + name],
 286               self.fields[u'label_left'], self._select(name + u'left', self.valid_options[u'tocformats']),
 287               self.fields[u'label_middle'], self._select(name + u'middle', self.valid_options[u'tocformats']),
 288               self.fields[u'label_right'], self._select(name + u'right', self.valid_options[u'tocformats']),
 289               self.fields[u'help_' + name],)
 290         return str
 291 
 292     def makeform (self, errormsg=u''):
 293         self.fields = {
 294             'error': errormsg,
 295             'pagename': wikiutil.escape(self.pagename),
 296             'action': self.action_name,
 297             'seperator': u'<tr><td colspan="3"><hr/></td></tr>',
 298             'space': u'<tr><td colspan="3">&nbsp;</td></tr>',
 299 
 300             'label_input': self._(u'Input'),
 301             'label_output': self._(u'Output'),
 302             'label_page': self._(u'Page'),
 303             'label_tableofcontents': self._(u'Contents'),
 304             'label_pdf': self._(u'PDF'),
 305             'label_security': self._(u'Security'),
 306 
 307             'label_choose_style': self._(u'Choose style'),
 308             'help_choose_style': self._(u'book: Create a structured PDF document with headings, chapters, etc.') + u'<br/>' + \
 309                                  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/>' + \
 310                                  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.'),
 311 
 312             'label_format': self._(u'Output format'),
 313             'help_format': self._(u'Specifies the output format.'),
 314 
 315             'label_outputoptions': self._(u'Output options'),
 316             'label_grayscale': self._(u'Grayscale document'),
 317             'label_title': self._(u'Title page'),
 318             'label_jpeg': self._(u'JPEG big images'),
 319             'label_compression': self._(u'Compression'),
 320 
 321             'label_no-toc': self._(u'Generate a table of contents'),
 322             'help_no-toc': self._(u''),
 323 
 324             'label_toclevels': self._(u'Limit the number of levels in the table-of-contents'),
 325             'help_toclevels': self._(u'Sets the number of levels in the table-of-contents.') + u' ' + self._(u'Empty for unlimited levels.'),
 326 
 327             'label_numbered': self._(u'Numbered headings'),
 328             'help_numbered': self._(u'Check to number all of the headings in the document.'),
 329 
 330             'label_toctitle': self._(u'Table-of-contents title'),
 331             'help_toctitle': self._(u'Sets the title for the table-of-contents.') + u' ' + self._(u'Empty for default title.'),
 332 
 333             'label_left': self._(u'Left'),
 334             'label_middle': self._(u'Middle'),
 335             'label_right': self._(u'Right'),
 336 
 337             'label_tocheader': self._(u'Header of table-of-contantes page'),
 338             'help_tocheader': self._(u'Sets the page header to use on table-of-contents pages.'),
 339 
 340             'label_tocfooter': self._(u'Footer of table-of-contantes page'),
 341             'help_tocfooter': self._(u'Sets the page footer to use on table-of-contents pages.'),
 342 
 343             'label_header': self._(u'Page header'),
 344             'help_header': self._(u'Sets the page header to use on body pages.'),
 345 
 346             'label_footer': self._(u'Page footer'),
 347             'help_footer': self._(u'Sets the page footer to use on body pages.'),
 348 
 349             'label_no-links': self._(u'Create HTTP links'),
 350             'help_no-links': self._(u'Enables generation of links in PDF files.'),
 351             
 352             'label_linkstyle': self._(u'Style of HTTP links'),
 353             'help_linkstyle': self._(u''),
 354 
 355             'label_linkcolor': self._(u'HTTP links color'),
 356             'help_linkcolor': self._(u'Sets the color of links.'),
 357 
 358             'label_duplex': self._(u'2-Sided'),
 359             'help_duplex': self._(u'Specifies that the output should be formatted for double-sided printing.'),
 360 
 361             'label_landscape': self._(u'Landscape'),
 362 
 363             'label_choose_size': self._(u'Choose page size'),
 364             'help_choose_size': self._(u'Choose one of the predefined standard sizes or select user defined.'),
 365 
 366             'label_usersize': self._(u'User defined page size'),
 367             '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).'),
 368 
 369             'label_margin': self._(u'User defined margin'),
 370             'label_margintop': self._(u'Top'),
 371             'label_marginbottom': self._(u'Bottom'),
 372             'label_marginleft': self._(u'Left'),
 373             'label_marginright': self._(u'Right'),
 374             '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.'),
 375 
 376             'label_pagemode': self._(u'Page mode'),
 377             '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.'),
 378 
 379             'label_pagelayout': self._(u'Page layout'),
 380             '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.'),
 381 
 382             'label_firstpage': self._(u'First page'),
 383             'help_firstpage': self._(u'Choose the initial page that will be shown.'),
 384 
 385             'label_encryption': self._(u'Encryption'),
 386             'help_encryptin': self._(u'Enables encryption and security features for PDF output.'),
 387 
 388             'label_permissions': self._(u'Permissions'),
 389             'help_permissions': self._(u'Specifies the document permissions.'),
 390 
 391             'label_permissionannotate': self._(u'Annotate'),
 392             'label_permissionprint': self._(u'Print'),
 393             'label_permissionmodify': self._(u'Modify'),
 394             'label_permissioncopy': self._(u'Copy'),
 395 
 396             'label_owner-password': self._(u'Owner password'),
 397             '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.'),
 398 
 399             'label_user-password': self._(u'User password'),
 400             'help_user-password': self._(u'Specifies the user password to restrict viewing permissions on this PDF document.') + u' ' + self._(u'Empty for no encryption.'),
 401 
 402             'label_expert': self._(u'Expert'),
 403             'label_language': self._(u'Language translation'),
 404             'help_language': self._(u'Specify language to use for date and time format.'),
 405 
 406             'label_charset': self._(u'Charset set'),
 407             'help_charset': self._(u'Change the encoding of the text in document.'),
 408 
 409             'button_generate': self._(u'Generate PDF'),
 410             'button_remember': self._(u'Remember form'),
 411             'button_cancel': self._(u'Cancel'),
 412             'button_reset': self._(u'Reset'),
 413             }
 414         self.fields.update(self.values)
 415 
 416         # Go through all format strings.
 417         for name in [u'tocheader', u'tocfooter', u'header', u'footer']:
 418             self.fields[u'choose_' + name] = self._chooseformat(name)
 419 
 420         self.fields[u'select_style'] = self._select (u'style')
 421         self.fields[u'select_format'] = self._select (u'format')
 422         self.fields[u'select_linkstyle'] = self._select (u'linkstyle')
 423         self.fields[u'select_size'] = self._select (u'size')
 424         self.fields[u'select_jpeg'] = self._select (u'jpeg')
 425         self.fields[u'select_compression'] = self._select (u'compression')
 426         self.fields[u'select_toclevels'] = self._select (u'toclevels')
 427         self.fields[u'select_pagemode'] = self._select (u'pagemode')
 428         self.fields[u'select_pagelayout'] = self._select (u'pagelayout')
 429         self.fields[u'select_firstpage'] = self._select (u'firstpage')
 430         self.fields[u'select_charset'] = self._select (u'charset')
 431 
 432         self.fields[u'pdfpydownloadlink'] = u'http://einstein.speech-design.de/~br/pdf.py'
 433         self.fields[u'pdfpyversion'] = self.version
 434 
 435         form = """<p class="error">%(error)s</p>
 436 <form method="post" action="">
 437 <input type="hidden" name="action" value="%(action)s"/>
 438 <table>
 439     <tr><td colspan="3">%(label_input)s</td></tr>
 440     <tr>
 441         <td class="label"><label>%(label_choose_style)s&nbsp;:</label></td>
 442         <td class="content">%(select_style)s</td>
 443         <td>%(help_choose_style)s</td>
 444     </tr>
 445     %(seperator)s
 446     <tr><td colspan="3">%(label_output)s</td></tr>
 447     <tr>
 448         <td class="label"><label>%(label_format)s&nbsp;:</label></td>
 449         <td class="content">%(select_format)s</td>
 450         <td>%(help_format)s</td>
 451     </tr>
 452     <tr>
 453         <td class="label"><label>%(label_outputoptions)s&nbsp;:</label></td>
 454         <td colspan="2"><input type="checkbox" name="grayscale" value="checked" %(grayscale)s />%(label_grayscale)s&nbsp;
 455             <input type="checkbox" name="title" value="checked" %(title)s />%(label_title)s<br />
 456             %(label_compression)s&nbsp:&nbsp;%(select_compression)s&nbsp;
 457             %(label_jpeg)s&nbsp;:&nbsp;%(select_jpeg)s</td>
 458     </tr>
 459     %(seperator)s
 460     <tr><td colspan="3">%(label_page)s</td></tr>
 461     <tr>
 462         <td class="label"><label>%(label_choose_size)s&nbsp;:</label></td>
 463         <td>%(select_size)s&nbsp;<br /><nobr>%(label_usersize)s&nbsp;:&nbsp;<input type="text" size="15" name="usersize" value="%(usersize)s" /></nobr></td>
 464         <td>%(help_choose_size)s<br />%(help_usersize)s</td>
 465     </tr>
 466     <tr>
 467         <td>&nbsp;</td>
 468         <td colspan="2"><input type="checkbox" name="duplex" value="checked" %(duplex)s />&nbsp;%(label_duplex)s&nbsp;
 469             <input type="checkbox" name="landscape" value="checked" %(landscape)s />&nbsp;%(label_landscape)s</td>
 470     </tr>
 471     <tr>
 472         <td class="label"><label>%(label_margin)s&nbsp;:</label></td>
 473         <td><table><tr><td>&nbsp;</td><td><nobr><label>%(label_margintop)s&nbsp;:</label>&nbsp;<input type="text" name="margintop" value="%(margintop)s" size="7" /></nobr></td><td>&nbsp;</td></tr>
 474             <tr><td><nobr><label>%(label_marginleft)s&nbsp;:</label>&nbsp;<input type="text" name="marginleft" value="%(marginleft)s" size="7" /></nobr></td><td>&nbsp;</td><td><nobr><label>%(label_marginright)s&nbsp;:</label>&nbsp;<input type="text" name="marginright" value="%(marginright)s" size="7" /></nobr></td></tr>
 475             <tr><td>&nbsp;</td><td><nobr><label>%(label_marginbottom)s&nbsp;:</label>&nbsp;<input type="text" name="marginbottom" value="%(marginbottom)s" size="7" /></nobr></td><td>&nbsp;</td></tr></table>
 476         <td>%(help_margin)s</td>
 477     </tr>
 478     %(choose_header)s
 479     %(choose_footer)s
 480     %(seperator)s
 481     <tr><td colspan="3">%(label_tableofcontents)s</td></tr>
 482     <tr>
 483         <td class="label"><label>%(label_no-toc)s&nbsp;:</label></td>
 484         <td class="checkbox"><input type="checkbox" name="no-toc" value="checked" %(no-toc)s /></td>
 485         <td>%(help_no-toc)s</td>
 486     </tr>
 487     <tr>
 488         <td class="label"><label>%(label_toclevels)s&nbsp;:</label></td>
 489         <td class="content">%(select_toclevels)s</td>
 490         <td>%(help_toclevels)s</td>
 491     </tr>
 492     <tr>
 493         <td>&nbsp;</td>
 494         <td><input type="checkbox" name="numbered" value="checked" %(numbered)s />&nbsp;%(label_numbered)s</td>
 495         <td>%(help_numbered)s</td>
 496     </tr>
 497     <tr>
 498         <td class="label"><label>%(label_toctitle)s&nbsp;:</label></td>
 499         <td class="content"><input type="text" size="30" name="toctitle" value="%(toctitle)s" /></td>
 500         <td>%(help_toctitle)s</td>
 501     </tr>
 502     %(choose_tocheader)s
 503     %(choose_tocfooter)s
 504     %(seperator)s
 505     <tr><td colspan="3">%(label_pdf)s</td></tr>
 506     <tr>
 507         <td class="label"><label>%(label_pagemode)s&nbsp;:</label></td>
 508         <td class="content">%(select_pagemode)s</td>
 509         <td>%(help_pagemode)s</td>
 510     </tr>
 511     <tr>
 512         <td class="label"><label>%(label_pagelayout)s&nbsp;:</label></td>
 513         <td class="content">%(select_pagelayout)s</td>
 514         <td>%(help_pagelayout)s</td>
 515     </tr>
 516     <tr>
 517         <td class="label"><label>%(label_firstpage)s&nbsp;:</label></td>
 518         <td class="content">%(select_firstpage)s</td>
 519         <td>%(help_firstpage)s</td>
 520     </tr>
 521     <tr>
 522         <td class="label"><label>%(label_no-links)s&nbsp;:</label></td>
 523         <td><input type="checkbox" name="no-links" value="checked" %(no-links)s /></td>
 524         <td>%(help_no-links)s</td>
 525     </tr>
 526     <tr>
 527         <td class="label"><label>%(label_linkstyle)s&nbsp;:</label></td>
 528         <td class="content">%(select_linkstyle)s</td>
 529         <td>%(help_linkstyle)s</td>
 530     </tr>
 531     <tr>
 532         <td class="label"><label>%(label_linkcolor)s&nbsp;:</label></td>
 533         <td class="content"><input type="text" size="6" name="linkcolor" value="%(linkcolor)s" /></td>
 534         <td>%(help_linkcolor)s</td>
 535     </tr>
 536     %(seperator)s
 537     <tr><td colspan="3">%(label_security)s</td></tr>
 538     <tr>
 539         <td class="label"><label>%(label_encryption)s&nbsp;:</label></td>
 540         <td><input type="checkbox" name="encryption" value="checked" %(encryption)s /></td>
 541         <td>%(help_numbered)s</td>
 542     </tr>
 543     <tr>
 544         <td class="label"><label>%(label_permissions)s&nbsp;:</label></td>
 545         <td><nobr><input type="checkbox" name="permissionprint" value="checked" %(permissionprint)s />&nbsp;%(label_permissionprint)s</nobr>&nbsp;
 546             <nobr><input type="checkbox" name="permissionmodify" value="checked" %(permissionmodify)s />&nbsp;%(label_permissionmodify)s</nobr><br />
 547             <nobr><input type="checkbox" name="permissioncopy" value="checked" %(permissioncopy)s />&nbsp;%(label_permissioncopy)s</nobr>&nbsp;
 548             <nobr><input type="checkbox" name="permissionannotate" value="checked" %(permissionannotate)s />&nbsp;%(label_permissionannotate)s</nobr></td>
 549         <td>%(help_permissions)s</td>
 550     </tr>
 551     <tr>
 552         <td class="label"><label>%(label_user-password)s&nbsp;:</label></td>
 553         <td class="content"><input type="password" size="30" name="user-password" value="%(user-password)s" /></td>
 554         <td>%(help_user-password)s</td>
 555     </tr>
 556     <tr>
 557         <td class="label"><label>%(label_owner-password)s&nbsp;:</label></td>
 558         <td class="content"><input type="password" size="30" name="owner-password" value="%(owner-password)s" /></td>
 559         <td>%(help_owner-password)s</td>
 560     </tr>
 561     %(seperator)s
 562     <tr><td colspan="3">%(label_expert)s</td></tr>
 563     <tr>
 564         <td class="label"><label>%(label_language)s&nbsp;:</label></td>
 565         <td class="content"><input type="text" size="6" name="language" value="%(language)s" /></td>
 566         <td>%(help_language)s</td>
 567     </tr>
 568     <tr>
 569         <td class="label"><label>%(label_charset)s&nbsp;:</label></td>
 570         <td class="content">%(select_charset)s</td>
 571         <td>%(help_charset)s</td>
 572     </tr>
 573     %(space)s
 574     <tr>
 575         <td><input type="hidden" name="debug" value="%(debug)s" /><input type="hidden" name="rev" value="%(rev)s" /></td>
 576         <td class="buttons" colspan="2">
 577             <input type="submit" name="generate_from_form" value="%(button_generate)s" />&nbsp;
 578             <input type="submit" name="remember" value="%(button_remember)s" />&nbsp;
 579             <input type="submit" name="cancel" value="%(button_cancel)s" />
 580         </td>
 581     </tr>
 582 </table>
 583 </form>""" % self.fields
 584         return Dialog (self.request, content=form)
 585 
 586     def run (self, pagename, request):
 587         """ Main dispatcher for the action."""
 588         self.pagename = pagename
 589         self.request = request
 590         self._ = self.request.getText
 591         self.form = self.request.form
 592         self.cfg = self.request.cfg
 593 
 594         # Canceled by user.
 595         if self.form.has_key(u'cancel'):
 596             return self.request.page.send_page(self.request)
 597 
 598         # Determine calling parameters.
 599         if self.form.get(u'debug', [u'0']) == [u'1']:
 600             self.debug = True
 601 
 602         # This dict contains all possible values.
 603         self.valid_options = {}
 604 
 605         self.valid_options[u'tocformats'] = {
 606             u'/': self._(u'1/N,2/N Arabic page numbers'),
 607             u':': self._(u'1/C,2/C Arabic chapter page numbers'),
 608             u'1': self._(u'1,2,3,...'),
 609             u'a': self._(u'a,b,c,...'),
 610             u'A': self._(u'A,B,C,...'),
 611             u'c': self._(u'Chapter title'),
 612             u'C': self._(u'Chapter page number'),
 613             u'd': self._(u'Date'),
 614             u'D': self._(u'Date + Time'),
 615             u'h': self._(u'Heading'),
 616             u'i': self._(u'i,ii,iii,iv,...'),
 617             u'I': self._(u'I,II,III,IV,...'),
 618             u't': self._(u'Title'),
 619             u'T': self._(u'Time'),
 620             u'.': self._(u'Blank'),
 621             # TODO: Not supported yet; u'l': self._(u'Logo image'),
 622         }
 623         self.valid_options[u'style'] = {
 624             u'webpage': self._(u'webpage'),
 625             u'book': self._(u'book'),
 626             u'continuous': self._(u'continuous'),
 627         }
 628         self.valid_options[u'size'] = {
 629             u'legal': self._(u'Legal (8.5x14in)'),
 630             u'a4': self._(u'A4 (210x297mm)'),
 631             u'letter': self._(u'Letter (8.5x11in)'),
 632             u'universal': self._(u'Universal (8.27x11in)'),
 633             u'': self._(u'User defined'),
 634         }
 635         self.valid_options[u'format'] = {
 636             u'pdf11': self._(u'PDF 1.1 (Acrobat 2.0)'),
 637             u'pdf12': self._(u'PDF 1.2 (Acrobat 3.0)'),
 638             u'pdf13': self._(u'PDF 1.3 (Acrobat 4.0)'),
 639             u'pdf14': self._(u'PDF 1.4 (Acrobat 5.0)'),
 640             # TODO: Not supported yet:
 641             #u'ps1': self._(u'PostScript Level 1'),
 642             #u'ps2': self._(u'PostScript Level 2'),
 643             #u'ps3': self._(u'PostScript Level 3'),
 644         }
 645         self.valid_options[u'linkstyle'] = {
 646             u'underline': self._(u'Underline'),
 647             u'plain': self._(u'Plain'),
 648         }
 649         self.valid_options[u'firstpage'] = {
 650             u'c1': self._(u'1st chapter'),
 651             u'p1': self._(u'1st page'),
 652             u'toc': self._(u'Contents'),
 653         }
 654         self.valid_options[u'jpeg'] = {
 655             u'0': self._(u'None'),
 656             u'50': self._(u' 50% (Good)'),
 657             u'55': u'55%', u' 60': u' 60%', u' 65': ' 65%', u' 70': ' 70%', u' 75': ' 75%',
 658             u'80': ' 80%', u' 85': ' 85%', u' 90': ' 90%', u' 95': ' 95%',
 659             u'100': self._(u'100% (Best)'),
 660         }
 661         self.valid_options[u'compression'] = {
 662             u'0': self._(u'None'),
 663             u'1': self._(u'1 (Fast)'),
 664             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',
 665             u'9': self._(u'9 (Best)'),
 666         }
 667         self.valid_options[u'toclevels'] = {
 668             u'0': self._(u'None'),
 669             u'1': u'1', u'2': '2', u'3': '3', u'4': '4'
 670         }
 671         self.valid_options[u'pagemode'] = {
 672             u'outline': self._(u'Outline'),
 673             u'document': self._(u'Document'),
 674             u'fullscreen': self._(u'Full-screen'),
 675         }
 676         self.valid_options[u'pagelayout'] = {
 677             u'single': self._(u'Single'),
 678             u'one': self._(u'One column'),
 679             u'twoleft': self._(u'Two column left'),
 680             u'tworight': self._(u'Two column right'),
 681         }
 682         self.valid_options[u'charset'] = {
 683             u'iso-8859-1': self._(u'ISO 8859-1'),
 684             u'iso-8859-2': self._(u'ISO 8859-2'),
 685             u'iso-8859-3': self._(u'ISO 8859-3'),
 686             u'iso-8859-4': self._(u'ISO 8859-4'),
 687             u'iso-8859-5': self._(u'ISO 8859-5'),
 688             u'iso-8859-6': self._(u'ISO 8859-6'),
 689             u'iso-8859-7': self._(u'ISO 8859-7'),
 690             u'iso-8859-8': self._(u'ISO 8859-8'),
 691             u'iso-8859-9': self._(u'ISO 8859-9'),
 692             u'iso-8859-14': self._(u'ISO 8859-14'),
 693             u'iso-8859-15': self._(u'ISO 8859-15'),
 694             u'cp-874': self._(u'cp-847'),
 695             u'cp-1250': self._(u'cp-1250'),
 696             u'cp-1251': self._(u'cp-1251'),
 697             u'cp-1252': self._(u'cp-1252'),
 698             u'cp-1253': self._(u'cp-1253'),
 699             u'cp-1254': self._(u'cp-1254'),
 700             u'cp-1255': self._(u'cp-1255'),
 701             u'cp-1256': self._(u'cp-1256'),
 702             u'cp-1257': self._(u'cp-1257'),
 703             u'cp-1258': self._(u'cp-1258'),
 704             u'koi-8r': self._(u'koi-8r'),
 705         }
 706 
 707         # Set translated name of table of contents as default.
 708         self.default_values[u'toctitle'] = self._(u'Contents')
 709 
 710         # Make sure we create date and time strings in right format.
 711         if self.request.page.language:
 712             self.default_values[u'language'] = self.request.page.language
 713         else:
 714             self.default_values[u'language'] = self.make_isolang(self.cfg.__dict__.get (u'default_language', u'en'))
 715 
 716         self.values = {}
 717 
 718         # If the configuration variable 'createpdfdocument_validoptions' exists we update our
 719         # self.valid_options dict with these values.
 720         if getattr (self.request.cfg, u'createpdfdocument_validoptions', None):
 721             self.valid_options.update (self.request.cfg.createpdfdocument_validoptions)
 722 
 723         # If the configuration variable 'createpdfdocument_defaultvalues' exists we update our
 724         # self.default_values dict with these values.
 725         if getattr (self.request.cfg, u'createpdfdocument_defaultvalues', None):
 726             for key, value in self.request.cfg.createpdfdocument_defaultvalues.items():
 727                 self.default_values[key] = value
 728 
 729         # Scan page to extract default values.
 730         self.set_page_default_values()
 731 
 732         # Create a PDF document direct without any user iteraction from default and page settings.
 733         if self.form.has_key(u'generate'):
 734             self.set_page_values()
 735             self.update_values(useform=False)
 736             return self.do_generate()
 737 
 738         # Create a PDF document from form settings.
 739         if self.form.has_key(u'generate_from_form'):
 740             self.update_values()
 741             return self.do_generate()
 742 
 743         if self.form.has_key(u'remember'):
 744             self.update_values()
 745             return self.do_remember()
 746 
 747         self.set_page_values()
 748         self.update_values(useform=False)
 749         return self.request.page.send_page (self.request, msg=self.makeform())
 750 
 751     def update_values(self, useform=True):
 752         """Preset values with they form values or defaults."""
 753         for key, default in self.default_values.items():
 754             # Modify value only if not already set.
 755             if not key in self.values:
 756                 # If the form does not contain the value (e.g. for checkboxes) set the
 757                 # default value (e.g. for checkboxes unset by default).
 758                 if not key in self.form:
 759                     # Special processing for checkboxes in forms. If the key does not exists
 760                     # within the form it is not checked.
 761                     if key in self.form_checkbox and useform:
 762                         self.values[key] = u'unchecked'
 763                     else:
 764                         self.values[key] = default
 765                 else:
 766                     self.values[key] = self.form[key][0]
 767         # Check if revision is an integer value.
 768         try:
 769             self.values[u'rev'] = int(self.values.get(u'rev', self.request.page.rev))
 770         except:
 771             self.values[u'rev'] = self.request.page.rev
 772         # Check if page revision exists.
 773         (pagefname, realrev, exists) = self.request.page.get_rev (rev=self.values[u'rev'])
 774         if exists:
 775             self.values[u'rev'] = realrev
 776         else:
 777             # Determine latest revision number.
 778             (pagefname, self.values[u'rev'], exists) = self.request.page.get_rev()
 779 
 780     def do_generate(self):
 781         """Create PDF document."""
 782         # Generate the HTML page using MoinMoin wiki engine.
 783         html = self.get_html()
 784         if html:
 785             if self.debug:
 786                 self.request.http_headers()
 787                 self.request.write(html)
 788             else:
 789                 pdfdata = self.html2pdf(html)
 790                 if pdfdata:
 791                     # Send as application/pdf the generated file by HTMLDOC
 792                     self.send_pdf(pdfdata)
 793                     raise MoinMoinNoFooter
 794 
 795     def do_remember(self):
 796         """Create a message containing information about how to save the form values for future reuse."""
 797         save = u''
 798         for key, value in self.values.items():
 799             if key in [u'user-password', u'owner-password', u'rev', u'debug']:
 800                 continue
 801             if key in self.default_values and value == self.default_values[key]:
 802                 continue
 803             save += u'##pdf %s %s\n' % (key, value,)
 804         if save:
 805             msg = self._(u'Add follwing lines at the beginning of your page:') + u'<br/><pre>' + save + u'</pre>'
 806         else:
 807             msg = self._(u'All values correspond to they default. Nothing have to be saved.')
 808         return self.request.page.send_page (self.request, msg)
 809 
 810     def send_pdf (self, data):
 811         filename = self.pagename.replace (u'/', u'-') + u'-v' + str(self.values[u'rev']) + u'.pdf'
 812 
 813         # Send HTTP header.
 814         self.request.http_headers([
 815             'Content-Type: %s' % self.contenttype,
 816             'Content-Length: %d' % len(data),
 817             # TODO: fix the encoding here, plain 8 bit is not allowed
 818             # according to the RFCs There is no solution that is
 819             # compatible to IE except stripping non-ascii chars
 820             'Content-Disposition: inline; filename="%s"' % filename.encode(config.charset),
 821             ])
 822 
 823         # Send binary data.
 824         sio = StringIO.StringIO(data)
 825         shutil.copyfileobj (sio, self.request, 8192)
 826 
 827     def get_html(self):
 828         """Generate the HTML body of this page."""
 829         newreq = RedirectOutputRequest(self.request)
 830 
 831         newreq.form[u'action'] = [u'print']
 832         if self.values.get(u'rev', None):
 833             newreq.form[u'rev'] = [self.values[u'rev']]
 834 
 835         # Save page as HTML.
 836         (html, errmsg) = newreq.run()
 837         if html:
 838             # Make URLs absolute.
 839             # Until MoinMoin is not XHTML compilant we can not use a XML parser (e.g. expat)
 840             # to transform the HTML document. In the meantime we try to achive the same with
 841             # regular expressions subtitution.
 842             base = self.request.getQualifiedURL()
 843             for htmlref in [u'src', u'href']:
 844                 reurlref = r'(%s=[\'"])(/[^\'"]*)[\'"]' % (htmlref,)
 845                 urlref = re.compile (reurlref, re.I)
 846                 for match in urlref.finditer(html):
 847                     foundref = match.groups()
 848                     html = html.replace (foundref[0] + foundref[1], foundref[0] + base + foundref[1])
 849         else:
 850             self.error_msg(self._(u'Could not redirect HTML output for further processing:') + errmsg)
 851 
 852         return html
 853 
 854     def make_isolang (self, language):
 855         return language + u'_' + language.upper()
 856 
 857     def html2pdf (self, html):
 858         """Create a PDF document based on the current parameters."""
 859         # Determine UID to access ACL protected sites too (mandatory to download attached images).
 860         htmldocopts = [u'LANG=' + self.values[u'language'], u'HTMLDOC_NOCGI=1', u'htmldoc', "--cookies", "MOIN_ID=" + self.request.user.id, u'--no-duplex']
 861 
 862         for key in [u'header', u'footer', u'tocheader', u'tocfooter']:
 863             self.values[key] = self.values.get (key + u'left', u'.') + self.values.get (key + u'middle', u'.') + self.values.get (key + u'right', u'.')
 864 
 865         permissions = []
 866         for opt, value in self.values.items():
 867             if opt in [u'language', u'debug', u'rev']:
 868                 continue
 869             value = value.strip()
 870             if not value:
 871                 continue
 872             # Skip options for header/footer configuration which differenciate between position (e.g. footerright or tocheadermiddle)
 873             if opt[:6] in [u'header', u'footer'] and opt[6:] or opt[:9] in [u'tocheader', u'tocfooter'] and opt[9:]:
 874                 continue
 875             if opt in [u'style']:
 876                 htmldocopts += [u'--' + value]
 877             elif opt in self.form_checkbox:
 878                 if value == u'checked':
 879                     if opt[:10] == u'permission':
 880                         permissions += [opt[10:]]
 881                     # Reverse meaning of 'no-' options.
 882                     elif opt[:3] != u'no-':
 883                         htmldocopts += [u'--' + opt]
 884                 elif opt[:3] == u'no-':
 885                     htmldocopts += [u'--' + opt]
 886             elif opt[:6] == u'margin':
 887                 htmldocopts += [u'--' + opt[6:], value]
 888             else:
 889                 htmldocopts += [u'--' + opt, value]
 890         if permissions:
 891             htmldocopts += [u'--permission', u','.join (permissions)]
 892         htmldocopts += [u'-']
 893         # Do not forget to escape all spaces!
 894         eschtmldocopts = [arg.replace(u' ', u'\\ ') for arg in htmldocopts]
 895         cmdstr = u' '.join(eschtmldocopts)
 896         errmsg = None
 897 
 898         pdf = None
 899         if self.debug:
 900             errmsg = self._(u'Command:') + u'<pre>' + self.escape(cmdstr) + u'</pre>'
 901         else:
 902             inp, out, err = os.popen3 (cmdstr, u'b')
 903             try:
 904                 inp.write(html)
 905                 inp.close()
 906             except:
 907                 pass
 908 
 909             pdf = out.read()
 910             out.close()
 911 
 912             htmldocmsg = err.read()
 913             err.close()
 914 
 915             try:
 916                 # REMARK: Otherwise we get <defunct> processes.
 917                 os.wait()
 918             except OSError, e:
 919                 # 10: No child processes.
 920                 if e.errno != 10:
 921                     raise
 922 
 923             # Check for error message on STDOUT.
 924             if pdf[:8] == u'HTMLDOC ':
 925                 htmldocmsg += pdf
 926                 pdf = None
 927 
 928             if htmldocmsg:
 929                 errmsg = self._(u'Command:') + u'<pre>' + self.escape(cmdstr) + u'</pre>' + self._('returned:') + u'<pre>' + \
 930                       self.escape(htmldocmsg).replace(u'\n', u'<br/>') + u'</pre>'
 931 
 932         # As it is difficult to get the htmldoc return code, we check for
 933         # error by checking the produced pdf length
 934         if not pdf and errmsg:
 935             self.error_msg(errmsg)
 936             return None
 937         elif pdf[:4] != '%PDF':
 938             self.error_msg(self._(u'Invalid PDF document generated') + self.escape(pdf[:80]))
 939             return None
 940 
 941         return pdf
 942 
 943 def execute (pagename, request):
 944     CreatePdfDocument().run (pagename = pagename, request = request)
 945 
 946 # vim:ts=4:et:sw=4:nu

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.