# -*- coding: latin-1 -*- """ Post Markup Author: Will McGugan (http://www.willmcgugan.com) """ __version__ = "1.0.4" import re from urllib import quote, unquote, quote_plus from urlparse import urlparse, urlunparse from copy import copy pygments_available = True try: from pygments import highlight from pygments.lexers import get_lexer_by_name, ClassNotFound from pygments.formatters import HtmlFormatter except ImportError: pygments_available = False def create(include=None, exclude=None, use_pygments=True): """Create a postmarkup object that coverts bbcode to XML snippets. include -- List or similar iterable containing the names of the tags to use If omitted, all tags will be used exclude -- List or similar iterable containing the names of the tags to exclude. If omitted, no tags will be excluded use_pygments -- If True, Pygments (http://pygments.org/) will be used for the code tag, otherwise it will use
code
""" markup = PostMarkup() def add_tag(name, tag_class, *args): if include is None or name in include: if exclude is not None and name in exclude: return markup.add_tag(name, tag_class, *args) add_tag(u'b', SimpleTag, u'b', u'strong') add_tag(u'i', SimpleTag, u'i', u'em') add_tag(u'u', SimpleTag, u'u', u'u') add_tag(u's', SimpleTag, u's', u'strike') add_tag(u'link', LinkTag, u'link') add_tag(u'url', LinkTag, u'url') add_tag(u'quote', QuoteTag) add_tag(u'img', ImgTag, u'img') add_tag(u'wiki', SearchTag, u'wiki', u"http://en.wikipedia.org/wiki/Special:Search?search=%s", u'wikipedia.com') add_tag(u'google', SearchTag, u'google', u"http://www.google.com/search?hl=en&q=%s&btnG=Google+Search", u'google.com') add_tag(u'dictionary', SearchTag, u'dictionary', u"http://dictionary.reference.com/browse/%s", u'dictionary.com') add_tag(u'dict', SearchTag, u'dict', u"http://dictionary.reference.com/browse/%s", u'dictionary.com') add_tag(u'list', ListTag) add_tag(u'*', ListItemTag) if use_pygments: assert pygments_available, "Could Not import pygments (http://pygments.org/)" add_tag(u'code', PygmentsCodeTag, u'code') else: add_tag(u'code', SimpleTag, u'code', u'pre') return markup _bbcode_postmarkup = None def render_bbcode(bbcode, encoding="ascii"): """Renders a bbcode string in to XHTML. This is a shortcut if you don't need to customize any tags. bbcode -- A string containing the bbcode encoding -- If bbcode is not unicode, then then it will be encoded with this encoding (defaults to 'ascii'). Ignore the coding if you already have a unicode string """ global _bbcode_postmarkup if _bbcode_postmarkup is None: _bbcode_postmarkup = create() return _bbcode_postmarkup(bbcode, encoding) re_html=re.compile('<.*?>|\&.*?\;') def textilize(s): """Remove markup from html""" return re_html.sub("", s) re_excerpt = re.compile(r'\[".*?\]+?.*?\[/".*?\]+?', re.DOTALL) re_remove_markup = re.compile(r'\[.*?\]', re.DOTALL) def remove_markup(post): """Removes html tags from a string.""" return re_remove_markup.sub("", post) def get_excerpt(post): """Returns an excerpt between ["] and [/"] post -- BBCode string""" match = re_excerpt.search(post) if match is None: return "" excerpt = match.group(0) excerpt = excerpt.replace(u'\n', u"
") return remove_markup(excerpt) class TagBase(object): """ Base class for a Post Markup tag. """ def __init__(self, name): self.name = name self.params = None self.auto_close = False self.enclosed = False self.open_pos = None self.close_pos = None self.raw = None def open(self, open_pos): """Called when the tag is opened. Should return a string or a stringifyable object.""" self.open_pos = open_pos return '' def close(self, close_pos, content): """Called when the tag is closed. Should return a string or a stringifyable object.""" self.close_pos = close_pos self.content = content return '' def get_tag_contents(self): """Gets the contents of the tag.""" content_elements = self.content[self.open_pos+1:self.close_pos] contents = u"".join([unicode(element) for element in content_elements\ if isinstance(element, StringToken)]) contents = textilize(contents) return contents def get_raw_tag_contents(self): """Gets the raw contents (includes html tags) of the tag.""" content_elements = self.content[self.open_pos+1:self.close_pos] contents = u"".join(element.raw for element in content_elements) return contents # A proxy object that calls a callback when converted to a string class TagStringify(object): def __init__(self, callback, raw): self.callback = callback self.raw = raw def __unicode__(self): return self.callback() def __repr__(self): return self.__unicode__() class SimpleTag(TagBase): """Simple substitution tag.""" def __init__(self, name, substitute): TagBase.__init__(self, name) self.substitute = substitute def open(self, open_pos): """Called to render the opened tag.""" return u"<%s>"%(self.substitute) def close(self, close_pos, content): """Called to render the closed tag.""" return u""%(self.substitute) class LinkTag(TagBase): """Tag that generates a link ().""" def __init__(self, name): TagBase.__init__(self, name) def open(self, open_pos): self.open_pos = open_pos return TagStringify(self._open, self.raw) def close(self, close_pos, content): self.close_pos = close_pos self.content = content return TagStringify(self._close, self.raw) def _open(self): if self.params: url = self.params else: url = self.get_tag_contents() self.domain = "" #Unquote the url self.url = unquote(url) #Disallow javascript links if u"javascript:" in self.url.lower(): return "" #Disallow non http: links url_parsed = urlparse(self.url) if url_parsed[0] and url_parsed[0].lower() != u'http': return "" #Prepend http: if it is not present if not url_parsed[0]: self.url="http://"+self.url url_parsed = urlparse(self.url) #Get domain self.domain = url_parsed[1].lower() #Remove www for brevity if self.domain.startswith(u'www.'): self.domain = self.domain[4:] #Quote the url self.url="http:"+urlunparse( map(quote, (u"",)+url_parsed[1:]) ) #Sanity check if not self.url: return u"" if self.domain: return u''%self.url else: return u"" def _close(self): if self.domain: return u''+self.annotate_link(self.domain) else: return u'' def annotate_link(self, domain): """Annotates a link with the domain name. Override this to disable or change link annotation. """ return u" [%s]"%domain class QuoteTag(TagBase): """ Generates a blockquote with a message regarding the author of the quote. """ def __init__(self): TagBase.__init__(self, 'quote') def open(self, open_pos): return u'
%s
'%(self.params) def close(self, close_pos, content): return u"
" class SearchTag(TagBase): """ Creates a link to a search term. """ def __init__(self, name, url, label=u""): TagBase.__init__(self, name) self.url = url self.search = u"" self.label = label or name def __unicode__(self): link = u''%self.url if u'%' in link: return link%quote_plus(self.get_tag_contents().encode('latin-1')) else: return link def open(self, open_pos): self.open_pos = open_pos return TagStringify(self._open, self.raw) def close(self, close_pos, content): self.close_pos = close_pos self.content = content return TagStringify(self._close, self.raw) def _open(self): if self.params: search=self.params else: search=self.get_tag_contents() link = u''%self.url if u'%' in link: return link%quote_plus(search.encode('latin-1')) else: return link def _close(self): if self.label: return u''+self.annotate_link(self.label) else: return u'' def annotate_link(self, domain): return u" [%s]"%domain class ImgTag(TagBase): def __init__(self, name): TagBase.__init__(self, name) self.enclosed=True def open(self, open_pos): self.open_pos = open_pos return TagStringify(self._open, self.raw) def close(self, close_pos, content): self.close_pos = close_pos self.content = content return TagStringify(self._close, self.raw) def _open(self): contents = self.get_raw_tag_contents() contents = contents.replace(u'"', "%22") return u'
'%(contents) def _close(self): return u"
" class ListTag(TagBase): """Simple substitution tag.""" def __init__(self): TagBase.__init__(self, "list") def open(self, open_pos): """Called to render the opened tag.""" if self.params == "1": self.close_tag = u"" return u"
    " elif self.params == "a": self.close_tag = u"
" return u'
    ' elif self.params == "A": self.close_tag = u"
" return u'
    ' else: self.close_tag = u"" return u"