""" AtomRenderer.py Atom Renderer renders your blog in Atom 1.0 format without the need for ATOM flavour templates. It is based on Blake Winton / Will Guaraldi's RSS2Renderer. Note: pyBlosxom 1.3 will have support for a native ATOM flavour - so this plugin will only be useful for pyBlosxom 1.2 or earlier. Required configuration entries are: blog_title - the title of your blog blog_author - your name blog_email - your email address Optionally, you can specify: atom_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 linksshould match 2. this doesn't handle comments 3. this plugin requires the w3cdate plugin NOTE: This plugin *does* work with Folksonomy, and will report your tags as category elements in your entry. If you use mod_rewrite to change the address of your ATOM feed - for instance: www.mysite.com/atom, instead of: www.mysite.com/cgi-bin/pyblosxom.cgi/index.atom, you'll want to set py['atom_url'] in your configuration. AtomRenderer will use that as the "self" reference link instead of py['base_url']/py['atom_extension'] - so that your feed will validate properly using feedvalidator.org. """ __author__ = "Timothy C. Fanelli" __version__ = "0.1.0" __url__ = "http://www.timfanelli.com" __description__ = "ATOM 1.0 renderer." from Pyblosxom.renderers.base import RendererBase from Pyblosxom.tools import Stripper from xml.dom.minidom import Document import urlparse,time class AtomRenderer(RendererBase): # How long you want the simple description to be desc_length = 20 # 20 words, at the most for me 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/", 'atom': "http://www.w3.org/2005/Atom" } 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 _createFeed(self): # Start our ATOM document here self._doc = Document() d = self._doc feed = d.createElement('atom:feed') self._feed = feed self._addNameSpaces(feed, self.namespaces) d.appendChild(feed) w3ctime = time.strftime('%Y-%m-%dT%H:%M:%S%Z') # Add details about our blog here self._addText('atom:id', self._config['base_url'], feed) self._addText('atom:title', self._config['blog_title'], feed) self._addText('atom:updated', self._content[0]["w3cdate"], feed ) atomurl = self._config.get( 'atom_url', self._config['base_url'] + "/" + self._config.get('atom_extension','index.atom') ) linkelement = self._doc.createElement('atom:link') self._addAttr( linkelement,'href',self._urlEncode(atomurl ) ) self._addAttr( linkelement,'rel','self' ) feed.appendChild(linkelement) pagelinkelement = self._doc.createElement('atom:link') self._addAttr( pagelinkelement,'href',self._urlEncode(self._config['base_url']) ) feed.appendChild(pagelinkelement) authorelement = self._doc.createElement('atom:author') self._addText('atom:name', self._config.get('atom_author', self._config.get('blog_author')), authorelement) self._addText('atom:email', self._config.get('atom_email', self._config.get('blog_email')), authorelement) feed.appendChild(authorelement) 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 _createEntry(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('atom:entry') self._feed.appendChild(item) url = urlparse.urljoin(burl + "/", entry["file_path"]) url = self._urlEncode(url) self._addText('atom:title', entry['title'], item) self._addText('atom:updated', entry['w3cdate'], item) self._addText('atom:id', url, item) if entry.has_key('tags'): for tag in entry.getMetadata('tags').split(','): self._addElemAttr('atom:category', 'term', tag, item) linkelement = self._doc.createElement('atom:link') #self._addAttr( linkelement,'rel','self' ) self._addAttr( linkelement,'href',url ) item.appendChild(linkelement) self._addText('atom:summary', self._generateDesc(entry['body']), item) contentElement = self._doc.createElement( 'atom:content' ) self._addAttr( contentElement, 'type', 'html' ) if self.entry_type == 'CDATA': contentElement.appendChild( self._doc.createCDATASection( entry['body'] ) ) else: contentElement.appendChild( self._doc.createTextNode( entry['body'] ) ) item.appendChild( contentElement ) def render(self, header = 1): if self.rendered == 1: return self._data = self._request.getData() self._config = self._request.getConfiguration() self.addHeader('Content-Type', 'application/xml') self.showHeaders() self._createFeed() if self._config.get("num_entries", 0): max_entries = self._config["num_entries"] else: max_entries = 20 if self._content: if max_entries > len(self._content): num_entries = len(self._content) else: num_entries = max_entries for count in xrange(num_entries): self._createEntry(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 = "" % self._config["blog_encoding"] + text[text.find("\n"):] self.write(text) self.rendered = 1 def cb_renderer(args): import sys req = args['request'] http = req.getHttp() conf = req.getConfiguration() ext = conf.get("atom_extension", "/index.atom") if http['PATH_INFO'].endswith( ext ): http['PATH_INFO'] = http['PATH_INFO'][:-len(ext)] return AtomRenderer(req, conf.get('stdoutput', sys.stdout))