# 
# $Id: ZReST.py 68978 2006-07-05 16:12:23Z andreasjung $
#
''' ReStructuredText Product for Zope

This Product stores two texts - a "source" text in ReStructureText format,
and a HTML "formatted" version of that text.

'''
import docutils.core, docutils.io

from Globals import InitializeClass, DTMLFile
from OFS.SimpleItem import Item
from OFS.PropertyManager import PropertyManager
from OFS.History import Historical, html_diff
from Acquisition import Implicit
from Persistence import Persistent
from AccessControl import ClassSecurityInfo
from AccessControl import ModuleSecurityInfo
from DateTime.DateTime import DateTime
from App.config import getConfiguration 
import sys
modulesecurity = ModuleSecurityInfo()

modulesecurity.declareProtected('View management screens',
    'manage_addZReSTForm')
manage_addZReSTForm = DTMLFile('dtml/manage_addZReSTForm', globals())

modulesecurity.declareProtected('Add Z Roundups', 'manage_addZReST')
def manage_addZReST(self, id, file='', REQUEST=None):
    """Add a ZReST product """
    # validate the instance_home
    self._setObject(id, ZReST(id))
    self._getOb(id).manage_upload(file)
    if REQUEST is not None:
        return self.manage_main(self, REQUEST)

class Warnings:
    def __init__(self):
        self.messages = []
    def write(self, message):
        self.messages.append(message)

class ZReST(Item, PropertyManager, Historical, Implicit, Persistent):
    '''An instance of this class provides an interface between Zope and
       ReStructuredText for one text.
    '''
    meta_type =  'ReStructuredText Document'
    security = ClassSecurityInfo()
    _v_formatted = _v_warnings = None

    def __init__(self, id,output_encoding=None,
                 input_encoding=None):
        self.id = id
        self.title = id
        self.stylesheet = 'default.css'
        self.report_level = '2'
        self.source = ''

        from reStructuredText import default_output_encoding, \
                                     default_input_encoding
        self.input_encoding = input_encoding or \
                              default_input_encoding
        self.output_encoding = output_encoding  or \
                               default_output_encoding

    # define the properties that define this object
    _properties = (
        {'id':'stylesheet', 'type': 'string', 'mode': 'w',
            'default': 'default.css'},
        {'id':'report_level', 'type': 'string', 'mode': 'w', 'default': '2'},
        {'id':'input_encoding', 'type': 'string', 'mode': 'w', 'default': 'iso-8859-15'},
        {'id':'output_encoding', 'type': 'string', 'mode': 'w', 'default': 'iso-8859-15'},
    )
    property_extensible_schema__ = 0

    # define the tabs for the management interface
    manage_options= ( {'label': 'Edit', 'action':'manage_main'},
                      {'label': 'View', 'action':'index_html'},
                      {'label': 'Source', 'action':'source_txt'},
                    ) \
        + PropertyManager.manage_options \
        + Historical.manage_options \
        + Item.manage_options

    # access to the source text and formatted text
    security.declareProtected('View', 'index_html')
    def index_html(self, REQUEST=None):
        ''' Getting the formatted text
        '''
        if REQUEST is not None:
            REQUEST.RESPONSE.setHeader('content-type', 'text/html; charset=%s' % self.output_encoding)
        return self.render()

    security.declareProtected('View', 'source_txt')
    def source_txt(self, REQUEST=None):
        ''' Getting the source text
        '''
        if REQUEST is not None:
            REQUEST.RESPONSE.setHeader('content-type', 'text/plain; charset=%s' % self.input_encoding)
        return self.source

    # edit form, which is also the primary interface
    security.declareProtected('Edit ReStructuredText', 'manage_main')
    manage_main = DTMLFile('dtml/manage_editForm', globals())

    # edit action
    security.declareProtected('Edit ReStructuredText', 'manage_edit')
    def manage_edit(self, data, SUBMIT='Change',dtpref_cols='50',
                    dtpref_rows='20', REQUEST=None):
        '''Alias index_html to roundup's index
        '''
        if self._size_changes.has_key(SUBMIT):
            return self._er(data, SUBMIT, dtpref_cols, dtpref_rows, REQUEST)
        if data != self.source:
            self.source = data
            self._clear_cache()

        if REQUEST is not None:
            message="Saved changes."
            return self.manage_main(self, REQUEST, manage_tabs_message=message)

    # handle edit window size changes
    _size_changes = {
        'Bigger': (5,5),
        'Smaller': (-5,-5),
        'Narrower': (0,-5),
        'Wider': (0,5),
        'Taller': (5,0),
        'Shorter': (-5,0),
    }
    def _er(self, data, SUBMIT, dtpref_cols, dtpref_rows, REQUEST):
        dr,dc = self._size_changes[SUBMIT]
        rows = str(max(1, int(dtpref_rows) + dr))
        cols = str(dtpref_cols)
        if cols.endswith('%'):
           cols = str(min(100, max(25, int(cols[:-1]) + dc))) + '%'
        else:
           cols = str(max(35, int(cols) + dc))
        e = (DateTime("GMT") + 365).rfc822()
        setCookie = REQUEST["RESPONSE"].setCookie
        setCookie("dtpref_rows", rows, path='/', expires=e)
        setCookie("dtpref_cols", cols, path='/', expires=e)
        REQUEST.other.update({"dtpref_cols":cols, "dtpref_rows":rows})
        return self.manage_main(self, REQUEST, __str__=self.quotedHTML(data))

    security.declarePrivate('quotedHTML')
    def quotedHTML(self,
                   text=None,
                   character_entities=(
                       (('&'), '&amp;'),
                       (("<"), '&lt;' ),
                       ((">"), '&gt;' ),
                       (('"'), '&quot;'))): #"
        if text is None: text=self.read_raw()
        for re,name in character_entities:
            if text.find(re) >= 0: text=name.join(text.split(re))
        return text

    security.declarePrivate('_clear_cache')
    def _clear_cache(self):
        """ Forget results of rendering.
        """
        try:
            del self._v_formatted
        except AttributeError:
            pass
        try:
            del self._v_warnings
        except AttributeError:
            pass

    # handle uploads too
    security.declareProtected('Edit ReStructuredText', 'manage_upload')
    def manage_upload(self, file='', REQUEST=None):
        ''' Replaces the current source with the upload.
        '''
        if isinstance(file, type('')):
            self.source = file
        else:
            self.source = file.read()
        self._clear_cache()

        if REQUEST is not None:
            message="Saved changes."
            return self.manage_main(self, REQUEST, manage_tabs_message=message)

    security.declarePrivate('render')
    def render(self):
        ''' Render the source to HTML
        '''
        if self._v_formatted is None:
            # format with strings
            pub = docutils.core.Publisher()
            pub.set_reader('standalone', None, 'restructuredtext')
            pub.set_writer('html')

            # go with the defaults
            pub.get_settings()

            # this is needed, but doesn't seem to do anything
            pub.settings._destination = ''

            # use the stylesheet chosen by the user
            pub.settings.stylesheet = self.stylesheet

            # set the reporting level to something sane
            pub.settings.report_level = int(self.report_level)

            # disallow use of the .. include directive for security reasons
            pub.settings.file_insertion_enabled = 0

            # disallow insertion of raw data through for security reasons
            pub.settings.raw_enabled = 0

            # don't break if we get errors
            pub.settings.halt_level = 6

            # remember warnings
            pub.settings.warning_stream = Warnings()

            pub.source = docutils.io.StringInput(
                source=self.source, encoding=self.input_encoding)

            # output - not that it's needed
            pub.settings.output_encoding = self.output_encoding
            pub.destination = docutils.io.StringOutput(
                encoding=self.output_encoding)

            # parse!
            document = pub.reader.read(pub.source, pub.parser, pub.settings)

            # transform
            pub.apply_transforms(document)

            self._v_warnings = ''.join(pub.settings.warning_stream.messages)

            if document.children:
                item = document.children[0]
                if item.tagname == 'title':
                    self.title = item.children[0].astext()

            # do the format
            self._v_formatted = pub.writer.write(document, pub.destination)

        return self._v_formatted


    security.declareProtected('Edit ReStructuredText', 'PUT', 'manage_FTPput')
    def PUT(self, REQUEST, RESPONSE):
        ''' Handle HTTP PUT requests
        '''
        data = REQUEST.get('BODY', '')
        if data != self.source:
            if data.startswith('.. '):
                data = data.splitlines()
                new = []
                for i in range(len(data)):
                    line = data[i]
                    if not line.startswith('.. '):
                        break
                    if line.startswith('.. stylesheet='):
                        self.stylesheet = line.split('=')[1]
                    elif line.startswith('.. report_level='):
                        self.report_level = line.split('=')[1]
                    else:
                        pass # ignore
                data = '\n'.join(new) + '\n'.join(data[i:])
            self.source = data
        RESPONSE.setStatus(204)
        return RESPONSE        

    manage_FTPput = PUT

    def manage_FTPget(self):
        ''' Get source for FTP download
        '''
        self.REQUEST.RESPONSE.setHeader('Content-Type', 'text/plain')
        s = [
            '.. This is a ReStructuredText Document. Initial comment lines '
                '(".. ") will be stripped.',
            '.. stylesheet='+self.stylesheet,
            '.. report_level='+self.report_level
        ]
        if self._v_warnings:
            s.append('.. ')
            s.append('.. ' + '\n.. '.join(self._v_warnings.splitlines()))
        s.append('.. ')
        return '\n'.join(s) + '\n' + self.source

    def __str__(self):
        ''' Stringfy .. return the source
        '''
        return self.quotedHTML(self.source)

    __call__ = __str__

    def PrincipiaSearchSource(self):
        ''' Support for searching - the document's contents are searched.
        '''
        return self.source

    def manage_historyCompare(self, rev1, rev2, REQUEST,
                              historyComparisonResults=''):
        return ZReST.inheritedAttribute('manage_historyCompare')(
            self, rev1, rev2, REQUEST,
            historyComparisonResults=html_diff(rev1.source, rev2.source))

    def manage_editProperties(self, REQUEST):
        """ re-render the page after changing the properties (encodings!!!) """
        result = PropertyManager.manage_editProperties(self, REQUEST)        
        self._clear_cache()
        return result


InitializeClass(ZReST)
modulesecurity.apply(globals())


# vim: set filetype=python ts=4 sw=4 et si
