changeset 1683:959d9a202b6e

merged
author Jaiditya Mathur <jaiditya.mathur@gmail.com>
date Tue, 14 Aug 2012 00:34:21 +0530
parents 71f843bae0eb (current diff) 36ae694c4122 (diff)
children 31c881977e5d
files
diffstat 18 files changed, 832 insertions(+), 109 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Mon Aug 13 21:08:53 2012 +0530
+++ b/.hgignore	Tue Aug 14 00:34:21 2012 +0530
@@ -27,3 +27,5 @@
 .~$
 ^docs/devel/api/modules.rst
 ^upload.py
+^build/
+\..*sw[op]$
--- a/MoinMoin/apps/frontend/views.py	Mon Aug 13 21:08:53 2012 +0530
+++ b/MoinMoin/apps/frontend/views.py	Tue Aug 14 00:34:21 2012 +0530
@@ -344,7 +344,9 @@
     return partial(add_presenter, view=view, add_trail=add_trail, abort404=abort404)
 
 
-@frontend.route('/<itemname:item_name>', defaults=dict(rev=CURRENT), methods=['GET'])
+# The first form accepts POST to allow modifying behavior like modify_item.
+# The second form only accpets GET since modifying a historical revision is not allowed (yet).
+@frontend.route('/<itemname:item_name>', defaults=dict(rev=CURRENT), methods=['GET', 'POST'])
 @frontend.route('/+show/+<rev>/<itemname:item_name>', methods=['GET'])
 def show_item(item_name, rev):
     flaskg.user.add_trail(item_name)
@@ -354,24 +356,7 @@
         item = Item.create(item_name, rev_id=rev)
     except AccessDenied:
         abort(403)
-    show_revision = rev != CURRENT
-    show_navigation = False # TODO
-    first_rev = last_rev = None # TODO
-    if isinstance(item, NonExistent):
-        status = 404
-    else:
-        status = 200
-    content = render_template('show.html',
-                              item=item, item_name=item.name,
-                              rev=item.rev,
-                              contenttype=item.contenttype,
-                              first_rev_id=first_rev,
-                              last_rev_id=last_rev,
-                              data_rendered=Markup(item.content._render_data()),
-                              show_revision=show_revision,
-                              show_navigation=show_navigation,
-                             )
-    return Response(content, status)
+    return item.do_show(rev)
 
 
 @frontend.route('/+show/<itemname:item_name>')
@@ -513,66 +498,6 @@
     return item.do_modify()
 
 
-@frontend.route('/+blog/+<rev>/<itemname:item_name>', methods=['GET'])
-@frontend.route('/+blog/<itemname:item_name>', defaults=dict(rev=CURRENT), methods=['GET'])
-def show_blog(item_name, rev):
-    """
-    Show a blog item and a list of blog entries below it.
-
-    If supertag GET-parameter is defined, the list of blog entries consists only
-    of those entries that contain the supertag value in their lists of tags.
-    """
-    supertag = request.values.get('supertag')
-    flaskg.user.add_trail(item_name)
-    try:
-        item = Item.create(item_name, rev_id=rev)
-    except AccessDenied:
-        abort(403)
-    if isinstance(item, NonExistent):
-        abort(404, item_name)
-    prefix = item_name + u'/'
-    current_timestamp = int(time.time())
-    terms = [Term(WIKINAME, app.cfg.interwikiname),
-             # Only sub items of this item
-             Prefix(NAME_EXACT, prefix),
-             # Filter out those items that do not have a PTIME meta or PTIME is in the future.
-             DateRange(PTIME, start=None, end=datetime.utcfromtimestamp(current_timestamp)),
-            ]
-    if supertag:
-        terms.append(Term(TAGS, supertag))
-    query = And(terms)
-    revs = flaskg.storage.search(query, sortedby=[PTIME], reverse=True, limit=None)
-    blog_entry_items = [Item.create(rev.meta[NAME], rev_id=rev.revid) for rev in revs]
-    return render_template('blog.html',
-                           item_name=item.name,
-                           blog_item=item,
-                           blog_entry_items=blog_entry_items,
-                           supertag=supertag,
-                          )
-
-@frontend.route('/+blog_entry/+<rev>/<itemname:item_name>', methods=['GET'])
-@frontend.route('/+blog_entry/<itemname:item_name>', defaults=dict(rev=CURRENT), methods=['GET'])
-def show_blog_entry(item_name, rev):
-    flaskg.user.add_trail(item_name)
-    blog_item_name = item_name.rsplit('/', 1)[0]
-    if blog_item_name == item_name:
-        abort(403)
-    try:
-        item = Item.create(item_name, rev_id=rev)
-        blog_item = Item.create(blog_item_name)
-    except AccessDenied:
-        abort(403)
-    if isinstance(item, NonExistent):
-        abort(404, item_name)
-    if isinstance(blog_item, NonExistent):
-        abort(404, blog_item_name)
-    return render_template('blog_entry.html',
-                           item_name=item.name,
-                           blog_item=blog_item,
-                           blog_entry_item=item,
-                          )
-
-
 class TargetChangeForm(BaseChangeForm):
     target = RequiredText.using(label=L_('Target')).with_properties(placeholder=L_("The name of the target item"))
 
--- a/MoinMoin/constants/contenttypes.py	Mon Aug 13 21:08:53 2012 +0530
+++ b/MoinMoin/constants/contenttypes.py	Tue Aug 14 00:34:21 2012 +0530
@@ -24,6 +24,7 @@
         ('text/x.moin.wiki;charset=utf-8', 'Wiki (MoinMoin)'),
         ('text/x.moin.creole;charset=utf-8', 'Wiki (Creole)'),
         ('text/x-mediawiki;charset=utf-8', 'Wiki (MediaWiki)'),
+        ('text/x-markdown;charset=utf-8', 'Markdown'),
         ('text/x-rst;charset=utf-8', 'ReST'),
         ('application/docbook+xml;charset=utf-8', 'DocBook'),
         ('text/html;charset=utf-8', 'HTML'),
--- a/MoinMoin/constants/forms.py	Mon Aug 13 21:08:53 2012 +0530
+++ b/MoinMoin/constants/forms.py	Tue Aug 14 00:34:21 2012 +0530
@@ -25,6 +25,9 @@
 
 WIDGET_SELECT = u'select'
 
+WIDGET_READONLY_STRING_LIST = u'readonly_string_list'
+WIDGET_READONLY_ITEM_LINK_LIST = u'readonly_item_link_list'
+
 WIDGET_MULTI_COLUMN_FORM = u'multi_column_form'
 WIDGET_TABBED_FORM = u'tabbed_form'
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/converter/_tests/test_markdown_in.py	Tue Aug 14 00:34:21 2012 +0530
@@ -0,0 +1,145 @@
+# Copyright: 2008 MoinMoin:BastianBlank
+# Copyright: 2012 MoinMoin:AndreasKloeckner
+# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
+
+"""
+MoinMoin - Tests for MoinMoin.converter.markdown_in
+"""
+
+
+import re
+
+from MoinMoin.util.tree import moin_page, xlink
+
+from ..markdown_in import Converter
+
+
+class TestConverter(object):
+    namespaces = {
+        moin_page: '',
+        xlink: 'xlink',
+    }
+
+    output_re = re.compile(r'\s+xmlns(:\S+)?="[^"]+"')
+
+    def setup_class(self):
+        self.conv = Converter()
+
+    def test_base(self):
+        data = [
+            (u'Text',
+                '<p>Text</p>'),
+            (u'Text\nTest',
+                '<p>Text\nTest</p>'),
+            (u'Text\n\nTest',
+                '<p>Text</p>\n<p>Test</p>'),
+            (u'<http://moinmo.in/>',
+                '<p><a xlink:href="http://moinmo.in/">http://moinmo.in/</a></p>'),
+            (u'[yo](javascript:alert("xss"))',
+                '<p>javascript:alert("xss")</p>'),
+            (u'[MoinMoin](http://moinmo.in/)',
+                '<p><a xlink:href="http://moinmo.in/">MoinMoin</a></p>'),
+            (u'----',
+                '<separator />'),
+        ]
+        for i in data:
+            yield (self.do, ) + i
+
+    def test_emphasis(self):
+        data = [
+            (u'*Emphasis*',
+                '<p><emphasis>Emphasis</emphasis></p>'),
+            (u'_Emphasis_',
+                '<p><emphasis>Emphasis</emphasis></p>'),
+            (u'**Strong**',
+                '<p><strong>Strong</strong></p>'),
+            (u'_**Both**_',
+                '<p><emphasis><strong>Both</strong></emphasis></p>'),
+            (u'**_Both_**',
+                '<p><strong><emphasis>Both</emphasis></strong></p>'),
+        ]
+        for i in data:
+            yield (self.do, ) + i
+
+    def test_escape(self):
+        data = [
+            (u'http://moinmo.in/',
+                '<p>http://moinmo.in/</p>'),
+            (u'\[escape](yo)',
+                '<p>[escape](yo)</p>'),
+            (u'\*yo\*',
+                '<p>*yo*</p>'),
+        ]
+        for i in data:
+            yield (self.do, ) + i
+
+    def test_heading(self):
+        data = [
+            (u'# Heading 1',
+                '<h outline-level="1">Heading 1</h>'),
+            (u'## Heading 2',
+                '<h outline-level="2">Heading 2</h>'),
+            (u'### Heading 3',
+                '<h outline-level="3">Heading 3</h>'),
+            (u'#### Heading 4',
+                '<h outline-level="4">Heading 4</h>'),
+            (u'##### Heading 5',
+                '<h outline-level="5">Heading 5</h>'),
+            (u'###### Heading 6',
+                '<h outline-level="6">Heading 6</h>'),
+            (u'# Heading 1 #',
+                '<h outline-level="1">Heading 1</h>'),
+            (u'## Heading 2 ##',
+                '<h outline-level="2">Heading 2</h>'),
+            (u'### Heading 3 ###',
+                '<h outline-level="3">Heading 3</h>'),
+            (u'#### Heading 4 ####',
+                '<h outline-level="4">Heading 4</h>'),
+            (u'##### Heading 5 #####',
+                '<h outline-level="5">Heading 5</h>'),
+            (u'###### Heading 6 ######',
+                '<h outline-level="6">Heading 6</h>'),
+            (u'Heading 1\n=========\nHeading 2\n---------\n',
+                '<h outline-level="1">Heading 1</h>\n<h outline-level="2">Heading 2</h>'),
+            (u'Heading 1\n---------\n',
+                '<h outline-level="2">Heading 1</h>'),
+            (u'Heading\n=======\n\nxxxx',
+                '<h outline-level="1">Heading</h>\n<p>xxxx</p>'),
+        ]
+        for i in data:
+            yield (self.do, ) + i
+
+    def test_list(self):
+        data = [
+            (u'* Item',
+                '<list item-label-generate="unordered">\n<list-item><list-item-body>Item</list-item-body></list-item>\n</list>'),
+            (u'* Item\nItem',
+                '<list item-label-generate="unordered">\n<list-item><list-item-body>Item\nItem</list-item-body></list-item>\n</list>'),
+            (u'* Item 1\n* Item 2',
+                '<list item-label-generate="unordered">\n<list-item><list-item-body>Item 1</list-item-body></list-item>\n<list-item><list-item-body>Item 2</list-item-body></list-item>\n</list>'),
+            (u'* Item 1\n    * Item 1.2\n* Item 2',
+                '<list item-label-generate="unordered">\n<list-item><list-item-body>Item 1<list item-label-generate="unordered">\n<list-item><list-item-body>Item 1.2</list-item-body></list-item>\n</list>\n</list-item-body></list-item>\n<list-item><list-item-body>Item 2</list-item-body></list-item>\n</list>'),
+            (u'* List 1\n\nyo\n\n\n* List 2',
+                '<list item-label-generate="unordered">\n<list-item><list-item-body>List 1</list-item-body></list-item>\n</list>\n<p>yo</p>\n<list item-label-generate="unordered">\n<list-item><list-item-body>List 2</list-item-body></list-item>\n</list>'),
+            (u'8. Item',
+                '<list item-label-generate="ordered">\n<list-item><list-item-body>Item</list-item-body></list-item>\n</list>'),
+        ]
+        for i in data:
+            yield (self.do, ) + i
+
+    def serialize(self, elem, **options):
+        from StringIO import StringIO
+        buffer = StringIO()
+        elem.write(buffer.write, namespaces=self.namespaces, **options)
+        return self.output_re.sub(u'', buffer.getvalue())
+
+    def do(self, input, output, args={}):
+        out = self.conv(input, 'text/x-markdown;charset=utf-8', **args)
+        got_output = self.serialize(out)
+        desired_output = "<page><body>\n%s\n</body></page>" % output
+        print '------------------------------------'
+        print "WANTED:"
+        print desired_output
+        print "GOT:"
+        print got_output
+        assert got_output == desired_output
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/converter/_tests/test_registry.py	Tue Aug 14 00:34:21 2012 +0530
@@ -0,0 +1,63 @@
+# Copyright: 2012 MoinMoin:CheerXiao
+# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
+
+"""
+MoinMoin - Tests for MoinMoin.converter.default_registry
+"""
+
+
+import pytest
+
+from MoinMoin.util.mime import Type, type_moin_document, type_moin_wiki
+from MoinMoin.converter import default_registry
+from MoinMoin.converter.text_in import Converter as TextInConverter
+from MoinMoin.converter.moinwiki_in import Converter as MoinwikiInConverter
+from MoinMoin.converter.html_in import Converter as HtmlInConverter
+from MoinMoin.converter.pygments_in import Converter as PygmentsInConverter
+from MoinMoin.converter.everything import Converter as EverythingConverter
+
+from MoinMoin.converter.html_out import ConverterPage as HtmlOutConverterPage
+from MoinMoin.converter.moinwiki_out import Converter as MoinwikiOutConverter
+
+from MoinMoin.converter.highlight import Converter as HighlightConverter
+from MoinMoin.converter.macro import Converter as MacroConverter
+from MoinMoin.converter.include import Converter as IncludeConverter
+from MoinMoin.converter.link import ConverterExternOutput as LinkConverterExternOutput
+from MoinMoin.converter.link import ConverterItemRefs as LinkConverterItemRefs
+
+
+class TestRegistry(object):
+    def testConverterFinder(self):
+        for type_input, type_output, ExpectedClass in [
+                # *_in converters
+                (type_moin_wiki, type_moin_document, MoinwikiInConverter),
+                (Type('x-moin/format;name=wiki'), type_moin_document, MoinwikiInConverter),
+                # pygments_in can handle this too but html_in should have more priority
+                (Type('text/html'), type_moin_document, HtmlInConverter),
+                # fall back to pygments_in
+                (Type('text/html+jinja'), type_moin_document, PygmentsInConverter),
+                # fallback for any random text/* input types
+                (Type('text/blahblah'), type_moin_document, TextInConverter),
+                # fallback for anything
+                (Type('mua/haha'), type_moin_document, EverythingConverter),
+
+                # *_out converters
+                (type_moin_document, Type('application/x-xhtml-moin-page'), HtmlOutConverterPage),
+                (type_moin_document, type_moin_wiki, MoinwikiOutConverter),
+                (type_moin_document, Type('x-moin/format;name=wiki'), MoinwikiOutConverter),
+            ]:
+            conv = default_registry.get(type_input, type_output)
+            assert isinstance(conv, ExpectedClass)
+
+        for kwargs, ExpectedClass in [
+                # DOM converters, which depend on keyword argument to default_registry.get
+                (dict(macros='expandall'), MacroConverter),
+                (dict(includes='expandall'), IncludeConverter),
+                (dict(links='extern'), LinkConverterExternOutput),
+                (dict(items='refs'), LinkConverterItemRefs),
+            ]:
+            conv = default_registry.get(type_moin_document, type_moin_document, **kwargs)
+            assert isinstance(conv, ExpectedClass)
+
+
+coverage_modules = ['MoinMoin.converter']
--- a/MoinMoin/converter/highlight.py	Mon Aug 13 21:08:53 2012 +0530
+++ b/MoinMoin/converter/highlight.py	Tue Aug 14 00:34:21 2012 +0530
@@ -13,10 +13,9 @@
 
 class Converter(object):
     @classmethod
-    def _factory(cls, input, output, **kw):
-        if input == 'application/x.moin.document' and \
-                output == 'application/x.moin.document;highlight=regex':
-            return cls
+    def _factory(cls, input, output, highlight='', re='', **kw):
+        if highlight == 'regex':
+            return cls(re)
 
     def recurse(self, elem):
         new_childs = []
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/converter/markdown_in.py	Tue Aug 14 00:34:21 2012 +0530
@@ -0,0 +1,413 @@
+# Copyright: 2008-2010 MoinMoin:BastianBlank
+# Copyright: 2012 MoinMoin:AndreasKloeckner
+# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
+
+"""
+MoinMoin - Markdown input converter
+
+http://daringfireball.net/projects/markdown/
+"""
+
+
+from __future__ import absolute_import, division
+
+import re
+import htmlentitydefs
+
+from MoinMoin.util.tree import moin_page, xml, html, xlink
+from ._util import allowed_uri_scheme, decode_data
+
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
+from emeraldtree import ElementTree as ET
+
+from markdown import Markdown
+import markdown.util as md_util
+
+def postproc_text(markdown, text):
+    """
+    Removes HTML or XML character references and entities from a text string.
+
+    @param text The HTML (or XML) source text.
+    @return The plain text, as a Unicode string, if necessary.
+    """
+
+    # http://effbot.org/zone/re-sub.htm#unescape-html
+
+    if text is None:
+        return None
+
+    for pp in markdown.postprocessors.values():
+        text = pp.run(text)
+
+    def fixup(m):
+        text = m.group(0)
+        if text[:2] == "&#":
+            # character reference
+            try:
+                if text[:3] == "&#x":
+                    return unichr(int(text[3:-1], 16))
+                else:
+                    return unichr(int(text[2:-1]))
+            except ValueError:
+                pass
+        else:
+            # named entity
+            try:
+                text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
+            except KeyError:
+                pass
+        return text # leave as is
+
+    return re.sub("&#?\w+;", fixup, text)
+
+class Converter(object):
+    # {{{ html conversion
+
+    # HTML tags which can be converted directly to the moin_page namespace
+    symmetric_tags = set(['div', 'p', 'strong', 'code', 'quote', 'blockquote'])
+
+    # HTML tags to define a list, except dl which is a little bit different
+    list_tags = set(['ul', 'dir', 'ol'])
+
+    # HTML tags which can be convert without attributes in a different DOM tag
+    simple_tags = {# Emphasis
+                   'em': moin_page.emphasis, 'i': moin_page.emphasis,
+                   # Strong
+                   'b': moin_page.strong, 'strong': moin_page.strong,
+                   # Code and Blockcode
+                   'pre': moin_page.blockcode, 'tt': moin_page.code,
+                   'samp': moin_page.code,
+                   # Lists
+                   'dt': moin_page.list_item_label, 'dd': moin_page.list_item_body,
+                   # TODO : Some tags related to tables can be also simplify
+                  }
+
+    # HTML Tag which does not have equivalence in the DOM Tree
+    # But we keep the information using <span element>
+    inline_tags = set(['abbr', 'acronym', 'address', 'dfn', 'kbd'])
+
+    # HTML tags which are completely ignored by our converter.
+    # We even do not process children of these elements.
+    ignored_tags = set(['applet', 'area', 'button', 'caption', 'center', 'fieldset',
+                        'form', 'frame', 'frameset', 'head', 'iframe', 'input', 'isindex',
+                        'label', 'legend', 'link', 'map', 'menu', 'noframes', 'noscript',
+                        'optgroup', 'option', 'param', 'script', 'select', 'style',
+                        'textarea', 'title', 'var',
+                       ])
+
+    # standard_attributes are html attributes which are used
+    # directly in the DOM tree, without any conversion
+    standard_attributes = set(['title', 'class', 'style'])
+
+    # Regular expression to detect an html heading tag
+    heading_re = re.compile('h[1-6]')
+
+    def new(self, tag, attrib, children):
+        """
+        Return a new element for the DOM Tree
+        """
+        return ET.Element(tag, attrib=attrib, children=children)
+
+    def new_copy(self, tag, element, attrib):
+        """
+        Function to copy one element to the DOM Tree.
+
+        It first converts the child of the element,
+        and the element itself.
+        """
+        attrib_new = self.convert_attributes(element)
+        attrib.update(attrib_new)
+        children = self.do_children(element)
+        return self.new(tag, attrib, children)
+
+    def new_copy_symmetric(self, element, attrib):
+        """
+        Create a new QName, with the same tag of the element,
+        but with a different namespace.
+
+        Then, we handle the copy normally.
+        """
+        tag = ET.QName(element.tag, moin_page)
+        return self.new_copy(tag, element, attrib)
+
+    def convert_attributes(self, element):
+        result = {}
+        for key, value in element.attrib.iteritems():
+            if key in self.standard_attributes:
+                result[html(key)] = value
+            if key == 'id':
+                result[xml('id')] = value
+        return result
+
+    def visit_heading(self, element):
+        """
+        Function to convert an heading tag into a proper
+        element in our moin_page namespace
+        """
+        heading_level = element.tag[1]
+        key = moin_page('outline-level')
+        attrib = {}
+        attrib[key] = heading_level
+        return self.new_copy(moin_page.h, element, attrib)
+
+    def visit_br(self, element):
+        return moin_page.line_break()
+
+    def visit_big(self, element):
+        key = moin_page('font-size')
+        attrib = {}
+        attrib[key] = '120%'
+        return self.new_copy(moin_page.span, element, attrib)
+
+    def visit_small(self, element):
+        key = moin_page('font-size')
+        attrib = {}
+        attrib[key] = '85%'
+        return self.new_copy(moin_page.span, element, attrib)
+
+    def visit_sub(self, element):
+        key = moin_page('baseline-shift')
+        attrib = {}
+        attrib[key] = 'sub'
+        return self.new_copy(moin_page.span, element, attrib)
+
+    def visit_sup(self, element):
+        key = moin_page('baseline-shift')
+        attrib = {}
+        attrib[key] = 'super'
+        return self.new_copy(moin_page.span, element, attrib)
+
+    def visit_u(self, element):
+        key = moin_page('text-decoration')
+        attrib = {}
+        attrib[key] = 'underline'
+        return self.new_copy(moin_page.span, element, attrib)
+
+    def visit_ins(self, element):
+        key = moin_page('text-decoration')
+        attrib = {}
+        attrib[key] = 'underline'
+        return self.new_copy(moin_page.span, element, attrib)
+
+    def visit_del(self, element):
+        key = moin_page('text-decoration')
+        attrib = {}
+        attrib[key] = 'line-through'
+        return self.new_copy(moin_page.span, element, attrib)
+
+    def visit_s(self, element):
+        key = moin_page('text-decoration')
+        attrib = {}
+        attrib[key] = 'line-through'
+        return self.new_copy(moin_page.span, element, attrib)
+
+    def visit_strike(self, element):
+        key = moin_page('text-decoration')
+        attrib = {}
+        attrib[key] = 'line-through'
+        return self.new_copy(moin_page.span, element, attrib)
+
+    def visit_hr(self, element, min_class=u'moin-hr1', max_class=u'moin-hr6', default_class=u'moin-hr3'):
+        hr_class = element.attrib.get('class')
+        if not (min_class <= hr_class <= max_class):
+            element.attrib[html('class')] = default_class
+        return self.new_copy(moin_page.separator, element, {})
+
+    def visit_img(self, element):
+        """
+        <img src="URI" /> --> <object xlink:href="URI />
+        """
+        key = xlink('href')
+        attrib = {}
+        attrib[key] = element.attrib.get("src")
+        return moin_page.object(attrib)
+
+    def visit_object(self, element):
+        """
+        <object data="href"></object> --> <object xlink="href" />
+        """
+        key = xlink('href')
+        attrib = {}
+        if self.base_url:
+            attrib[key] = ''.join([self.base_url, element.get(html.data)])
+        else:
+            attrib[key] = element.get(html.data)
+
+        # Convert the href attribute into unicode
+        attrib[key] = unicode(attrib[key])
+        return moin_page.object(attrib)
+
+    def visit_inline(self, element):
+        """
+        For some specific inline tags (defined in inline_tags)
+        We just return <span element="tag.name">
+        """
+        key = html('class')
+        attrib = {}
+        attrib[key] = ''.join(['html-', element.tag.name])
+        return self.new_copy(moin_page.span, element, attrib)
+
+    def visit_li(self, element):
+        """
+        NB : A list item (<li>) is like the following snippet :
+        <list-item>
+            <list-item-label>label</list-item-label>
+            <list-item-body>Body</list-item-body>
+        </list-item>
+
+        For <li> element, there is no label
+        """
+        list_item_body = ET.Element(moin_page.list_item_body,
+                                    attrib={}, children=self.do_children(element))
+        return ET.Element(moin_page.list_item, attrib={}, children=[list_item_body])
+
+    def visit_list(self, element):
+        """
+        Convert a list of item (whatever the type : ordered or unordered)
+        So we have a html code like :
+        <ul>
+            <li>Item 1</li>
+            <li>Item 2</li>
+        </ul>
+
+        Which will be convert like :
+        <list>
+            <list-item>
+                <list-item-body>Item 1</list-item-body>
+            </list-item>
+            <list-item>
+                <list-item-body>Item 2</list-item-body>
+            </list-item>
+        </list>
+        """
+        # We will define the appropriate attribute
+        # according to the type of the list
+        attrib = {}
+        if element.tag == "ul" or element.tag == "dir":
+            attrib[moin_page('item-label-generate')] = 'unordered'
+        elif element.tag == "ol":
+            attrib[moin_page('item-label-generate')] = 'ordered'
+
+        return ET.Element(moin_page.list, attrib=attrib,
+                children=self.do_children(element))
+
+    def visit_a(self, element):
+        key = xlink('href')
+        attrib = {}
+        href = postproc_text(self.markdown, element.attrib.get("href"))
+        if allowed_uri_scheme(href):
+            attrib[key] = href
+        else:
+            return href
+        return self.new_copy(moin_page.a, element, attrib)
+
+    def visit(self, element):
+        # Our element can be converted directly, just by changing the namespace
+        if element.tag in self.symmetric_tags:
+            return self.new_copy_symmetric(element, attrib={})
+
+        # Our element is enough simple to just change the tag name
+        if element.tag in self.simple_tags:
+            return self.new_copy(self.simple_tags[element.tag], element, attrib={})
+
+        # Our element defines a list
+        if element.tag in self.list_tags:
+            return self.visit_list(element)
+
+        # We convert our element as a span tag with element attribute
+        if element.tag in self.inline_tags:
+            return self.visit_inline(element)
+
+        # We have a heading tag
+        if self.heading_re.match(element.tag):
+            return self.visit_heading(element)
+
+        # Otherwise we need a specific procedure to handle it
+        method_name = 'visit_' + element.tag
+        method = getattr(self, method_name, None)
+        if method:
+            return method(element)
+
+        # We should ignore this tag
+        if element.tag in self.ignored_tags:
+            logging.info("INFO : Ignored tag : {0}".format(element.tag))
+            return
+
+        logging.info("INFO : Unhandled tag : {0}".format(element.tag))
+        return
+
+    def do_children(self, element):
+        new = []
+        if hasattr(element, "text") and element.text is not None:
+            new.append(postproc_text(self.markdown, element.text))
+
+        for child in element:
+            r = self.visit(child)
+            if r is None:
+                r = ()
+            elif not isinstance(r, (list, tuple)):
+                r = (r, )
+            new.extend(r)
+            if hasattr(child, "tail") and child.tail is not None:
+                new.append(postproc_text(self.markdown, child.tail))
+        return new
+
+    # }}}
+
+    def __init__(self):
+        self.markdown = Markdown()
+
+    @classmethod
+    def _factory(cls, input, output, **kw):
+        return cls()
+
+    def __call__(self, data, contenttype=None, arguments=None):
+        text = decode_data(data, contenttype)
+
+        # {{{ stolen from Markdown.convert
+
+        # Fixup the source text
+        try:
+            text = unicode(text)
+        except UnicodeDecodeError, e:
+            # Customise error message while maintaining original traceback
+            e.reason += '. -- Note: Markdown only accepts unicode input!'
+            raise
+
+        text = text.replace(md_util.STX, "").replace(md_util.ETX, "")
+        text = text.replace("\r\n", "\n").replace("\r", "\n") + "\n\n"
+        text = re.sub(r'\n\s+\n', '\n\n', text)
+        text = text.expandtabs(8)
+
+        # Split into lines and run the line preprocessors.
+        lines = text.split("\n")
+        for prep in self.markdown.preprocessors.values():
+            lines = prep.run(lines)
+
+        # Parse the high-level elements.
+        md_root = self.markdown.parser.parseDocument(lines).getroot()
+
+        # Run the tree-processors
+        for treeprocessor in self.markdown.treeprocessors.values():
+            new_md_root = treeprocessor.run(md_root)
+            if new_md_root:
+                md_root = new_md_root
+
+        # }}}
+
+        # md_root is a list of plain old Python ElementTree objects.
+
+        converted = self.do_children(md_root)
+        body = moin_page.body(children=converted)
+        root = moin_page.page(children=[body])
+
+        return root
+
+from . import default_registry
+from MoinMoin.util.mime import Type, type_moin_document
+default_registry.register(Converter._factory, Type("text/x-markdown"), type_moin_document)
+default_registry.register(Converter._factory, Type('x-moin/format;name=markdown'), type_moin_document)
+
+# vim: foldmethod=marker
--- a/MoinMoin/forms.py	Mon Aug 13 21:08:53 2012 +0530
+++ b/MoinMoin/forms.py	Tue Aug 14 00:34:21 2012 +0530
@@ -12,11 +12,15 @@
 import re, datetime
 import json
 
-from flatland import Element, Form, String, Integer, Boolean, Enum, Dict, DateTime as _DateTime, JoinedString
+from flatland import Element, Form, String, Integer, Boolean, Enum, Dict, JoinedString, List, DateTime as _DateTime
+from flatland.util import class_cloner, Unspecified
 from flatland.validation import Validator, Present, IsEmail, ValueBetween, URLValidator, Converted, ValueAtLeast
 from flatland.exc import AdaptationError
 
+from flask import g as flaskg
+
 from MoinMoin.constants.forms import *
+from MoinMoin.constants.keys import ITEMID, NAME
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin.security.textcha import TextCha, TextChaizedForm, TextChaValid
 from MoinMoin.util.forms import FileStorage
@@ -140,3 +144,59 @@
 Submit = String.using(default=L_('OK'), optional=True).with_properties(widget=WIDGET_SUBMIT, class_=CLASS_BUTTON)
 
 Hidden = String.using(optional=True).with_properties(widget=WIDGET_HIDDEN)
+
+# optional=True is needed to get rid of the "required field" indicator on the UI (usually an asterisk)
+ReadonlyStringList = List.of(String).using(optional=True).with_properties(widget=WIDGET_READONLY_STRING_LIST)
+
+ReadonlyItemLinkList = ReadonlyStringList.with_properties(widget=WIDGET_READONLY_ITEM_LINK_LIST)
+
+
+# XXX When some user chooses a Reference candidate that is removed before the
+# user POSTs, the validator fails. This can be confusing.
+class ValidReference(Validator):
+    """
+    Validator for Reference
+    """
+    invalid_reference_msg = L_('Invalid Reference.')
+
+    def validate(self, element, state):
+        if element.value not in element.valid_values:
+            return self.note_error(element, state, 'invalid_reference_msg')
+        return True
+
+class Reference(Select.with_properties(empty_label=L_(u'(None)')).validated_by(ValidReference())):
+    """
+    A metadata property that points to another item selected out of the
+    Results of a search query.
+    """
+    @class_cloner
+    def to(cls, query, query_args={}):
+        cls._query = query
+        cls._query_args = query_args
+        return cls
+
+    @classmethod
+    def _get_choices(cls):
+        revs = flaskg.storage.search(cls._query, **cls._query_args)
+        choices = [(rev.meta[ITEMID], rev.meta[NAME]) for rev in revs]
+        if cls.optional:
+            choices.append((u'', cls.properties['empty_label']))
+        return choices
+
+    def __init__(self, value=Unspecified, **kw):
+        super(Reference, self).__init__(value, **kw)
+        # NOTE There is a slight chance of two instances of the same Reference
+        # subclass having different set of choices when the storage changes
+        # between their initialization.
+        choices = self._get_choices()
+        self.properties['labels'] = dict(choices)
+        self.valid_values = [id_ for id_, name in choices]
+
+
+class BackReference(ReadonlyItemLinkList):
+    """
+    Back references built from Whoosh query.
+    """
+    def set(self, query, **query_args):
+        revs = flaskg.storage.search(query, **query_args)
+        super(BackReference, self).set([rev.meta[NAME] for rev in revs])
--- a/MoinMoin/items/__init__.py	Mon Aug 13 21:08:53 2012 +0530
+++ b/MoinMoin/items/__init__.py	Tue Aug 14 00:34:21 2012 +0530
@@ -23,11 +23,14 @@
 from StringIO import StringIO
 from collections import namedtuple
 from functools import partial
+from datetime import datetime
 
 from flatland import Form
 from flatland.validation import Validator
 
-from whoosh.query import Term, And, Prefix
+from jinja2 import Markup
+
+from whoosh.query import Term, And, Prefix, DateRange
 
 from MoinMoin.forms import RequiredText, OptionalText, JSON, Tags, DateTime, Submit
 
@@ -52,7 +55,7 @@
 from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError, StorageError
 from MoinMoin.util.registry import RegistryBase
 from MoinMoin.constants.keys import (
-    NAME, NAME_OLD, NAME_EXACT, WIKINAME, MTIME, SYSITEM_VERSION, ITEMTYPE,
+    NAME, NAME_OLD, NAME_EXACT, WIKINAME, MTIME, PTIME, SYSITEM_VERSION, ITEMTYPE,
     CONTENTTYPE, SIZE, TAGS, ACTION, ADDRESS, HOSTNAME, USERID, COMMENT,
     HASH_ALGORITHM, ITEMID, REVID, DATAID, CURRENT, PARENTID
     )
@@ -96,11 +99,16 @@
 
 class DummyRev(dict):
     """ if we have no stored Revision, we use this dummy """
-    def __init__(self, item, itemtype, contenttype):
+    def __init__(self, item, itemtype=None, contenttype=None):
         self.item = item
-        self.meta = {ITEMTYPE: itemtype, CONTENTTYPE: contenttype}
+        self.meta = {
+            ITEMTYPE: itemtype or u'nonexistent',
+            CONTENTTYPE: contenttype or u'application/x-nonexistent'
+        }
         self.data = StringIO('')
         self.revid = None
+        if self.item:
+            self.meta[NAME] = self.item.name
 
 
 class DummyItem(object):
@@ -153,10 +161,6 @@
         previously created Content instance is assigned to its content
         property.
         """
-        if contenttype is None:
-            contenttype = u'application/x-nonexistent'
-        if itemtype is None:
-            itemtype = u'nonexistent'
         if 1: # try:
             if item is None:
                 item = flaskg.storage[name]
@@ -562,6 +566,21 @@
                                data_rendered='',
                                )
 
+    def do_show(self, revid):
+        show_revision = revid != CURRENT
+        show_navigation = False # TODO
+        first_rev = last_rev = None # TODO
+        return render_template(self.show_template,
+                               item=self, item_name=self.name,
+                               rev=self.rev,
+                               contenttype=self.contenttype,
+                               first_rev_id=first_rev,
+                               last_rev_id=last_rev,
+                               data_rendered=Markup(self.content._render_data()),
+                               show_revision=show_revision,
+                               show_navigation=show_navigation,
+                              )
+
     def do_modify(self):
         method = request.method
         if method == 'GET':
@@ -607,18 +626,11 @@
                                search_form=None,
                               )
 
+    show_template = 'show.html'
     modify_template = 'modify.html'
 
 
 @register
-class Ticket(Contentful):
-    """
-    Stub for ticket item class.
-    """
-    itemtype = u'ticket'
-
-
-@register
 class Userprofile(Item):
     """
     Currently userprofile is implemented as a contenttype. This is a stub of an
@@ -638,11 +650,23 @@
     def _convert(self, doc):
         abort(404)
 
+    def do_show(self, revid):
+        # First, check if the current user has the required privileges
+        if flaskg.user.may.create(self.name):
+            content = self._select_itemtype()
+        else:
+            content = render_template('show_nonexistent.html',
+                                      item_name=self.name,
+                                     )
+        return Response(content, 404)
+
     def do_modify(self):
         # First, check if the current user has the required privileges
         if not flaskg.user.may.create(self.name):
             abort(403)
+        return self._select_itemtype()
 
+    def _select_itemtype(self):
         # TODO Construct this list from the item_registry. Two more fields (ie.
         # display name and description) are needed in the registry then to
         # support the automatic construction.
@@ -677,6 +701,34 @@
         meta_form = BlogMetaForm
         meta_template = 'modify_blog_meta.html'
 
+    def do_show(self, revid):
+        """
+        Show a blog item and a list of its blog entries below it.
+
+        If tag GET-parameter is defined, the list of blog entries consists only
+        of those entries that contain the tag value in their lists of tags.
+        """
+        # for now it is just one tag=value, later it could be tag=value1&tag=value2&...
+        tag = request.values.get('tag')
+        prefix = self.name + u'/'
+        current_timestamp = int(time.time())
+        terms = [Term(WIKINAME, app.cfg.interwikiname),
+                 # Only sub items of this item
+                 Prefix(NAME_EXACT, prefix),
+                 # Filter out those items that do not have a PTIME meta or PTIME is in the future.
+                 DateRange(PTIME, start=None, end=datetime.utcfromtimestamp(current_timestamp)),
+                ]
+        if tag:
+            terms.append(Term(TAGS, tag))
+        query = And(terms)
+        revs = flaskg.storage.search(query, sortedby=[PTIME], reverse=True, limit=None)
+        blog_entry_items = [Item.create(rev.meta[NAME], rev_id=rev.revid) for rev in revs]
+        return render_template('blog.html',
+                               item_name=self.name,
+                               blog_item=self,
+                               blog_entry_items=blog_entry_items,
+                               tag=tag,
+                              )
 
 @register
 class BlogEntry(Default):
@@ -685,3 +737,22 @@
     class _ModifyForm(Default._ModifyForm):
         meta_form = BlogEntryMetaForm
         meta_template = 'modify_blog_entry_meta.html'
+
+    def do_show(self, revid):
+        blog_item_name = self.name.rsplit('/', 1)[0]
+        try:
+            blog_item = Item.create(blog_item_name)
+        except AccessDenied:
+            abort(403)
+        if not isinstance(blog_item, Blog):
+            # The parent item of this blog entry item is not a Blog item.
+            abort(403)
+        return render_template('blog_entry.html',
+                               item_name=self.name,
+                               blog_item=blog_item,
+                               blog_entry_item=self,
+                              )
+
+
+from ..util.pysupport import load_package_modules
+load_package_modules(__name__, __path__)
--- a/MoinMoin/items/_tests/test_Item.py	Mon Aug 13 21:08:53 2012 +0530
+++ b/MoinMoin/items/_tests/test_Item.py	Tue Aug 14 00:34:21 2012 +0530
@@ -18,7 +18,7 @@
 from MoinMoin._tests import become_trusted, update_item
 from MoinMoin.items import Item, NonExistent
 from MoinMoin.items.content import Binary, Text, Image, TransformableBitmapImage, MarkupItem
-from MoinMoin.constants.keys import ITEMTYPE, CONTENTTYPE, ADDRESS, COMMENT, HOSTNAME, USERID, ACTION
+from MoinMoin.constants.keys import ITEMTYPE, CONTENTTYPE, NAME, ADDRESS, COMMENT, HOSTNAME, USERID, ACTION
 
 class TestItem(object):
 
@@ -29,6 +29,7 @@
         assert meta == {
                 ITEMTYPE: u'nonexistent',
                 CONTENTTYPE: u'application/x-nonexistent',
+                NAME: u'DoesNotExist',
                 }
         assert data == ''
 
--- a/MoinMoin/items/content.py	Mon Aug 13 21:08:53 2012 +0530
+++ b/MoinMoin/items/content.py	Tue Aug 14 00:34:21 2012 +0530
@@ -806,6 +806,12 @@
 
 
 @register
+class Markdown(MarkupItem):
+    """ Markdown markup """
+    contenttype = 'text/x-markdown'
+
+
+@register
 class HTML(Text):
     """
     HTML markup
--- a/MoinMoin/templates/blog.html	Mon Aug 13 21:08:53 2012 +0530
+++ b/MoinMoin/templates/blog.html	Tue Aug 14 00:34:21 2012 +0530
@@ -1,19 +1,19 @@
 {% extends theme("blog_layout.html") %}
 
 {% set no_entries_msg = _("There are no entries in this blog.") %}
-{% if supertag %}
-    {% set no_entries_msg = _("There are no entries in the \"%(supertag)s\" blog category.", supertag=supertag) %}
+{% if tag %}
+    {% set no_entries_msg = _("There are no entries in the '%(tag)s' blog category.", tag=tag) %}
 {% endif %}
 
 {% block pagepath %}
     {{ super() }}
-    {% if supertag %}
+    {% if tag %}
         <span class="sep">/</span>
         {{ _("Category:") }}
         {% if blog_name %}
-            <a href="{{ url_for('frontend.show_blog', item_name=blog_name, supertag=supertag) }}">{{ supertag }}</a>
+            <a href="{{ url_for('frontend.show_item', item_name=blog_name, tag=tag) }}">{{ tag }}</a>
         {% else %}
-            {{ supertag }}
+            {{ tag }}
         {% endif %}
     {% endif %}
 {% endblock %}
--- a/MoinMoin/templates/blog_layout.html	Mon Aug 13 21:08:53 2012 +0530
+++ b/MoinMoin/templates/blog_layout.html	Tue Aug 14 00:34:21 2012 +0530
@@ -46,7 +46,7 @@
         <h2>{{ _("Categories") }}</h2>
         <ul>
             {% for supertag in supertags %}
-                <li><a href="{{ url_for('frontend.show_blog', item_name=blog_name, supertag=supertag) }}">{{ supertag }}</a></li>
+                <li><a href="{{ url_for('frontend.show_item', item_name=blog_name, tag=supertag) }}">{{ supertag }}</a></li>
             {% endfor %}
         </ul>
     </div>
--- a/MoinMoin/templates/forms.html	Mon Aug 13 21:08:53 2012 +0530
+++ b/MoinMoin/templates/forms.html	Tue Aug 14 00:34:21 2012 +0530
@@ -55,6 +55,8 @@
       'submit': raw_input,
       'hidden': raw_input,
       'select': select,
+      'readonly_string_list': readonly_string_list,
+      'readonly_item_link_list': readonly_item_link_list,
   }[field.properties.widget] or stub -%}
   {{ macro(field, *varargs, **kwargs) }}
 {% endmacro %}
@@ -155,6 +157,30 @@
   </dd>
 {% endmacro %}
 
+{% macro readonly_string_list(field) %}
+  <dt>
+    {{ gen.label(field) }}
+  </dt>
+  <dd>
+    {% for v in field.value %}
+      {# TODO style .moin-string-list-item #}
+      <span class="moin-string-list-item">{{ v }}</span>
+    {% endfor %}
+  </dd>
+{% endmacro %}
+
+{% macro readonly_item_link_list(field) %}
+  <dt>
+    {{ gen.label(field) }}
+  </dt>
+  <dd>
+    {% for v in field.value %}
+      {# TODO style .moin-item-link-list-item #}
+      <span class="moin-item-link-list-item"><a href="{{ url_for_item(v) }}">{{ v }}</a></span>
+    {% endfor %}
+  </dd>
+{% endmacro %}
+
 {% macro render_file_uploader(submit_url) %}
     <div id="file_upload">
         <div class="upload-form">
--- a/MoinMoin/templates/modify_select_itemtype.html	Mon Aug 13 21:08:53 2012 +0530
+++ b/MoinMoin/templates/modify_select_itemtype.html	Tue Aug 14 00:34:21 2012 +0530
@@ -1,8 +1,8 @@
 {% extends theme("show.html") %}
 {% block content %}
-<h1>{{ _("Create new item?") }}</h1>
+<h1>{{ _("Item not found, create it now?") }}</h1>
 <p>
-{{ _("This item does not exist (yet), but you can try creating it now. Please select the type of the item you want to create.") }}
+{{ _("Item '%(name)s' does not exist (yet), but you can try creating it now. Please select the type of the item you want to create.", name=item_name) }}
 </p>
 <table class="zebra">
     {% for itname, itlabel, itdesc in itemtypes %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/templates/show_nonexistent.html	Tue Aug 14 00:34:21 2012 +0530
@@ -0,0 +1,7 @@
+{% extends theme("layout.html") %}
+{% block content %}
+<h1>{{ _("Item not found") }}</h1>
+<p>
+{{ _("Item '%(name)s' does not exist.", name=item_name) }}
+</p>
+{% endblock %}
--- a/setup.py	Mon Aug 13 21:08:53 2012 +0530
+++ b/setup.py	Tue Aug 14 00:34:21 2012 +0530
@@ -82,6 +82,7 @@
     install_requires=[
         'blinker>=1.1', # event signalling (e.g. for change notification trigger)
         'docutils>=0.8.1', # reST markup processing
+        'Markdown>=2.1.1', # Markdown markup processing
         'Flask>=0.8', # micro framework
         'Flask-Babel>=0.7', # i18n support
         'Flask-Cache>=0.3.4', # caching support