"""
The rss2renderer is a renderer that will render your blog in RSS2
format.  This allows you to have RSS2 syndication WITHOUT writing
flavour templates for RSS2.

The following are required properties in your config.py file:

   blog_title        - the title of your blog
   blog_author       - your name
   blog_email        - your email address
   blog_description  - the description of your blog
   blog_language     - the language code for your blog
   blog_encoding     - the encoding of your blog (defaults to utf-8)

Optionally, you can specify:

   rss2_extension    - the extension (defaults to "/index.rss2") that
                       causes this renderer to be used

Miscellaneous notes about this plugin:

1. the Content-Type we return is "application/xml" so your links
   should match
2. this doesn't handle comments
3. this plugin requires pyxml be installed

FIXME - probably needs more information in this help portion!


Originally written by Blake Winton.
Overhauled by Will Guaraldi.

Just for clarity sake, Blake wrote the majority of the code, but Will is
going to take responsibility to maintain the code going forward (unless
Blake really wants to--doesn't bother Will either way).

Revisions:
1.4 - (September 15, 2004) Fixed encoding issues (thanks Aslak!)
1.3 - (September 14, 2004) Fixed the content type (thanks Gabor!)
                           Fixed spaces in the links (thanks Brett!)
1.2 - (September 13, 2004) Fixed minor issue causing invalidation (thanks Brett!)
1.1 - (September 12, 2004) Will's overhaul.
1.0 - Blake's original writing.
"""
__author__ = "Blake Winton"
__version__ = "1.4 (15 September, 2004)"
__url__ = "http://www.bluesock.org/~willg/pyblosxom/"
__description__ = "RSS2 renderer."

from Pyblosxom.renderers.base import RendererBase
from Pyblosxom.tools import Stripper
from xml.dom.minidom import Document
import urlparse

class RSS2Renderer(RendererBase):
    """
    This renderer is to create valid RSS2 documents without the need for a
    pyblosxom template. I mostly expect you to know what you are doing, before
    attempting this
    """
    # How long you want the simple description to be
    desc_length = 20 # 20 words, at the most for me

    # Create the big html? <content:encoded>, yes, then 1, else 0
    create_entry = 1
    entry_type = 'CDATA' # or 'escaped' - choose your poison

    # Namespaces for you to pick and choose
    namespaces = {
        'admin': "http://webns.net/mvcb/",
        'content': "http://purl.org/rss/1.0/modules/content/",
        'creativeCommons': "http://backend.userland.com/creativeCommonsRssModule",
        'dc': "http://purl.org/dc/elements/1.1/",
        'rdf': "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
        'html': "http://www.w3.org/1999/html",
        'slash': "http://purl.org/rss/1.0/modules/slash/",
    }

    def _addText(self, element, text, baseElement):
        e = self._doc.createElement(element)
        e.appendChild(self._doc.createTextNode(text))
        baseElement.appendChild(e)
        return e

    def _addCDATA(self, element, text, baseElement):
        e = self._doc.createElement(element)
        e.appendChild(self._doc.createCDATASection(text))
        baseElement.appendChild(e)
        return e

    def _addElemAttr(self, element, attr, content, baseElement, text = None):
        e = self._doc.createElement(element)
        e.setAttribute(attr, content)
        if text:
            e.appendChild(self._doc.createTextNode(text))
        baseElement.appendChild(e)
        return e

    def _addAttr(self, element, attr, content):
        element.setAttribute(attr, content)

    def _addNameSpaces(self, element, namespace_dict):
        for attr in namespace_dict:
            self._addAttr(element, 'xmlns:' + attr, namespace_dict[attr])

    def _urlEncode(self, txt):
        # I'm doing it here because it's only a partial url-encoding
        txt = txt.replace(" ", "%20")
        return txt

    def _createChannel(self):
        # Start our RSS document here
        self._doc = Document()
        d = self._doc
        rss = d.createElement('rss')
        rss.setAttribute('version', '2.0')
        self._addNameSpaces(rss, self.namespaces)
        d.appendChild(rss)
        self._channel = d.createElement('channel')
        channel = self._channel
        rss.appendChild(channel)

        # Add details about our blog here
        self._addText('title', self._config['blog_title'], channel)
        self._addText('link', self._urlEncode(self._config['base_url']), channel)
        self._addText('description', self._config['blog_description'], channel)
        self._addText('language', self._config['blog_language'], channel)
        self._addText('ttl', '60', channel)
        self._addText('dc:creator', self._config['blog_author'], channel)
        self._addElemAttr('admin:generatorAgent', \
                          'rdf:resource', \
                          'http://roughingit.subtlehints.net/pyblosxom', \
                          channel)
        self._addElemAttr('admin:errorReportsTo', \
                          'rdf:resource', \
                          "mailto:" + self._config.get("blog_email", "none"), \
                          channel)

    def _generateDesc(self, html):
        s = Stripper()
        s.feed(html)
        str = s.gettext()
        frag = str.split()
        if len(frag) > self.desc_length:
            frag = frag[:self.desc_length]
            frag.append('...')
        return ' '.join(frag)

    def _createItem(self, entry):
        burl = self._config['base_url']

        d = urlparse.urlsplit(self._config['base_url'])
        domain = '%s://%s' % (d[0], d[1])
        item = self._doc.createElement('item')
        self._channel.appendChild(item)
        self._addText('title', entry['title'], item)
        self._addElemAttr('guid', 'isPermaLink', 'false', item, entry['file_path'])
        url = urlparse.urljoin(burl + "/", entry["file_path"])
        self._addText('link', self._urlEncode(url), item)

        # Text entry
        self._addText('description', self._generateDesc(entry['body']), item)
        if self.create_entry:
            if self.entry_type == 'CDATA':
                self._addCDATA('content:encoded', entry['body'], item)
            else:
                self._addText('content:encoded', entry['body'], item)
        # Metadata stuff
        # Category
        if entry['path'].strip():
            # category or dc:subject, but NOT both
            self._addElemAttr('category', 'domain', domain, item, entry['path'])
            #self._addText('dc:subject', entry['path'], item)
        self._addText('dc:date', entry['w3cdate'], item)

    def render(self, header = 1):
        self._data = self._request.getData()
        self._config = self._request.getConfiguration()
        self.addHeader('Content-Type', 'application/xml')
        self.showHeaders()
        self._createChannel()

        if self._config['num_entries'] > len(self._content):
            num_entries = len(self._content)
        else:
            num_entries = self._config['num_entries']

        for count in xrange(num_entries):
            self._createItem(self._content[count])

        # We are now ready to present the xml

        # FIXME this is totally hokey, but if I pass the encoding into
        # toxml, then it tries to convert the data to the new encoding
        # and assumes the data is ascii (which is wrong).
        text = self._doc.toxml()
        if self._config.has_key("blog_encoding"):
            text = "<?xml version=\"1.0\" encoding=\"%s\" ?>" % self._config["blog_encoding"] + text[text.find("\n"):]
            
        self.write(text)

def cb_renderer(args):
    import sys
    req = args['request']
    http = req.getHttp()
    conf = req.getConfiguration()

    ext = conf.get("rss2_extension", "/index.rss2")
    if http['PATH_INFO'].endswith( ext ):
        http['PATH_INFO'] = http['PATH_INFO'][:-len(ext)]
        return RSS2Renderer(req, conf.get('stdoutput', sys.stdout))

# vim: tabstop=4 shiftwidth=4 expandtab
