# -*- coding: iso-8859-1 -*-
"""
    MoinMoin - Generate PDF document using HTMLDOC

    This action script generate PDF documents from a Wiki site using
    the HTMLDOC (http://www.htmldoc.org) software packages which has
    to be preinstalled first.

    To use this feature install this script in your's MoinMoin action
    script plugin directory.

    Thanks goes to Pascal Bauermeister who initiated the implementaion.
    Lot of things changes since then but the idear using HTMLDOC is the
    main concept of this implementation.

    @copyright: (C) 2006  Pascal Bauermeister
    @copyright: (C) 2006  Raphael Bossek <raphael.bossek@solutions4linux.de>
    @license: GNU GPL, see COPYING for details
    
    2006-11-16  RaphaelBossek
    * Release v2.0.8
    * Fixed another missing configuration for RedirectOutputRequest()

    2006-11-15  RaphaelBossek
    * Release v2.0.7
    * Fixed support for MoinMoin 1.5.3
    * Fixed SSL support.

    2006-11-14  RaphaelBossek
    * Release v2.0.6
    * MoinMoin 1.6 support added.
    * Fixed Windows(TM) platform support.
    * Added support for alternative title text.

    2006-09-13  RaphaelBossek
    * Release v2.0.5
    * Fixed RedirectOutputRequest class where definition of script_name
      was missing.

    2006-09-12  RaphaelBossek
    * Release v2.0.4
    * Fixed RedirectOutputRequest class where function was redifined
      to boolean value.

    2006-09-06  RaphaelBossek
    * Release v2.0.3
    * Fixed FastCGI support by removing stdout redirect output code. The
      same functionality will be done by the RedirectOutputRequest class.
    * Renamed to CreatePdfDocument (for better translation possibilities).
    * Fixed encoding of title page.
    * Added charset set option.
    * Fixed waiting for HTMLDOC.

    2006-09-02  RaphaelBossek
    * Release v2.0.2
    * Added createpdfdocument_validoptions and createpdfdocument_defaultvalues
      configuration parameter support. You are not able to preset available
      options and which defaults are used in your configuration file.

    2006-08-30  RaphaelBossek
    * Release v2.0.1
    * Fixed issue with page revision number forwarding (traceback).

    2006-08-30  RaphaelBossek
    * Release v2.0.0
    * Feature enchanced and bug fixed version.

    2006-05-26  PascalBauermeister
    * Release v1.0.2
    * Relative image URLs turned absolute was bogus. It is less bogus now.

    2006-05-26  PascalBauermeister
    * Release v1.0.1
    * Set env var HTMLDOC_NOCGI to solve CGI issue

    2006-05-24  PascalBauermeister
    * Initial release v1.0.0
"""

import os
import sys
import stat
import re
import copy
import shutil
import StringIO
from MoinMoin import config
from MoinMoin import util
from MoinMoin import wikiutil
from MoinMoin import packages
from MoinMoin import error
from MoinMoin.Page import Page
from MoinMoin.request import RequestBase
from MoinMoin.widget.dialog import Dialog


class RedirectOutputRequest(RequestBase):
    """Redirect output to string without HTTP headers."""
    def __init__ (self, req, action=u'print'):
        """Prepare a compatible request."""
        # 1.5,1.6:req.path_info = u'/RaphaelBossek/\xd6nologe' | u'/FrontPage'
        # after enconde() self.path_info = '/RaphaelBossek/\xd6nologe'
        self.path_info = req.path_info.encode(config.charset)
        # 1.5,1.6:query_string = req.query_string = u'action=CreatePdfDocument'
        self.query_string = u'action=' + action
        # 1.5,1.6:request_uri = req.request_uri = u'/FrontPage?action=CreatePdfDocument'
        self.request_uri = req.path_info.replace(req.query_string, self.query_string)
        # 1.5,1.6:url = req.url = u'localhost:8080/FrontPage?action=CreatePdfDocument'
        self.url = req.url.replace(req.query_string, self.query_string)
        # 1.5,1.6: Not all kinds of request support SSL yet.
        if 'is_ssl' in req.__dict__:
            self.is_ssl = req.is_ssl
        else:
            self.is_ssl = 0
        self.http_host = req.http_host
        self.http_user_agent = req.http_user_agent
        self.request_method = None
        self.saved_cookie = req.saved_cookie
        self.remote_addr = req.remote_addr
        self.script_name = getattr (req, u'script_name', u'')

        self.req = req
        RequestBase.__init__(self)

    def run(self, rev = None):
        """Start processing the document."""
        # 1.6:MoinMoin/request/__init__.py: Initialise action from environment variables not from form.
        self.action = u'print'
        if rev:
            self.form[u'rev'] = [rev]
        self.output_string = u''
        self.error_string = u''
        self.sent_headers = False
        self.failed = 0
        RequestBase.run(self)
        return (self.output_string, self.error_string)

    def http_headers (self, *args, **kw):
        """Used by MoinMoin 1.5.x instead of emit_http_headers()."""
        self.sent_headers = True
    
    def emit_http_headers(self, *args, **kw):
        """Used by MoinMoin 1.6.x instaed of http_headers()."""
        self.sent_headers = 1

    def fail (self, err):
        """Catch if a error occurs and save the message in our string."""
        RequestBase.fail (self, err)
        if not self.error_string:
            self.error_string = str(err)

    def write (self, *data):
        """Catch the document in our output_string."""
        if self.sent_headers:
            if self.failed:
                self.error_string += data[0]
            else:
                self.output_string += data[0]

    def flush(self):
        pass


class CreatePdfDocument:
    """Implementation of the PDF document generator."""

    def __init__(self):
        self.action_name = self.__class__.__name__
        self.pagename = None
        self.request = None
        self._ = None
        self.debug = False
        self.msg = None
        self.errormsgsent = False
        self.default_values = {
            'style': u'webpage',
            'format': u'pdf13',
            'linkstyle': u'underline',
            'headerleft': u't',
            'headermiddle': u'.',
            'headerright': u'D',
            'footerleft': u'.',
            'footermiddle': u'/',
            'footerright': u'.',
            'tocheaderleft': u'.',
            'tocheadermiddle': u't',
            'tocheaderright': u'.',
            'tocfooterleft': u'.',
            'tocfootermiddle': u'.',
            'tocfooterright': u'i',
            'linkcolor': u'0000E0',
            'size': u'legal',
            'user-password': u'',
            'owner-password': u'',
            'toclevels': u'3',
            'grayscale': u'unchecked',
            'title': u'checked',
            'duplex': u'unchecked',
            'landscape': u'unchecked',
            'usersize': u'',
            'margintop': u'0.50in',
            'marginbottom': u'0.50in',
            'marginleft': u'1.00in',
            'marginright': u'0.50in',
            'no-toc': u'checked',
            'no-links': u'checked',
            'firstpage': u'p1',
            'jpeg': u'0',
            'compression': u'0',
            'pagemode': u'outline',
            'pagelayout': u'single',
            'firstpage': u'c1',
            'numbered': u'checked',
            'encryption': u'unchecked',
            'permissioncopy': u'checked',
            'permissionprint': u'checked',
            'permissionannotate': u'checked',
            'permissionmodify': u'checked',
            'charset': u'iso-8859-1',
            'debug': u'',
            'rev': 0,
        }
        # We have to know which values are checkboxes within the form. If a key does
        # not exists wihtin the form the corresponding checkbox is not checked.
        self.form_checkbox = []
        for key, value in self.default_values.items():
            if value in [u'checked', u'unchecked']:
                self.form_checkbox += [key]
        self.contenttype = u'application/pdf'

    def error_msg (self, msg):
        """Display error message."""
        if not self.errormsgsent:
            Page (self.request, self.pagename).send_page (self.request, msg=msg)
            self.errormsgsent = True

    def fixhtmlstr (self, str):
        """Convert utf-8 encoded multi-byte sequences into &#XXXX; format."""
        htmlstr = u''
        for c in str:
            if ord(c) >= 128:
                htmlstr = htmlstr + '&#%d;' % ord(c)
            else:
                htmlstr = htmlstr + c
        return htmlstr

    def fixutf8latin1 (self, str):
        """Convert a UTF-8 string containing ISO-8859-1 encoding to a true ISO-8859-1 string.
        It's the weakness of MoinMoin 1.5 where pages are save as UTF-8 (multi byte) data
        containing ISO-8859-1 characters (sigle byte). Python run into trouble with this
        'currupted' UNICODE strings.
        Until be able to use the string within Python's functions we have to fix this problem.
        """
        return str.encode(config.charset).decode(self.values['charset'])#.encode(self.values['charset'])

    def set_page_values(self):
        """Scan raw page for additional information relating PDF generation."""
        #pdflines = False
        for line in self.request.page.get_raw_body().split(u'\n'):
            if line[:6] == u'##pdf ':
                cols = line[6:].split()
                # Only accept known values/settings.
                if len(cols) > 1 and cols[0] in self.default_values:
                    self.values[cols[0]] = u' '.join(cols[1:])
                    #pdflines = True
                continue
            # Stop parsing with first non-pdf line (if detected at least one).
            #elif pdflines and not line:
            #    break

    def set_page_default_values(self):
        # We are not able to recognise if this string is part of a verbatim area.
        matchtoclvl = re.compile(r'^\[\[TableOfContents\(\s*(\d+)\s*\)\]\]')
        matchtoc = re.compile(r'^\[\[TableOfContents\(*\)*\]\]')
        toc = False
        for line in self.request.page.get_raw_body().split(u'\n'):
            if line[:10] == u'#language ' and not u'language' in self.values:
                lang = self.make_isolang(line[10:])
                if lang:
                    self.default_values[u'language'] = lang
            elif not u'toclevels' in self.values and not toc:
                result = matchtoclvl.match(line)
                if result:
                    toclevels = int(result.group(1).strip())
                    if toclevels > 4:
                        toclevels = 4
                    self.default_values[u'toclevels'] = str(toclevels)
                    toc = True
                elif matchtoc.match(line):
                    toc = True
        # We assume if table-of-contents is used we intent to generate a book.
        if toc:
            self.default_values[u'style'] = u'book'
        else:
            # Do not generate a table of contents page.
            self.default_values[u'no-toc'] = u'unchecked'
 
    def _select (self, name, description=None):
        """Helper function to create a selection control."""
        str = u'<select name="%s" size="1">' % (name,)
        if not description:
            description = self.valid_options[name]
        keys = description.keys()
        keys.sort()
        for value in keys:
            if value == self.values[name]:
                selected = u'selected'
            else:
                selected = u''
            str += u'<option value="%s" %s>%s</option>' % (value, selected, description[value],)
        str += u'</select>'
        return str

    def _chooseformat (self, name):
        """Helper function to create left/middle/right selection controls."""
        str = u"""    <tr>
        <td class="label"><label>%s&nbsp;:</label></td>
        <td><table>
                <tr>
                    <td>%s&nbsp;:</td>
                    <td>%s</td>
                </tr>
                <tr>
                    <td>%s&nbsp;:</td>
                    <td>%s</td>
                </tr>
                <tr>
                    <td>%s&nbsp;:</td>
                    <td>%s</td>
                </tr>
            </table>
        </td>
        <td>%s</td>
    </tr>""" % (self.fields[u'label_' + name],
              self.fields[u'label_left'], self._select(name + u'left', self.valid_options[u'tocformats']),
              self.fields[u'label_middle'], self._select(name + u'middle', self.valid_options[u'tocformats']),
              self.fields[u'label_right'], self._select(name + u'right', self.valid_options[u'tocformats']),
              self.fields[u'help_' + name],)
        return str

    def makeform (self, errormsg=u''):
        self.fields = {
            'error': errormsg,
            'pagename': wikiutil.escape(self.pagename),
            'action': self.action_name,
            'seperator': u'<tr><td colspan="3"><hr/></td></tr>',
            'space': u'<tr><td colspan="3">&nbsp;</td></tr>',

            'label_input': self._(u'Input'),
            'label_output': self._(u'Output'),
            'label_page': self._(u'Page'),
            'label_tableofcontents': self._(u'Contents'),
            'label_pdf': self._(u'PDF'),
            'label_security': self._(u'Security'),

            'label_choose_style': self._(u'Choose style'),
            'help_choose_style': self._(u'book: Create a structured PDF document with headings, chapters, etc.') + u'<br/>' + \
                                 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/>' + \
                                 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.'),

            'help_titletext': self._(u'Title of the document for the front page.'),

            'label_format': self._(u'Output format'),
            'help_format': self._(u'Specifies the output format.'),

            'label_outputoptions': self._(u'Output options'),
            'label_grayscale': self._(u'Grayscale document'),
            'label_titlepage': self._(u'Title page'),
            'label_titletext': self._(u'Title'),
            'label_jpeg': self._(u'JPEG big images'),
            'label_compression': self._(u'Compression'),

            'label_no-toc': self._(u'Generate a table of contents'),
            'help_no-toc': self._(u''),

            'label_toclevels': self._(u'Limit the number of levels in the table-of-contents'),
            'help_toclevels': self._(u'Sets the number of levels in the table-of-contents.') + u' ' + self._(u'Empty for unlimited levels.'),

            'label_numbered': self._(u'Numbered headings'),
            'help_numbered': self._(u'Check to number all of the headings in the document.'),

            'label_toctitle': self._(u'Table-of-contents title'),
            'help_toctitle': self._(u'Sets the title for the table-of-contents.') + u' ' + self._(u'Empty for default title.'),

            'label_left': self._(u'Left'),
            'label_middle': self._(u'Middle'),
            'label_right': self._(u'Right'),

            'label_tocheader': self._(u'Header of table-of-contantes page'),
            'help_tocheader': self._(u'Sets the page header to use on table-of-contents pages.'),

            'label_tocfooter': self._(u'Footer of table-of-contantes page'),
            'help_tocfooter': self._(u'Sets the page footer to use on table-of-contents pages.'),

            'label_header': self._(u'Page header'),
            'help_header': self._(u'Sets the page header to use on body pages.'),

            'label_footer': self._(u'Page footer'),
            'help_footer': self._(u'Sets the page footer to use on body pages.'),

            'label_no-links': self._(u'Create HTTP links'),
            'help_no-links': self._(u'Enables generation of links in PDF files.'),
            
            'label_linkstyle': self._(u'Style of HTTP links'),
            'help_linkstyle': self._(u''),

            'label_linkcolor': self._(u'HTTP links color'),
            'help_linkcolor': self._(u'Sets the color of links.'),

            'label_duplex': self._(u'2-Sided'),
            'help_duplex': self._(u'Specifies that the output should be formatted for double-sided printing.'),

            'label_landscape': self._(u'Landscape'),

            'label_choose_size': self._(u'Choose page size'),
            'help_choose_size': self._(u'Choose one of the predefined standard sizes or select user defined.'),

            'label_usersize': self._(u'User defined page size'),
            '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).'),

            'label_margin': self._(u'User defined margin'),
            'label_margintop': self._(u'Top'),
            'label_marginbottom': self._(u'Bottom'),
            'label_marginleft': self._(u'Left'),
            'label_marginright': self._(u'Right'),
            '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.'),

            'label_pagemode': self._(u'Page mode'),
            '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.'),

            'label_pagelayout': self._(u'Page layout'),
            '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.'),

            'label_firstpage': self._(u'First page'),
            'help_firstpage': self._(u'Choose the initial page that will be shown.'),

            'label_encryption': self._(u'Encryption'),
            'help_encryptin': self._(u'Enables encryption and security features for PDF output.'),

            'label_permissions': self._(u'Permissions'),
            'help_permissions': self._(u'Specifies the document permissions.'),

            'label_permissionannotate': self._(u'Annotate'),
            'label_permissionprint': self._(u'Print'),
            'label_permissionmodify': self._(u'Modify'),
            'label_permissioncopy': self._(u'Copy'),

            'label_owner-password': self._(u'Owner password'),
            '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.'),

            'label_user-password': self._(u'User password'),
            'help_user-password': self._(u'Specifies the user password to restrict viewing permissions on this PDF document.') + u' ' + self._(u'Empty for no encryption.'),

            'label_expert': self._(u'Expert'),
            'label_language': self._(u'Language translation'),
            'help_language': self._(u'Specify language to use for date and time format.'),

            'label_charset': self._(u'Charset set'),
            'help_charset': self._(u'Change the encoding of the text in document.'),

            'button_generate': self._(u'Generate PDF'),
            'button_remember': self._(u'Remember form'),
            'button_cancel': self._(u'Cancel'),
            'button_reset': self._(u'Reset'),
            }
        self.fields.update(self.values)

        # Go through all format strings.
        for name in [u'tocheader', u'tocfooter', u'header', u'footer']:
            self.fields[u'choose_' + name] = self._chooseformat(name)

        self.fields[u'select_style'] = self._select (u'style')
        self.fields[u'select_format'] = self._select (u'format')
        self.fields[u'select_linkstyle'] = self._select (u'linkstyle')
        self.fields[u'select_size'] = self._select (u'size')
        self.fields[u'select_jpeg'] = self._select (u'jpeg')
        self.fields[u'select_compression'] = self._select (u'compression')
        self.fields[u'select_toclevels'] = self._select (u'toclevels')
        self.fields[u'select_pagemode'] = self._select (u'pagemode')
        self.fields[u'select_pagelayout'] = self._select (u'pagelayout')
        self.fields[u'select_firstpage'] = self._select (u'firstpage')
        self.fields[u'select_charset'] = self._select (u'charset')

        form = """<p class="error">%(error)s</p>
<form method="post" action="">
<input type="hidden" name="action" value="%(action)s"/>
<table>
    <tr><td colspan="3">%(label_input)s</td></tr>
    <tr>
        <td class="label"><label>%(label_choose_style)s&nbsp;:</label></td>
        <td class="content">%(select_style)s</td>
        <td>%(help_choose_style)s</td>
    </tr>
    <tr>
        <td class="label"><label>%(label_titletext)s&nbsp;:</label></td>
        <td class="content"><input type="text" size="30" name="titletext" value="%(titletext)s" /></td>
        <td>%(help_titletext)s</td>
    </tr>
    %(seperator)s
    <tr><td colspan="3">%(label_output)s</td></tr>
    <tr>
        <td class="label"><label>%(label_format)s&nbsp;:</label></td>
        <td class="content">%(select_format)s</td>
        <td>%(help_format)s</td>
    </tr>
    <tr>
        <td class="label"><label>%(label_outputoptions)s&nbsp;:</label></td>
        <td colspan="2"><input type="checkbox" name="grayscale" value="checked" %(grayscale)s />%(label_grayscale)s&nbsp;
            <input type="checkbox" name="title" value="checked" %(title)s />%(label_titlepage)s<br />
            %(label_compression)s&nbsp:&nbsp;%(select_compression)s&nbsp;
            %(label_jpeg)s&nbsp;:&nbsp;%(select_jpeg)s</td>
    </tr>
    %(seperator)s
    <tr><td colspan="3">%(label_page)s</td></tr>
    <tr>
        <td class="label"><label>%(label_choose_size)s&nbsp;:</label></td>
        <td>%(select_size)s&nbsp;<br /><nobr>%(label_usersize)s&nbsp;:&nbsp;<input type="text" size="15" name="usersize" value="%(usersize)s" /></nobr></td>
        <td>%(help_choose_size)s<br />%(help_usersize)s</td>
    </tr>
    <tr>
        <td>&nbsp;</td>
        <td colspan="2"><input type="checkbox" name="duplex" value="checked" %(duplex)s />&nbsp;%(label_duplex)s&nbsp;
            <input type="checkbox" name="landscape" value="checked" %(landscape)s />&nbsp;%(label_landscape)s</td>
    </tr>
    <tr>
        <td class="label"><label>%(label_margin)s&nbsp;:</label></td>
        <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>
            <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>
            <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>
        <td>%(help_margin)s</td>
    </tr>
    %(choose_header)s
    %(choose_footer)s
    %(seperator)s
    <tr><td colspan="3">%(label_tableofcontents)s</td></tr>
    <tr>
        <td class="label"><label>%(label_no-toc)s&nbsp;:</label></td>
        <td class="checkbox"><input type="checkbox" name="no-toc" value="checked" %(no-toc)s /></td>
        <td>%(help_no-toc)s</td>
    </tr>
    <tr>
        <td class="label"><label>%(label_toclevels)s&nbsp;:</label></td>
        <td class="content">%(select_toclevels)s</td>
        <td>%(help_toclevels)s</td>
    </tr>
    <tr>
        <td>&nbsp;</td>
        <td><input type="checkbox" name="numbered" value="checked" %(numbered)s />&nbsp;%(label_numbered)s</td>
        <td>%(help_numbered)s</td>
    </tr>
    <tr>
        <td class="label"><label>%(label_toctitle)s&nbsp;:</label></td>
        <td class="content"><input type="text" size="30" name="toctitle" value="%(toctitle)s" /></td>
        <td>%(help_toctitle)s</td>
    </tr>
    %(choose_tocheader)s
    %(choose_tocfooter)s
    %(seperator)s
    <tr><td colspan="3">%(label_pdf)s</td></tr>
    <tr>
        <td class="label"><label>%(label_pagemode)s&nbsp;:</label></td>
        <td class="content">%(select_pagemode)s</td>
        <td>%(help_pagemode)s</td>
    </tr>
    <tr>
        <td class="label"><label>%(label_pagelayout)s&nbsp;:</label></td>
        <td class="content">%(select_pagelayout)s</td>
        <td>%(help_pagelayout)s</td>
    </tr>
    <tr>
        <td class="label"><label>%(label_firstpage)s&nbsp;:</label></td>
        <td class="content">%(select_firstpage)s</td>
        <td>%(help_firstpage)s</td>
    </tr>
    <tr>
        <td class="label"><label>%(label_no-links)s&nbsp;:</label></td>
        <td><input type="checkbox" name="no-links" value="checked" %(no-links)s /></td>
        <td>%(help_no-links)s</td>
    </tr>
    <tr>
        <td class="label"><label>%(label_linkstyle)s&nbsp;:</label></td>
        <td class="content">%(select_linkstyle)s</td>
        <td>%(help_linkstyle)s</td>
    </tr>
    <tr>
        <td class="label"><label>%(label_linkcolor)s&nbsp;:</label></td>
        <td class="content"><input type="text" size="6" name="linkcolor" value="%(linkcolor)s" /></td>
        <td>%(help_linkcolor)s</td>
    </tr>
    %(seperator)s
    <tr><td colspan="3">%(label_security)s</td></tr>
    <tr>
        <td class="label"><label>%(label_encryption)s&nbsp;:</label></td>
        <td><input type="checkbox" name="encryption" value="checked" %(encryption)s /></td>
        <td>%(help_numbered)s</td>
    </tr>
    <tr>
        <td class="label"><label>%(label_permissions)s&nbsp;:</label></td>
        <td><nobr><input type="checkbox" name="permissionprint" value="checked" %(permissionprint)s />&nbsp;%(label_permissionprint)s</nobr>&nbsp;
            <nobr><input type="checkbox" name="permissionmodify" value="checked" %(permissionmodify)s />&nbsp;%(label_permissionmodify)s</nobr><br />
            <nobr><input type="checkbox" name="permissioncopy" value="checked" %(permissioncopy)s />&nbsp;%(label_permissioncopy)s</nobr>&nbsp;
            <nobr><input type="checkbox" name="permissionannotate" value="checked" %(permissionannotate)s />&nbsp;%(label_permissionannotate)s</nobr></td>
        <td>%(help_permissions)s</td>
    </tr>
    <tr>
        <td class="label"><label>%(label_user-password)s&nbsp;:</label></td>
        <td class="content"><input type="password" size="30" name="user-password" value="%(user-password)s" /></td>
        <td>%(help_user-password)s</td>
    </tr>
    <tr>
        <td class="label"><label>%(label_owner-password)s&nbsp;:</label></td>
        <td class="content"><input type="password" size="30" name="owner-password" value="%(owner-password)s" /></td>
        <td>%(help_owner-password)s</td>
    </tr>
    %(seperator)s
    <tr><td colspan="3">%(label_expert)s</td></tr>
    <tr>
        <td class="label"><label>%(label_language)s&nbsp;:</label></td>
        <td class="content"><input type="text" size="6" name="language" value="%(language)s" /></td>
        <td>%(help_language)s</td>
    </tr>
    <tr>
        <td class="label"><label>%(label_charset)s&nbsp;:</label></td>
        <td class="content">%(select_charset)s</td>
        <td>%(help_charset)s</td>
    </tr>
    %(space)s
    <tr>
        <td><input type="hidden" name="debug" value="%(debug)s" /><input type="hidden" name="rev" value="%(rev)s" /></td>
        <td class="buttons" colspan="2">
            <input type="submit" name="generate_from_form" value="%(button_generate)s" />&nbsp;
            <input type="submit" name="remember" value="%(button_remember)s" />&nbsp;
            <input type="submit" name="cancel" value="%(button_cancel)s" />
        </td>
    </tr>
</table>
</form>""" % self.fields
        return Dialog (self.request, content=form)

    def run (self, pagename, request):
        """ Main dispatcher for the action."""
        self.pagename = pagename
        self.request = request
        self._ = self.request.getText
        self.form = self.request.form
        self.cfg = self.request.cfg

        # Canceled by user.
        if self.form.has_key(u'cancel'):
            return self.request.page.send_page(self.request)

        # Determine calling parameters.
        if self.form.get(u'debug', [u'0']) == [u'1']:
            self.debug = True

        # This dict contains all possible values.
        self.valid_options = {}

        self.valid_options[u'tocformats'] = {
            u'/': self._(u'1/N,2/N Arabic page numbers'),
            u':': self._(u'1/C,2/C Arabic chapter page numbers'),
            u'1': self._(u'1,2,3,...'),
            u'a': self._(u'a,b,c,...'),
            u'A': self._(u'A,B,C,...'),
            u'c': self._(u'Chapter title'),
            u'C': self._(u'Chapter page number'),
            u'd': self._(u'Date'),
            u'D': self._(u'Date + Time'),
            u'h': self._(u'Heading'),
            u'i': self._(u'i,ii,iii,iv,...'),
            u'I': self._(u'I,II,III,IV,...'),
            u't': self._(u'Title'),
            u'T': self._(u'Time'),
            u'.': self._(u'Blank'),
            # TODO: Not supported yet; u'l': self._(u'Logo image'),
        }
        self.valid_options[u'style'] = {
            u'webpage': self._(u'webpage'),
            u'book': self._(u'book'),
            u'continuous': self._(u'continuous'),
        }
        self.valid_options[u'size'] = {
            u'legal': self._(u'Legal (8.5x14in)'),
            u'a4': self._(u'A4 (210x297mm)'),
            u'letter': self._(u'Letter (8.5x11in)'),
            u'universal': self._(u'Universal (8.27x11in)'),
            u'': self._(u'User defined'),
        }
        self.valid_options[u'format'] = {
            u'pdf11': self._(u'PDF 1.1 (Acrobat 2.0)'),
            u'pdf12': self._(u'PDF 1.2 (Acrobat 3.0)'),
            u'pdf13': self._(u'PDF 1.3 (Acrobat 4.0)'),
            u'pdf14': self._(u'PDF 1.4 (Acrobat 5.0)'),
            # TODO: Not supported yet:
            #u'ps1': self._(u'PostScript Level 1'),
            #u'ps2': self._(u'PostScript Level 2'),
            #u'ps3': self._(u'PostScript Level 3'),
        }
        self.valid_options[u'linkstyle'] = {
            u'underline': self._(u'Underline'),
            u'plain': self._(u'Plain'),
        }
        self.valid_options[u'firstpage'] = {
            u'c1': self._(u'1st chapter'),
            u'p1': self._(u'1st page'),
            u'toc': self._(u'Contents'),
        }
        self.valid_options[u'jpeg'] = {
            u'0': self._(u'None'),
            u'50': self._(u' 50% (Good)'),
            u'55': u'55%', u' 60': u' 60%', u' 65': ' 65%', u' 70': ' 70%', u' 75': ' 75%',
            u'80': ' 80%', u' 85': ' 85%', u' 90': ' 90%', u' 95': ' 95%',
            u'100': self._(u'100% (Best)'),
        }
        self.valid_options[u'compression'] = {
            u'0': self._(u'None'),
            u'1': self._(u'1 (Fast)'),
            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',
            u'9': self._(u'9 (Best)'),
        }
        self.valid_options[u'toclevels'] = {
            u'0': self._(u'None'),
            u'1': u'1', u'2': '2', u'3': '3', u'4': '4'
        }
        self.valid_options[u'pagemode'] = {
            u'outline': self._(u'Outline'),
            u'document': self._(u'Document'),
            u'fullscreen': self._(u'Full-screen'),
        }
        self.valid_options[u'pagelayout'] = {
            u'single': self._(u'Single'),
            u'one': self._(u'One column'),
            u'twoleft': self._(u'Two column left'),
            u'tworight': self._(u'Two column right'),
        }
        self.valid_options[u'charset'] = {
            u'iso-8859-1': self._(u'ISO 8859-1'),
            u'iso-8859-2': self._(u'ISO 8859-2'),
            u'iso-8859-3': self._(u'ISO 8859-3'),
            u'iso-8859-4': self._(u'ISO 8859-4'),
            u'iso-8859-5': self._(u'ISO 8859-5'),
            u'iso-8859-6': self._(u'ISO 8859-6'),
            u'iso-8859-7': self._(u'ISO 8859-7'),
            u'iso-8859-8': self._(u'ISO 8859-8'),
            u'iso-8859-9': self._(u'ISO 8859-9'),
            u'iso-8859-14': self._(u'ISO 8859-14'),
            u'iso-8859-15': self._(u'ISO 8859-15'),
            u'cp-874': self._(u'cp-847'),
            u'cp-1250': self._(u'cp-1250'),
            u'cp-1251': self._(u'cp-1251'),
            u'cp-1252': self._(u'cp-1252'),
            u'cp-1253': self._(u'cp-1253'),
            u'cp-1254': self._(u'cp-1254'),
            u'cp-1255': self._(u'cp-1255'),
            u'cp-1256': self._(u'cp-1256'),
            u'cp-1257': self._(u'cp-1257'),
            u'cp-1258': self._(u'cp-1258'),
            u'koi-8r': self._(u'koi-8r'),
        }

        # Set translated name of table of contents as default.
        self.default_values[u'toctitle'] = self._(u'Contents')

        self.default_values[u'titletext'] = self.pagename

        # Make sure we create date and time strings in right format.
        if self.request.page.language:
            self.default_values[u'language'] = self.request.page.language
        else:
            self.default_values[u'language'] = self.make_isolang(self.cfg.__dict__.get (u'default_language', u'en'))

        self.values = {}

        # If the configuration variable 'createpdfdocument_validoptions' exists we update our
        # self.valid_options dict with these values.
        if getattr (self.request.cfg, u'createpdfdocument_validoptions', None):
            self.valid_options.update (self.request.cfg.createpdfdocument_validoptions)

        # If the configuration variable 'createpdfdocument_defaultvalues' exists we update our
        # self.default_values dict with these values.
        if getattr (self.request.cfg, u'createpdfdocument_defaultvalues', None):
            for key, value in self.request.cfg.createpdfdocument_defaultvalues.items():
                self.default_values[key] = value

        # Scan page to extract default values.
        self.set_page_default_values()

        # Create a PDF document direct without any user iteraction from default and page settings.
        if self.form.has_key(u'generate'):
            self.set_page_values()
            self.update_values(useform=False)
            return self.do_generate()

        # Create a PDF document from form settings.
        if self.form.has_key(u'generate_from_form'):
            self.update_values()
            return self.do_generate()

        if self.form.has_key(u'remember'):
            self.update_values()
            return self.do_remember()

        self.set_page_values()
        self.update_values(useform=False)
        return self.request.page.send_page (self.request, msg=self.makeform())

    def update_values(self, useform=True):
        """Preset values with they form values or defaults."""
        for key, default in self.default_values.items():
            # Modify value only if not already set.
            if not key in self.values:
                # If the form does not contain the value (e.g. for checkboxes) set the
                # default value (e.g. for checkboxes unset by default).
                if not key in self.form:
                    # Special processing for checkboxes in forms. If the key does not exists
                    # within the form it is not checked.
                    if key in self.form_checkbox and useform:
                        self.values[key] = u'unchecked'
                    else:
                        self.values[key] = default
                else:
                    self.values[key] = self.form[key][0]
        # Check if revision is an integer value.
        try:
            self.values[u'rev'] = int(self.values.get(u'rev', self.request.page.rev))
        except:
            self.values[u'rev'] = self.request.page.rev
        # Check if page revision exists.
        (pagefname, realrev, exists) = self.request.page.get_rev (rev=self.values[u'rev'])
        if exists:
            self.values[u'rev'] = realrev
        else:
            # Determine latest revision number.
            (pagefname, self.values[u'rev'], exists) = self.request.page.get_rev()

    def do_generate(self):
        """Create PDF document."""
        # Generate the HTML page using MoinMoin wiki engine.
        html = self.get_html()
        if html:
            if self.debug:
                self.request.http_headers()
                self.request.write(html)
            else:
                pdfdata = self.html2pdf(html)
                if pdfdata:
                    # Send as application/pdf the generated file by HTMLDOC
                    self.send_pdf(pdfdata)
                    
                    # MoinMoin1.6: send_closing_html() has to be called explicit.
                    # MoinMoin1.5: raise MoinMoinNoFooter exception to forbit creation of HTML footer.
                    if not 'send_closing_html' in self.request.theme.__dict__:
                        from MoinMoin.util import MoinMoinNoFooter
                        raise MoinMoinNoFooter

    def do_remember(self):
        """Create a message containing information about how to save the form values for future reuse."""
        save = u''
        for key, value in self.values.items():
            if key in [u'user-password', u'owner-password', u'rev', u'debug']:
                continue
            if key in self.default_values and value == self.default_values[key]:
                continue
            save += u'##pdf %s %s\n' % (key, value,)
        if save:
            msg = self._(u'Add follwing lines at the beginning of your page:') + u'<br/><pre>' + save + u'</pre>'
        else:
            msg = self._(u'All values correspond to they default. Nothing have to be saved.')
        return self.request.page.send_page (self.request, msg)

    def send_pdf (self, data):
        filename = self.pagename.replace (u'/', u'-') + u'-v' + str(self.values[u'rev']) + u'.pdf'

        # Send HTTP header.
        self.request.http_headers([
            'Content-Type: %s' % self.contenttype,
            'Content-Length: %d' % len(data),
            # TODO: fix the encoding here, plain 8 bit is not allowed
            # according to the RFCs There is no solution that is
            # compatible to IE except stripping non-ascii chars
            'Content-Disposition: inline; filename="%s"' % filename.encode(config.charset),
            ])

        # Send binary data.
        sio = StringIO.StringIO(data)
        shutil.copyfileobj (sio, self.request, 8192)
        
    def get_html(self):
        """Generate the HTML body of this page."""
        # Save page as HTML.
        newreq = RedirectOutputRequest(self.request)
        (html, errmsg) = newreq.run(rev = self.values.get(u'rev', None))
        if html:
            html = self.fixhtmlstr(html)
            # Make URLs absolute.
            # FIXME: Until MoinMoin is not XHTML compilant we can not use a XML parser
            # (e.g. expat) to transform the HTML document. In the meantime we try to
            # achive the same with regular expressions subtitution.
            base = self.request.getQualifiedURL()
            for htmlref in [u'src', u'href']:
                reurlref = r'(%s=[\'"])(/[^\'"]*)[\'"]' % (htmlref,)
                urlref = re.compile (reurlref, re.I)
                for match in urlref.finditer(html):
                    foundref = match.groups()
                    html = html.replace (foundref[0] + foundref[1], foundref[0] + base + foundref[1])
            # Rename title of the document.
            titletext_html = self.fixhtmlstr(wikiutil.escape(self.values['titletext']))
            html = re.compile(r'<title>[^<]+</title>').sub(u'<title>%s</title>' % titletext_html, html)
        else:
            self.error_msg(self._(u'Could not redirect HTML output for further processing:') + errmsg)

        return html

    def make_isolang (self, language):
        return language + u'_' + language.upper()

    def html2pdf (self, html):
        """Create a PDF document based on the current parameters."""
        # Not all os support environment variable definition in one line with the calling application.
        if os.name in ['posix', 'mac']:
            htmldocopts = [u'LANG=' + self.values[u'language'], u'HTMLDOC_NOCGI=1']
        else:
            htmldocopts = []
        # Determine UID to access ACL protected sites too (mandatory to download attached images).
        htmldocopts += [u'htmldoc', "--cookies", "MOIN_ID=" + self.request.user.id, u'--no-duplex']

        for key in [u'header', u'footer', u'tocheader', u'tocfooter']:
            self.values[key] = self.values.get (key + u'left', u'.') + self.values.get (key + u'middle', u'.') + self.values.get (key + u'right', u'.')

        permissions = []
        for opt, value in self.values.items():
            if opt in [u'language', u'debug', u'rev', u'titletext']:
                continue
            value = value.strip()
            if not value:
                continue
            # Skip options for header/footer configuration which differenciate between position (e.g. footerright or tocheadermiddle)
            if opt[:6] in [u'header', u'footer'] and opt[6:] or opt[:9] in [u'tocheader', u'tocfooter'] and opt[9:]:
                continue
            if opt in [u'style']:
                htmldocopts += [u'--' + value]
            elif opt in self.form_checkbox:
                if value == u'checked':
                    if opt[:10] == u'permission':
                        permissions += [opt[10:]]
                    # Reverse meaning of 'no-' options.
                    elif opt[:3] != u'no-':
                        htmldocopts += [u'--' + opt]
                elif opt[:3] == u'no-':
                    htmldocopts += [u'--' + opt]
            elif opt[:6] == u'margin':
                htmldocopts += [u'--' + opt[6:], value]
            else:
                htmldocopts += [u'--' + opt, value]
        if permissions:
            htmldocopts += [u'--permission', u','.join (permissions)]
        htmldocopts += [u'-']
        # Do not forget to escape all spaces!
        eschtmldocopts = [arg.replace(u' ', u'\\ ') for arg in htmldocopts]
        cmdstr = u' '.join(eschtmldocopts)
        errmsg = None

        pdf = None
        if self.debug:
            errmsg = self._(u'Command:') + u'<pre>' + wikiutil.escape(cmdstr) + u'</pre>'
        else:
            inp, out, err = os.popen3 (cmdstr, u'b')
            try:
                inp.write(html)
                inp.close()
            except:
                pass

            pdf = out.read()
            out.close()

            htmldocmsg = err.read()
            err.close()

            if os.name in ['posix', 'mac']:
                try:
                    # REMARK: Otherwise we get <defunct> processes.
                    os.wait()
                except OSError, e:
                    # 10: No child processes.
                    if e.errno != 10:
                        raise

            # Check for error message on STDOUT.
            if pdf[:8] == u'HTMLDOC ':
                htmldocmsg += pdf
                pdf = None

            if htmldocmsg:
                errmsg = self._(u'Command:') + u'<pre>' + wikiutil.escape(cmdstr) + u'</pre>' + self._('returned:') + u'<pre>' + \
                      wikiutil.escape(htmldocmsg).replace(u'\n', u'<br/>') + u'</pre>'

        # As it is difficult to get the htmldoc return code, we check for
        # error by checking the produced pdf length
        if not pdf and errmsg:
            self.error_msg(errmsg)
            return None
        elif pdf[:4] != '%PDF':
            self.error_msg(self._(u'Invalid PDF document generated') + wikiutil.escape(pdf[:80]))
            return None

        return pdf


def execute (pagename, request):
    try:
        CreatePdfDocument().run (pagename = pagename, request = request)
    except:
        raise

# vim:ts=4:et:sw=4:nu
