changeset 1583:f9d787239c02

merged
author Jaiditya Mathur <jaiditya.mathur@gmail.com>
date Sun, 29 Jul 2012 02:34:27 +0530
parents 895436f44f93 (current diff) e612c71d6338 (diff)
children cc5fb52be013
files MoinMoin/templates/modify_applet.html MoinMoin/templates/modify_show_type_selection.html
diffstat 20 files changed, 1703 insertions(+), 1400 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/apps/frontend/views.py	Fri Jul 27 19:08:35 2012 +0530
+++ b/MoinMoin/apps/frontend/views.py	Sun Jul 29 02:34:27 2012 +0530
@@ -22,7 +22,7 @@
 from datetime import datetime
 from itertools import chain
 from collections import namedtuple
-from functools import wraps
+from functools import wraps, partial
 
 try:
     import json
@@ -53,7 +53,6 @@
 from MoinMoin.apps.frontend import frontend
 from MoinMoin.forms import OptionalText, RequiredText, URL, YourOpenID, YourEmail, RequiredPassword, Checkbox, InlineCheckbox, Select, Tags, Natural, Submit, Hidden
 from MoinMoin.items import BaseChangeForm, Item, NonExistent
-from MoinMoin.items import ROWS_META, COLS, ROWS_DATA
 from MoinMoin import config, user, util
 from MoinMoin.config import CONTENTTYPE_GROUPS
 from MoinMoin.constants.keys import *
@@ -268,9 +267,9 @@
     return html
 
 
-def presenter(view, add_trail=False, abort404=True):
+def add_presenter(wrapped, view, add_trail=False, abort404=True):
     """
-    Decorator to create new "presenter" views.
+    Add new "presenter" views.
 
     Presenter views handle GET requests to locations like
     +{view}/+<rev>/<item_name> and +{view}/<item_name>, and always try to
@@ -280,22 +279,27 @@
     :param add_trail: whether to call flaskg.user.add_trail
     :param abort404: whether to abort(404) for nonexistent items
     """
-    def decorator(wrapped):
-        @frontend.route('/+{view}/+<rev>/<itemname:item_name>'.format(view=view))
-        @frontend.route('/+{view}/<itemname:item_name>'.format(view=view), defaults=dict(rev=CURRENT))
-        @wraps(wrapped)
-        def wrapper(item_name, rev):
-            if add_trail:
-                flaskg.user.add_trail(item_name)
-            try:
-                item = Item.create(item_name, rev_id=rev)
-            except AccessDenied:
-                abort(403)
-            if abort404 and isinstance(item, NonExistent):
-                abort(404, item_name)
-            return wrapped(item)
-        return wrapper
-    return decorator
+    @frontend.route('/+{view}/+<rev>/<itemname:item_name>'.format(view=view))
+    @frontend.route('/+{view}/<itemname:item_name>'.format(view=view), defaults=dict(rev=CURRENT))
+    @wraps(wrapped)
+    def wrapper(item_name, rev):
+        if add_trail:
+            flaskg.user.add_trail(item_name)
+        try:
+            item = Item.create(item_name, rev_id=rev)
+        except AccessDenied:
+            abort(403)
+        if abort404 and isinstance(item, NonExistent):
+            abort(404, item_name)
+        return wrapped(item)
+    return wrapper
+
+
+def presenter(view, add_trail=False, abort404=True):
+    """
+    Decorator factory to apply add_presenter().
+    """
+    return partial(add_presenter, view=view, add_trail=add_trail, abort404=abort404)
 
 
 @frontend.route('/<itemname:item_name>', defaults=dict(rev=CURRENT), methods=['GET'])
@@ -321,7 +325,7 @@
                               contenttype=item.contenttype,
                               first_rev_id=first_rev,
                               last_rev_id=last_rev,
-                              data_rendered=Markup(item._render_data()),
+                              data_rendered=Markup(item.content._render_data()),
                               show_revision=show_revision,
                               show_navigation=show_navigation,
                              )
@@ -340,7 +344,7 @@
     else:
         status = 200
     content = render_template('dom.xml',
-                              data_xml=Markup(item._render_data_xml()),
+                              data_xml=Markup(item.content._render_data_xml()),
                              )
     return Response(content, status, mimetype='text/xml')
 
@@ -363,7 +367,7 @@
 def highlight_item(item):
     return render_template('highlight.html',
                            item=item, item_name=item.name,
-                           data_text=Markup(item._render_data_highlight()),
+                           data_text=Markup(item.content._render_data_highlight()),
                           )
 
 
@@ -408,17 +412,17 @@
         abort(404, item_name)
     return render_template('content.html',
                            item_name=item.name,
-                           data_rendered=Markup(item._render_data()),
+                           data_rendered=Markup(item.content._render_data()),
                            )
 
 @presenter('get')
 def get_item(item):
-    return item.do_get()
+    return item.content.do_get()
 
 @presenter('download')
 def download_item(item):
     mimetype = request.values.get("mimetype")
-    return item.do_get(force_attachment=True, mimetype=mimetype)
+    return item.content.do_get(force_attachment=True, mimetype=mimetype)
 
 @frontend.route('/+convert/<itemname:item_name>')
 def convert_item(item_name):
@@ -441,10 +445,11 @@
     # XXX Maybe use a random name to be sure it does not exist
     item_name_converted = item_name + 'converted'
     try:
-        converted_item = Item.create(item_name_converted, contenttype=contenttype)
+        # TODO implement Content.create and use it here
+        converted_item = Item.create(item_name_converted, itemtype=u'default', contenttype=contenttype)
     except AccessDenied:
         abort(403)
-    return converted_item._convert(item.internal_representation())
+    return converted_item.content._convert(item.content.internal_representation())
 
 
 @frontend.route('/+modify/<itemname:item_name>', methods=['GET', 'POST'])
@@ -455,15 +460,15 @@
     On POST, saves the new page (unless there's an error in input).
     After successful POST, redirects to the page.
     """
+    itemtype = request.values.get('itemtype')
     contenttype = request.values.get('contenttype')
-    template_name = request.values.get('template')
     try:
-        item = Item.create(item_name, contenttype=contenttype)
+        item = Item.create(item_name, itemtype=itemtype, contenttype=contenttype)
     except AccessDenied:
         abort(403)
     if not flaskg.user.may.write(item_name):
         abort(403)
-    return item.do_modify(contenttype, template_name)
+    return item.do_modify()
 
 
 @frontend.route('/+blog/+<rev>/<itemname:item_name>', methods=['GET'])
@@ -1664,7 +1669,7 @@
     rev_ids = [CURRENT]  # XXX TODO we need a reverse sorted list
     return render_template(item.diff_template,
                            item=item, item_name=item.name,
-                           diff_html=Markup(item._render_data_diff(oldrev, newrev)),
+                           diff_html=Markup(item.content._render_data_diff(oldrev, newrev)),
                            rev=item.rev,
                            first_rev_id=rev_ids[0],
                            last_rev_id=rev_ids[-1],
@@ -1682,7 +1687,7 @@
         item = Item.create(item.name, contenttype=commonmt, rev_id=newrev.revid)
     except AccessDenied:
         abort(403)
-    return item._render_data_diff_raw(oldrev, newrev)
+    return item.content._render_data_diff_raw(oldrev, newrev)
 
 
 @frontend.route('/+similar_names/<itemname:item_name>')
--- a/MoinMoin/constants/keys.py	Fri Jul 27 19:08:35 2012 +0530
+++ b/MoinMoin/constants/keys.py	Sun Jul 29 02:34:27 2012 +0530
@@ -27,6 +27,7 @@
 SOMEDICT = "somedict"
 
 CONTENTTYPE = "contenttype"
+ITEMTYPE = "itemtype"
 SIZE = "size"
 LANGUAGE = "language"
 EXTERNALLINKS = "externallinks"
@@ -82,10 +83,10 @@
 DISABLED = "disabled"
 
 # keys for blog homepages
-LOGO = u"logo"
-SUPERTAGS = u"supertags"
+LOGO = "logo"
+SUPERTAGS = "supertags"
 # keys for blog entries
-PTIME = u"ptime"
+PTIME = "ptime"
 
 # index names
 LATEST_REVS = 'latest_revs'
--- a/MoinMoin/converter/_tests/test_include.py	Fri Jul 27 19:08:35 2012 +0530
+++ b/MoinMoin/converter/_tests/test_include.py	Sun Jul 29 02:34:27 2012 +0530
@@ -9,7 +9,7 @@
 import pytest
 
 from MoinMoin.converter.include import *
-from MoinMoin.items import MoinWiki
+from MoinMoin.items import Item
 from MoinMoin.config import CONTENTTYPE
 from MoinMoin._tests import wikiconfig, update_item
 
@@ -65,20 +65,20 @@
 
     def test_IncludeHandlesCircularRecursion(self):
         # issue #80
-        # we use MoinWiki items to make tests simple
+        # we use text/x.moin.wiki markup to make tests simple
         update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{page2}}')
         update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{page3}}')
         update_item(u'page3', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{page4}}')
         update_item(u'page4', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{page2}}')
 
-        page1 = MoinWiki.create(u'page1')
-        rendered = page1._render_data()
+        page1 = Item.create(u'page1')
+        rendered = page1.content._render_data()
         # an error message will follow strong tag
         assert '<strong class="moin-error">' in rendered
 
     def test_ExternalInclude(self):
         update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{http://moinmo.in}}')
-        rendered = MoinWiki.create(u'page1')._render_data()
+        rendered = Item.create(u'page1').content._render_data()
         assert '<object class="moin-http moin-transclusion" data="http://moinmo.in" data-href="http://moinmo.in">http://moinmo.in</object>' in rendered
 
     def test_InlineInclude(self):
@@ -86,32 +86,32 @@
         update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Content of page2 is "{{page2}}".')
 
         update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Single line')
-        rendered = MoinWiki.create(u'page1')._render_data()
+        rendered = Item.create(u'page1').content._render_data()
         assert '<p>Content of page2 is "<span class="moin-transclusion" data-href="/page2">Single line</span>".</p>' in rendered
 
         update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Two\n\nParagraphs')
-        rendered = MoinWiki.create(u'page1')._render_data()
+        rendered = Item.create(u'page1').content._render_data()
         assert '<p>Content of page2 is "</p><div class="moin-transclusion" data-href="/page2"><p>Two</p><p>Paragraphs</p></div><p>".</p></div>' in rendered
 
         update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u"this text contains ''italic'' string")
-        rendered = MoinWiki.create(u'page1')._render_data()
+        rendered = Item.create(u'page1').content._render_data()
         assert 'Content of page2 is "<span class="moin-transclusion" data-href="/page2">this text contains <em>italic</em>' in rendered
 
         update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Content of page2 is\n\n{{page2}}')
         update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u"Single Line")
-        rendered = MoinWiki.create(u'page1')._render_data()
+        rendered = Item.create(u'page1').content._render_data()
         assert '<p>Content of page2 is</p><p><span class="moin-transclusion" data-href="/page2">Single Line</span></p>' in rendered
 
         update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Content of page2 is "{{page2}}"')
         update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u"|| table || cell ||")
-        rendered = MoinWiki.create(u'page1')._render_data()
+        rendered = Item.create(u'page1').content._render_data()
         assert 'Content of page2 is "</p>' in rendered
         assert '<table>' in rendered
         assert rendered.count('<table>') == 1
 
         update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Content of page2 is "{{page2}}"')
         update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u"|| this || has ||\n|| two || rows ||")
-        rendered = MoinWiki.create(u'page1')._render_data()
+        rendered = Item.create(u'page1').content._render_data()
         assert 'Content of page2 is "</p>' in rendered
         assert '<table>' in rendered
         assert rendered.count('<table>') == 1
@@ -121,11 +121,11 @@
         update_item(u'logo', {CONTENTTYPE: u'image/png'}, u'')
 
         update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{logo}}')
-        rendered = MoinWiki.create(u'page1')._render_data()
+        rendered = Item.create(u'page1').content._render_data()
         assert '<img alt="logo" class="moin-transclusion"' in rendered
 
         # <p /> is not valid html5; should be <p></p>. to be valid.  Even better, there should be no empty p's.
         update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{logo}}{{logo}}')
-        rendered = MoinWiki.create(u'page1')._render_data()
+        rendered = Item.create(u'page1').content._render_data()
         assert '<p />' not in rendered
         assert '<p></p>' not in rendered
--- a/MoinMoin/converter/include.py	Fri Jul 27 19:08:35 2012 +0530
+++ b/MoinMoin/converter/include.py	Sun Jul 29 02:34:27 2012 +0530
@@ -249,7 +249,7 @@
                         elem_h = ET.Element(self.tag_h, attrib, children=(elem_a, ))
                         included_elements.append(elem_h)
 
-                    page_doc = page.internal_representation()
+                    page_doc = page.content.internal_representation()
                     # page_doc.tag = self.tag_div # XXX why did we have this?
 
                     self.recurse(page_doc, page_href)
--- a/MoinMoin/items/__init__.py	Fri Jul 27 19:08:35 2012 +0530
+++ b/MoinMoin/items/__init__.py	Sun Jul 29 02:34:27 2012 +0530
@@ -8,45 +8,31 @@
 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
 
 """
-    MoinMoin - misc. mimetype items
+    MoinMoin - high-level (frontend) items
 
     While MoinMoin.storage cares for backend storage of items,
     this module cares for more high-level, frontend items,
     e.g. showing, editing, etc. of wiki items.
-"""
-# TODO: split this huge module into multiple ones after code has stabilized
 
-import os, re, time, datetime, base64
-import tarfile
-import zipfile
-import tempfile
+    Each class in this module corresponds to an itemtype.
+"""
+
+import re, time
 import itertools
 from StringIO import StringIO
-from array import array
 
-from flatland import Form, String, Integer, Boolean, Enum
-from flatland.validation import Validator, Present, IsEmail, ValueBetween, URLValidator, Converted
+from flatland import Form
+from flatland.validation import Validator
 
 from whoosh.query import Term, And, Prefix
 
-from MoinMoin.forms import RequiredText, OptionalText, File, Submit
+from MoinMoin.forms import RequiredText, OptionalText, OptionalMultilineText, Tags, Submit
 
-from MoinMoin.security.textcha import TextCha, TextChaizedForm, TextChaValid
+from MoinMoin.security.textcha import TextCha, TextChaizedForm
 from MoinMoin.signalling import item_modified
-from MoinMoin.util.mimetype import MimeType
-from MoinMoin.util.mime import Type, type_moin_document
-from MoinMoin.util.tree import moin_page, html, xlink, docbook
-from MoinMoin.util.iri import Iri
-from MoinMoin.util.crypto import cache_key
+from MoinMoin.util.mime import Type
 from MoinMoin.storage.middleware.protecting import AccessDenied
 
-try:
-    import PIL
-    from PIL import Image as PILImage
-    from PIL.ImageChops import difference as PILdiff
-except ImportError:
-    PIL = None
-
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
@@ -58,47 +44,44 @@
 from flask import current_app as app
 from flask import g as flaskg
 
-from flask import request, url_for, flash, Response, redirect, abort, escape
+from flask import request, Response, redirect, abort, escape
 
 from werkzeug import is_resource_modified
-from jinja2 import Markup
 
-from MoinMoin.i18n import _, L_, N_
+from MoinMoin.i18n import L_
 from MoinMoin.themes import render_template
-from MoinMoin import wikiutil, config, user
-from MoinMoin.util.send_file import send_file
 from MoinMoin.util.interwiki import url_for_item
 from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError, StorageError
-from MoinMoin.config import NAME, NAME_OLD, NAME_EXACT, WIKINAME, MTIME, REVERTED_TO, ACL, \
-                            IS_SYSITEM, SYSITEM_VERSION, USERGROUP, SOMEDICT, \
-                            CONTENTTYPE, SIZE, LANGUAGE, ITEMLINKS, ITEMTRANSCLUSIONS, \
-                            TAGS, ACTION, ADDRESS, HOSTNAME, USERID, EXTRA, COMMENT, \
-                            HASH_ALGORITHM, CONTENTTYPE_GROUPS, ITEMID, REVID, DATAID, \
-                            CURRENT, PARENTID
+from MoinMoin.util.registry import RegistryBase
+from MoinMoin.constants.keys import (
+    NAME, NAME_OLD, NAME_EXACT, WIKINAME, MTIME, SYSITEM_VERSION, ITEMTYPE,
+    CONTENTTYPE, SIZE, TAGS, ACTION, ADDRESS, HOSTNAME, USERID, COMMENT,
+    HASH_ALGORITHM, ITEMID, REVID, DATAID, CURRENT, PARENTID
+    )
+from MoinMoin.constants.contenttypes import charset, CONTENTTYPE_GROUPS
+
+from .content import Draw, NonExistentContent, content_registry
+
 
 COLS = 80
-ROWS_DATA = 20
 ROWS_META = 10
 
 
-from ..util.registry import RegistryBase
-
-
 class RegistryItem(RegistryBase):
     class Entry(object):
-        def __init__(self, factory, content_type, priority):
+        def __init__(self, factory, itemtype, priority):
             self.factory = factory
-            self.content_type = content_type
+            self.itemtype = itemtype
             self.priority = priority
 
-        def __call__(self, name, content_type, kw):
-            if self.content_type.issupertype(content_type):
-                return self.factory(name, content_type, **kw)
+        def __call__(self, name, itemtype, kw):
+            if self.itemtype == itemtype:
+                return self.factory(name, itemtype, **kw)
 
         def __eq__(self, other):
             if isinstance(other, self.__class__):
                 return (self.factory == other.factory and
-                        self.content_type == other.content_type and
+                        self.itemtype == other.itemtype and
                         self.priority == other.priority)
             return NotImplemented
 
@@ -106,44 +89,33 @@
             if isinstance(other, self.__class__):
                 if self.priority < other.priority:
                     return True
-                if self.content_type != other.content_type:
-                    return other.content_type.issupertype(self.content_type)
-                return False
+                return self.itemtype == other.itemtype
             return NotImplemented
 
         def __repr__(self):
             return '<{0}: {1}, prio {2} [{3!r}]>'.format(self.__class__.__name__,
-                    self.content_type,
+                    self.itemtype,
                     self.priority,
                     self.factory)
 
-    def get(self, name, content_type, **kw):
+    def get(self, name, itemtype, **kw):
         for entry in self._entries:
-            item = entry(name, content_type, kw)
+            item = entry(name, itemtype, kw)
             if item is not None:
                 return item
 
-    def register(self, factory, content_type, priority=RegistryBase.PRIORITY_MIDDLE):
+    def register(self, factory, itemtype, priority=RegistryBase.PRIORITY_MIDDLE):
         """
         Register a factory
 
         :param factory: Factory to register. Callable, must return an object.
         """
-        return self._register(self.Entry(factory, content_type, priority))
+        return self._register(self.Entry(factory, itemtype, priority))
 
 
 item_registry = RegistryItem()
 
 
-def conv_serialize(doc, namespaces, method='polyglot'):
-    out = array('u')
-    flaskg.clock.start('conv_serialize')
-    doc.write(out.fromunicode, namespaces=namespaces, method=method)
-    out = out.tounicode()
-    flaskg.clock.stop('conv_serialize')
-    return out
-
-
 class DummyRev(dict):
     """ if we have no stored Revision, we use this dummy """
     def __init__(self, item, contenttype):
@@ -163,17 +135,54 @@
         return True
 
 
+class ValidJSON(Validator):
+    """Validator for JSON
+    """
+    invalid_json_msg = L_('Invalid JSON.')
+
+    def validate(self, element, state):
+        try:
+            json.loads(element.value)
+        except:
+            return self.note_error(element, state, 'invalid_json_msg')
+        return True
+
+
+class BaseChangeForm(TextChaizedForm):
+    comment = OptionalText.using(label=L_('Comment')).with_properties(placeholder=L_("Comment about your change"))
+    submit = Submit
+
+
 class Item(object):
-    """ Highlevel (not storage) Item """
+    """ Highlevel (not storage) Item, wraps around a storage Revision"""
     @classmethod
-    def _factory(cls, name=u'', contenttype=None, **kw):
-        return cls(name, contenttype=unicode(contenttype), **kw)
+    def _factory(cls, name=u'', itemtype=None, **kw):
+        return cls(name, **kw)
 
+    # TODO split Content creation to Content.create
     @classmethod
-    def create(cls, name=u'', contenttype=None, rev_id=CURRENT, item=None):
+    def create(cls, name=u'', itemtype=None, contenttype=None, rev_id=CURRENT, item=None):
+        """
+        Create a highlevel Item by looking up :name or directly wrapping
+        :item and extract the Revision designated by :rev_id revision.
+
+        The highlevel Item is created by creating an instance of Content
+        subclass according to the item's contenttype metadata entry; The
+        :contenttype argument can be used to override contenttype. It is used
+        only when handling +convert (when deciding the contenttype of target
+        item), +modify (when creating a new item whose contenttype is not yet
+        decided), +diff and +diffraw (to coerce the Content to a common
+        super-contenttype of both revisions).
+
+        After that the Content instance, an instance of Item subclass is
+        created according to the item's itemtype metadata entry, and the
+        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]
@@ -189,6 +198,7 @@
             try:
                 rev = item.get_revision(rev_id)
                 contenttype = u'application/octet-stream' # it exists
+                itemtype = u'default' # default itemtype to u'default' for compatibility
             except KeyError: # NoSuchRevisionError:
                 try:
                     rev = item.get_revision(CURRENT) # fall back to current revision
@@ -202,120 +212,37 @@
         logging.debug("Item {0!r}, got contenttype {1!r} from revision meta".format(name, contenttype))
         #logging.debug("Item %r, rev meta dict: %r" % (name, dict(rev.meta)))
 
-        item = item_registry.get(name, Type(contenttype), rev=rev)
-        logging.debug("ItemClass {0!r} handles {1!r}".format(item.__class__, contenttype))
+        # XXX Cannot pass item=item to Content.__init__ via
+        # content_registry.get yet, have to patch it later.
+        content = content_registry.get(name, Type(contenttype))
+        logging.debug("Content class {0!r} handles {1!r}".format(content.__class__, contenttype))
+
+        itemtype = rev.meta.get(ITEMTYPE) or itemtype
+        logging.debug("Item {0!r}, got itemtype {1!r} from revision meta".format(name, itemtype))
+
+        item = item_registry.get(name, itemtype, rev=rev, content=content)
+        logging.debug("Item class {0!r} handles {1!r}".format(item.__class__, itemtype))
+
+        content.item = item
+
         return item
 
-    def __init__(self, name, rev=None, contenttype=None):
+    def __init__(self, name, rev=None, content=None):
         self.name = name
         self.rev = rev
-        self.contenttype = contenttype
+        self.content = content
 
     def get_meta(self):
         return self.rev.meta
     meta = property(fget=get_meta)
 
-    def _render_meta(self):
-        # override this in child classes
-        return ''
-
-    def internal_representation(self, converters=['smiley']):
-        """
-        Return the internal representation of a document using a DOM Tree
-        """
-        flaskg.clock.start('conv_in_dom')
-        hash_name = HASH_ALGORITHM
-        hash_hexdigest = self.rev.meta.get(hash_name)
-        if hash_hexdigest:
-            cid = cache_key(usage="internal_representation",
-                            hash_name=hash_name,
-                            hash_hexdigest=hash_hexdigest)
-            doc = app.cache.get(cid)
-        else:
-            # likely a non-existing item
-            doc = cid = None
-        if doc is None:
-            # We will see if we can perform the conversion:
-            # FROM_mimetype --> DOM
-            # if so we perform the transformation, otherwise we don't
-            from MoinMoin.converter import default_registry as reg
-            input_conv = reg.get(Type(self.contenttype), type_moin_document)
-            if not input_conv:
-                raise TypeError("We cannot handle the conversion from {0} to the DOM tree".format(self.contenttype))
-            smiley_conv = reg.get(type_moin_document, type_moin_document,
-                    icon='smiley')
-
-            # We can process the conversion
-            links = Iri(scheme='wiki', authority='', path='/' + self.name)
-            doc = input_conv(self.rev, self.contenttype)
-            # XXX is the following assuming that the top element of the doc tree
-            # is a moin_page.page element? if yes, this is the wrong place to do that
-            # as not every doc will have that element (e.g. for images, we just get
-            # moin_page.object, for a tar item, we get a moin_page.table):
-            doc.set(moin_page.page_href, unicode(links))
-            for conv in converters:
-                if conv == 'smiley':
-                    doc = smiley_conv(doc)
-            if cid:
-                app.cache.set(cid, doc)
-        flaskg.clock.stop('conv_in_dom')
-        return doc
+    # XXX Backward compatibility, remove soon
+    @property
+    def contenttype(self):
+        return self.content.contenttype if self.content else None
 
-    def _expand_document(self, doc):
-        from MoinMoin.converter import default_registry as reg
-        include_conv = reg.get(type_moin_document, type_moin_document, includes='expandall')
-        macro_conv = reg.get(type_moin_document, type_moin_document, macros='expandall')
-        link_conv = reg.get(type_moin_document, type_moin_document, links='extern')
-        flaskg.clock.start('conv_include')
-        doc = include_conv(doc)
-        flaskg.clock.stop('conv_include')
-        flaskg.clock.start('conv_macro')
-        doc = macro_conv(doc)
-        flaskg.clock.stop('conv_macro')
-        flaskg.clock.start('conv_link')
-        doc = link_conv(doc)
-        flaskg.clock.stop('conv_link')
-        return doc
-
-    def _render_data(self):
-        from MoinMoin.converter import default_registry as reg
-        # TODO: Real output format
-        doc = self.internal_representation()
-        doc = self._expand_document(doc)
-        flaskg.clock.start('conv_dom_html')
-        html_conv = reg.get(type_moin_document, Type('application/x-xhtml-moin-page'))
-        doc = html_conv(doc)
-        flaskg.clock.stop('conv_dom_html')
-        rendered_data = conv_serialize(doc, {html.namespace: ''})
-        return rendered_data
-
-    def _render_data_xml(self):
-        doc = self.internal_representation()
-        return conv_serialize(doc,
-                              {moin_page.namespace: '',
-                               xlink.namespace: 'xlink',
-                               html.namespace: 'html',
-                              },
-                              'xml')
-
-    def _render_data_highlight(self):
-        # override this in child classes
-        return ''
-
-    def _do_modify_show_templates(self):
-        # call this if the item is still empty
-        rev_ids = []
-        item_templates = self.get_templates(self.contenttype)
-        return render_template('modify_show_template_selection.html',
-                               item_name=self.name,
-                               rev=self.rev,
-                               contenttype=self.contenttype,
-                               templates=item_templates,
-                               first_rev_id=rev_ids and rev_ids[0],
-                               last_rev_id=rev_ids and rev_ids[-1],
-                               meta_rendered='',
-                               data_rendered='',
-                               )
+    def _render_meta(self):
+        return "<pre>{0}</pre>".format(escape(self.meta_dict_to_text(self.meta, use_filter=False)))
 
     def meta_filter(self, meta):
         """ kill metadata entries that we set automatically when saving """
@@ -359,31 +286,11 @@
             meta[PARENTID] = revid
         return meta
 
-    def get_data(self):
-        return '' # TODO create a better method for binary stuff
-    data = property(fget=get_data)
-
-    def _write_stream(self, content, new_rev, bufsize=8192):
-        written = 0
-        if hasattr(content, "read"):
-            while True:
-                buf = content.read(bufsize)
-                if not buf:
-                    break
-                new_rev.data.write(buf)
-                written += len(buf)
-        elif isinstance(content, str):
-            new_rev.data.write(content)
-            written += len(content)
-        else:
-            raise StorageError("unsupported content object: {0!r}".format(content))
-        return written
-
     def _rename(self, name, comment, action):
-        self._save(self.meta, self.data, name=name, action=action, comment=comment)
+        self._save(self.meta, self.content.data, name=name, action=action, comment=comment)
         for child in self.get_index():
             item = Item.create(child[0])
-            item._save(item.meta, item.data, name='/'.join((name, child[1])), action=action, comment=comment)
+            item._save(item.meta, item.content.data, name='/'.join((name, child[1])), action=action, comment=comment)
 
     def rename(self, name, comment=u''):
         """
@@ -402,7 +309,7 @@
         return self._rename(trashname, comment, action=u'TRASH')
 
     def revert(self, comment=u''):
-        return self._save(self.meta, self.data, action=u'REVERT', comment=comment)
+        return self._save(self.meta, self.content.data, action=u'REVERT', comment=comment)
 
     def destroy(self, comment=u'', destroy_item=False):
         # called from destroy UI/POST
@@ -420,6 +327,42 @@
 
         return self._save(meta, data, contenttype_guessed=contenttype_guessed, comment=comment)
 
+    class _ModifyForm(BaseChangeForm):
+        """Base class for ModifyForm of Item subclasses."""
+        meta_text = OptionalMultilineText.using(label=L_("MetaData (JSON)")).with_properties(rows=ROWS_META, cols=COLS).validated_by(ValidJSON())
+
+        def _load(self, item):
+            self['meta_text'] = item.meta_dict_to_text(item.prepare_meta_for_modify(item.meta))
+            self['content_form']._load(item.content)
+
+        def _dump(self, item):
+            meta = item.meta_text_to_dict(self['meta_text'].value)
+            data, contenttype_guessed = self['content_form']._dump(item.content)
+            comment = self['comment'].value
+            return meta, data, contenttype_guessed, comment
+
+        @classmethod
+        def from_item(cls, item):
+            form = cls.from_defaults()
+            TextCha(form).amend_form()
+            form._load(item)
+            return form
+
+        @classmethod
+        def from_request(cls, request):
+            form = cls.from_flat(request.form.items() + request.files.items())
+            TextCha(form).amend_form()
+            return form
+
+    def do_modify(self):
+        """
+        Handle +modify requests, both GET and POST.
+
+        This method should be overridden in subclasses, providing polymorphic
+        behavior for the +modify view.
+        """
+        raise NotImplementedError
+
     def _save(self, meta, data=None, name=None, action=u'SAVE', contenttype_guessed=None, comment=u'', overwrite=False):
         backend = flaskg.storage
         storage_item = backend[self.name]
@@ -459,7 +402,7 @@
                 data = ''
 
         if isinstance(data, unicode):
-            data = data.encode(config.charset) # XXX wrong! if contenttype gives a coding, we MUST use THAT.
+            data = data.encode(charset) # XXX wrong! if contenttype gives a coding, we MUST use THAT.
 
         if isinstance(data, str):
             data = StringIO(data)
@@ -599,122 +542,69 @@
     rename_template = 'rename.html'
     revert_template = 'revert.html'
 
-class NonExistent(Item):
-    def do_get(self, force_attachment=False, mimetype=None):
-        abort(404)
-
-    def _convert(self, doc):
-        abort(404)
-
-    def do_modify(self, contenttype, template_name):
-        # First, check if the current user has the required privileges
-        if not flaskg.user.may.create(self.name):
-            abort(403)
-
-        return render_template('modify_show_type_selection.html',
-                               item_name=self.name,
-                               contenttype_groups=CONTENTTYPE_GROUPS,
-                              )
 
-item_registry.register(NonExistent._factory, Type('application/x-nonexistent'))
-
-class ValidJSON(Validator):
-    """Validator for JSON
+class Contentful(Item):
     """
-    invalid_json_msg = L_('Invalid JSON.')
-
-    def validate(self, element, state):
-        try:
-            json.loads(element.value)
-        except:
-            return self.note_error(element, state, 'invalid_json_msg')
-        return True
-
-
-class BaseChangeForm(TextChaizedForm):
-    comment = OptionalText.using(label=L_('Comment')).with_properties(placeholder=L_("Comment about your change"))
-    submit = Submit
+    Base class for Item subclasses that have content.
+    """
+    @property
+    def ModifyForm(self):
+        class C(Item._ModifyForm):
+            content_form = self.content.ModifyForm
+        C.__name__ = 'ModifyForm'
+        return C
 
 
-class Binary(Item):
-    """ An arbitrary binary item, fallback class for every item mimetype. """
-    modify_help = """\
-There is no help, you're doomed!
-"""
-
-    template = "modify_binary.html"
-
-    # XXX reads item rev data into memory!
-    def get_data(self):
-        if self.rev is not None:
-            return self.rev.data.read()
-        else:
-            return ''
-    data = property(fget=get_data)
-
-    def _render_meta(self):
-        return "<pre>{0}</pre>".format(escape(self.meta_dict_to_text(self.meta, use_filter=False)))
-
-    def get_templates(self, contenttype=None):
-        """ create a list of templates (for some specific contenttype) """
-        terms = [Term(WIKINAME, app.cfg.interwikiname), Term(TAGS, u'template')]
-        if contenttype is not None:
-            terms.append(Term(CONTENTTYPE, contenttype))
-        query = And(terms)
-        revs = flaskg.storage.search(query, sortedby=NAME_EXACT, limit=None)
-        return [rev.meta[NAME] for rev in revs]
-
-    class ModifyForm(BaseChangeForm):
-        """Base class for ModifyForm of Binary's subclasses."""
-        meta_text = RequiredText.with_properties(placeholder=L_("MetaData (JSON)")).validated_by(ValidJSON())
-        data_file = File.using(optional=True, label=L_('Upload file:'))
+class Default(Contentful):
+    """
+    A "conventional" wiki item.
+    """
+    def _do_modify_show_templates(self):
+        # call this if the item is still empty
+        rev_ids = []
+        item_templates = self.content.get_templates(self.contenttype)
+        return render_template('modify_show_template_selection.html',
+                               item_name=self.name,
+                               # XXX u'default' should be a constant
+                               itemtype=u'default',
+                               rev=self.rev,
+                               contenttype=self.contenttype,
+                               templates=item_templates,
+                               first_rev_id=rev_ids and rev_ids[0],
+                               last_rev_id=rev_ids and rev_ids[-1],
+                               meta_rendered='',
+                               data_rendered='',
+                               )
 
-        def _load(self, item):
-            self['meta_text'] = item.meta_dict_to_text(item.prepare_meta_for_modify(item.meta))
-
-        def _dump(self, item):
-            data = meta = contenttype_guessed = None
-            data_file = self['data_file'].value
-            if data_file:
-                data = data_file.stream
-                # this is likely a guess by the browser, based on the filename
-                contenttype_guessed = data_file.content_type # comes from form multipart data
-            meta = item.meta_text_to_dict(self['meta_text'].value)
-            comment = self['comment'].value
-            return meta, data, contenttype_guessed, comment
-
-        extra_template_args = {}
-
-        @classmethod
-        def from_item(cls, item):
-            form = cls.from_defaults()
-            TextCha(form).amend_form()
-            form._load(item)
-            return form
-
-        @classmethod
-        def from_request(cls, request):
-            form = cls.from_flat(request.form.items() + request.files.items())
-            TextCha(form).amend_form()
-            return form
-
-    def do_modify(self, contenttype, template_name):
-        """
-        Handle +modify requests, both GET and POST.
-
-        This method can be overridden in subclasses, providing polymorphic
-        behavior for the +modify view.
-        """
+    def do_modify(self):
         method = request.method
         if method == 'GET':
+            if isinstance(self.content, NonExistentContent):
+                return render_template('modify_show_contenttype_selection.html',
+                                       item_name=self.name,
+                                       # XXX see comment above
+                                       itemtype=u'default',
+                                       contenttype_groups=CONTENTTYPE_GROUPS,
+                                      )
             item = self
             if isinstance(self.rev, DummyRev):
+                template_name = request.values.get('template')
                 if template_name is None:
                     return self._do_modify_show_templates()
                 elif template_name:
                     item = Item.create(template_name)
             form = self.ModifyForm.from_item(item)
         elif method == 'POST':
+            # XXX workaround for *Draw items
+            if isinstance(self.content, Draw):
+                try:
+                    self.content.handle_post()
+                except AccessDenied:
+                    abort(403)
+                else:
+                    # *Draw Applets POSTs more than once, redirecting would
+                    # break them
+                    return "OK"
             form = self.ModifyForm.from_request(request)
             if form.validate():
                 meta, data, contenttype_guessed, comment = form._dump(self)
@@ -725,755 +615,55 @@
                     abort(403)
                 else:
                     return redirect(url_for_item(self.name))
-        return render_template(self.template,
+        return render_template(self.modify_template,
                                item_name=self.name,
                                rows_meta=str(ROWS_META), cols=str(COLS),
-                               help=self.modify_help,
                                form=form,
                                search_form=None,
-                               **form.extra_template_args
                               )
 
-    def _render_data_diff(self, oldrev, newrev):
-        hash_name = HASH_ALGORITHM
-        if oldrev.meta[hash_name] == newrev.meta[hash_name]:
-            return _("The items have the same data hash code (that means they very likely have the same data).")
-        else:
-            return _("The items have different data.")
-
-    _render_data_diff_text = _render_data_diff
-    _render_data_diff_raw = _render_data_diff
-
-    def _render_data_diff_atom(self, oldrev, newrev):
-        return render_template('atom.html',
-                               oldrev=oldrev, newrev=newrev, get='binary',
-                               content=Markup(self._render_data()))
-
-    def _convert(self, doc):
-        return _("Impossible to convert the data to the contenttype: %(contenttype)s",
-                 contenttype=request.values.get('contenttype'))
-
-    def do_get(self, force_attachment=False, mimetype=None):
-        hash = self.rev.meta.get(HASH_ALGORITHM)
-        if is_resource_modified(request.environ, hash): # use hash as etag
-            return self._do_get_modified(hash, force_attachment=force_attachment, mimetype=mimetype)
-        else:
-            return Response(status=304)
-
-    def _do_get_modified(self, hash, force_attachment=False, mimetype=None):
-        member = request.values.get('member')
-        return self._do_get(hash, member, force_attachment=force_attachment, mimetype=mimetype)
-
-    def _do_get(self, hash, member=None, force_attachment=False, mimetype=None):
-        if member: # content = file contained within a archive item revision
-            path, filename = os.path.split(member)
-            mt = MimeType(filename=filename)
-            content_length = None
-            file_to_send = self.get_member(member)
-        else: # content = item revision
-            rev = self.rev
-            filename = rev.item.name
-            try:
-                mimestr = rev.meta[CONTENTTYPE]
-            except KeyError:
-                mt = MimeType(filename=filename)
-            else:
-                mt = MimeType(mimestr=mimestr)
-            content_length = rev.meta[SIZE]
-            file_to_send = rev.data
-        if mimetype:
-            content_type = mimetype
-        else:
-            content_type = mt.content_type()
-        as_attachment = force_attachment or mt.as_attachment(app.cfg)
-        return send_file(file=file_to_send,
-                         mimetype=content_type,
-                         as_attachment=as_attachment, attachment_filename=filename,
-                         cache_timeout=10, # wiki data can change rapidly
-                         add_etags=True, etag=hash, conditional=True)
-
-item_registry.register(Binary._factory, Type('*/*'))
-
-
-class RenderableBinary(Binary):
-    """ Base class for some binary stuff that renders with a object tag. """
-
-
-class Application(Binary):
-    """ Base class for application/* """
-
-
-class TarMixin(object):
-    """
-    TarMixin offers additional functionality for tar-like items to list and
-    access member files and to create new revisions by multiple posts.
-    """
-    def list_members(self):
-        """
-        list tar file contents (member file names)
-        """
-        self.rev.data.seek(0)
-        tf = tarfile.open(fileobj=self.rev.data, mode='r')
-        return tf.getnames()
-
-    def get_member(self, name):
-        """
-        return a file-like object with the member file data
-
-        :param name: name of the data in the container file
-        """
-        self.rev.data.seek(0)
-        tf = tarfile.open(fileobj=self.rev.data, mode='r')
-        return tf.extractfile(name)
-
-    def put_member(self, name, content, content_length, expected_members):
-        """
-        puts a new member file into a temporary tar container.
-        If all expected members have been put, it saves the tar container
-        to a new item revision.
+    modify_template = 'modify.html'
 
-        :param name: name of the data in the container file
-        :param content: the data to store into the tar file (str or file-like)
-        :param content_length: byte-length of content (for str, None can be given)
-        :param expected_members: set of expected member file names
-        """
-        if not name in expected_members:
-            raise StorageError("tried to add unexpected member {0!r} to container item {1!r}".format(name, self.name))
-        if isinstance(name, unicode):
-            name = name.encode('utf-8')
-        temp_fname = os.path.join(tempfile.gettempdir(), 'TarContainer_' +
-                                  cache_key(usage='TarContainer', name=self.name))
-        tf = tarfile.TarFile(temp_fname, mode='a')
-        ti = tarfile.TarInfo(name)
-        if isinstance(content, str):
-            if content_length is None:
-                content_length = len(content)
-            content = StringIO(content) # we need a file obj
-        elif not hasattr(content, 'read'):
-            logging.error("unsupported content object: {0!r}".format(content))
-            raise StorageError("unsupported content object: {0!r}".format(content))
-        assert content_length >= 0  # we don't want -1 interpreted as 4G-1
-        ti.size = content_length
-        tf.addfile(ti, content)
-        tf_members = set(tf.getnames())
-        tf.close()
-        if tf_members - expected_members:
-            msg = "found unexpected members in container item {0!r}".format(self.name)
-            logging.error(msg)
-            os.remove(temp_fname)
-            raise StorageError(msg)
-        if tf_members == expected_members:
-            # everything we expected has been added to the tar file, save the container as revision
-            meta = {CONTENTTYPE: self.contenttype}
-            data = open(temp_fname, 'rb')
-            self._save(meta, data, name=self.name, action=u'SAVE', comment='')
-            data.close()
-            os.remove(temp_fname)
-
-
-class ApplicationXTar(TarMixin, Application):
-    """
-    Tar items
-    """
-
-item_registry.register(ApplicationXTar._factory, Type('application/x-tar'))
-item_registry.register(ApplicationXTar._factory, Type('application/x-gtar'))
-
-
-class ZipMixin(object):
-    """
-    ZipMixin offers additional functionality for zip-like items to list and
-    access member files.
-    """
-    def list_members(self):
-        """
-        list zip file contents (member file names)
-        """
-        self.rev.data.seek(0)
-        zf = zipfile.ZipFile(self.rev.data, mode='r')
-        return zf.namelist()
-
-    def get_member(self, name):
-        """
-        return a file-like object with the member file data
-
-        :param name: name of the data in the zip file
-        """
-        self.rev.data.seek(0)
-        zf = zipfile.ZipFile(self.rev.data, mode='r')
-        return zf.open(name, mode='r')
-
-    def put_member(self, name, content, content_length, expected_members):
-        raise NotImplementedError
-
-
-class ApplicationZip(ZipMixin, Application):
-    """
-    Zip items
-    """
-
-item_registry.register(ApplicationZip._factory, Type('application/zip'))
-
-
-class PDF(Application):
-    """ PDF """
-
-item_registry.register(PDF._factory, Type('application/pdf'))
-
-
-class Video(Binary):
-    """ Base class for video/* """
-
-item_registry.register(Video._factory, Type('video/*'))
-
-
-class Audio(Binary):
-    """ Base class for audio/* """
-
-item_registry.register(Audio._factory, Type('audio/*'))
-
-
-class Image(Binary):
-    """ Base class for image/* """
-
-item_registry.register(Image._factory, Type('image/*'))
-
-
-class RenderableImage(RenderableBinary):
-    """ Base class for renderable Image mimetypes """
-
-
-class SvgImage(RenderableImage):
-    """ SVG images use <object> tag mechanism from RenderableBinary base class """
-
-item_registry.register(SvgImage._factory, Type('image/svg+xml'))
-
-
-class RenderableBitmapImage(RenderableImage):
-    """ PNG/JPEG/GIF images use <img> tag (better browser support than <object>) """
-    # if mimetype is also transformable, please register in TransformableImage ONLY!
+item_registry.register(Default._factory, u'default')
 
 
-class TransformableBitmapImage(RenderableBitmapImage):
-    """ We can transform (resize, rotate, mirror) some image types """
-    def _transform(self, content_type, size=None, transpose_op=None):
-        """ resize to new size (optional), transpose according to exif infos,
-            result data should be content_type.
-        """
-        try:
-            from PIL import Image as PILImage
-        except ImportError:
-            # no PIL, we can't do anything, we just output the revision data as is
-            return content_type, self.rev.data.read()
-
-        if content_type == 'image/jpeg':
-            output_type = 'JPEG'
-        elif content_type == 'image/png':
-            output_type = 'PNG'
-        elif content_type == 'image/gif':
-            output_type = 'GIF'
-        else:
-            raise ValueError("content_type {0!r} not supported".format(content_type))
-
-        # revision obj has read() seek() tell(), thus this works:
-        image = PILImage.open(self.rev.data)
-        image.load()
-
-        try:
-            # if we have EXIF data, we can transpose (e.g. rotate left),
-            # so the rendered image is correctly oriented:
-            transpose_op = transpose_op or 1 # or self.exif['Orientation']
-        except KeyError:
-            transpose_op = 1 # no change
-
-        if size is not None:
-            image = image.copy() # create copy first as thumbnail works in-place
-            image.thumbnail(size, PILImage.ANTIALIAS)
-
-        transpose_func = {
-            1: lambda image: image,
-            2: lambda image: image.transpose(PILImage.FLIP_LEFT_RIGHT),
-            3: lambda image: image.transpose(PILImage.ROTATE_180),
-            4: lambda image: image.transpose(PILImage.FLIP_TOP_BOTTOM),
-            5: lambda image: image.transpose(PILImage.ROTATE_90).transpose(PILImage.FLIP_TOP_BOTTOM),
-            6: lambda image: image.transpose(PILImage.ROTATE_270),
-            7: lambda image: image.transpose(PILImage.ROTATE_90).transpose(PILImage.FLIP_LEFT_RIGHT),
-            8: lambda image: image.transpose(PILImage.ROTATE_90),
-        }
-        image = transpose_func[transpose_op](image)
-
-        outfile = StringIO()
-        image.save(outfile, output_type)
-        data = outfile.getvalue()
-        outfile.close()
-        return content_type, data
-
-    def _do_get_modified(self, hash, force_attachment=False, mimetype=None):
-        try:
-            width = int(request.values.get('w'))
-        except (TypeError, ValueError):
-            width = None
-        try:
-            height = int(request.values.get('h'))
-        except (TypeError, ValueError):
-            height = None
-        try:
-            transpose = int(request.values.get('t'))
-            assert 1 <= transpose <= 8
-        except (TypeError, ValueError, AssertionError):
-            transpose = 1
-        if width or height or transpose != 1:
-            # resize requested, XXX check ACL behaviour! XXX
-            hash_name = HASH_ALGORITHM
-            hash_hexdigest = self.rev.meta[hash_name]
-            cid = cache_key(usage="ImageTransform",
-                            hash_name=hash_name,
-                            hash_hexdigest=hash_hexdigest,
-                            width=width, height=height, transpose=transpose)
-            c = app.cache.get(cid)
-            if c is None:
-                if mimetype:
-                    content_type = mimetype
-                else:
-                    content_type = self.rev.meta[CONTENTTYPE]
-                size = (width or 99999, height or 99999)
-                content_type, data = self._transform(content_type, size=size, transpose_op=transpose)
-                headers = wikiutil.file_headers(content_type=content_type, content_length=len(data))
-                app.cache.set(cid, (headers, data))
-            else:
-                # XXX TODO check ACL behaviour
-                headers, data = c
-            return Response(data, headers=headers)
-        else:
-            return self._do_get(hash, force_attachment=force_attachment, mimetype=mimetype)
-
-    def _render_data_diff_atom(self, oldrev, newrev):
-        if PIL is None:
-            # no PIL, we can't do anything, we just call the base class method
-            return super(TransformableBitmapImage, self)._render_data_diff_atom(oldrev, newrev)
-        url = url_for('frontend.diffraw', _external=True, item_name=self.name, rev1=oldrev.revid, rev2=newrev.revid)
-        return render_template('atom.html',
-                               oldrev=oldrev, newrev=newrev, get='binary',
-                               content=Markup(u'<img src="{0}" />'.format(escape(url))))
-
-    def _render_data_diff(self, oldrev, newrev):
-        if PIL is None:
-            # no PIL, we can't do anything, we just call the base class method
-            return super(TransformableBitmapImage, self)._render_data_diff(oldrev, newrev)
-        url = url_for('frontend.diffraw', item_name=self.name, rev1=oldrev.revid, rev2=newrev.revid)
-        return Markup(u'<img src="{0}" />'.format(escape(url)))
-
-    def _render_data_diff_raw(self, oldrev, newrev):
-        hash_name = HASH_ALGORITHM
-        cid = cache_key(usage="ImageDiff",
-                        hash_name=hash_name,
-                        hash_old=oldrev.meta[hash_name],
-                        hash_new=newrev.meta[hash_name])
-        c = app.cache.get(cid)
-        if c is None:
-            if PIL is None:
-                abort(404) # TODO render user friendly error image
-
-            content_type = newrev.meta[CONTENTTYPE]
-            if content_type == 'image/jpeg':
-                output_type = 'JPEG'
-            elif content_type == 'image/png':
-                output_type = 'PNG'
-            elif content_type == 'image/gif':
-                output_type = 'GIF'
-            else:
-                raise ValueError("content_type {0!r} not supported".format(content_type))
-
-            try:
-                oldimage = PILImage.open(oldrev.data)
-                newimage = PILImage.open(newrev.data)
-                oldimage.load()
-                newimage.load()
-                diffimage = PILdiff(newimage, oldimage)
-                outfile = StringIO()
-                diffimage.save(outfile, output_type)
-                data = outfile.getvalue()
-                outfile.close()
-                headers = wikiutil.file_headers(content_type=content_type, content_length=len(data))
-                app.cache.set(cid, (headers, data))
-            except (IOError, ValueError) as err:
-                logging.exception("error during PILdiff: {0}".format(err.message))
-                abort(404) # TODO render user friendly error image
-        else:
-            # XXX TODO check ACL behaviour
-            headers, data = c
-        return Response(data, headers=headers)
-
-    def _render_data_diff_text(self, oldrev, newrev):
-        return super(TransformableBitmapImage, self)._render_data_diff_text(oldrev, newrev)
-
-item_registry.register(TransformableBitmapImage._factory, Type('image/png'))
-item_registry.register(TransformableBitmapImage._factory, Type('image/jpeg'))
-item_registry.register(TransformableBitmapImage._factory, Type('image/gif'))
-
-
-class Text(Binary):
-    """ Base class for text/* """
-    template = "modify_text.html"
-
-    class ModifyForm(Binary.ModifyForm):
-        data_text = String.using(strip=False, optional=True).with_properties(placeholder=L_("Type your text here"))
-
-        def _load(self, item):
-            super(Text.ModifyForm, self)._load(item)
-            data = item.data
-            data = item.data_storage_to_internal(data)
-            data = item.data_internal_to_form(data)
-            self['data_text'] = data
-
-        def _dump(self, item):
-            meta, data, contenttype_guessed, comment = super(Text.ModifyForm, self)._dump(item)
-            if data is None:
-                data = self['data_text'].value
-                data = item.data_form_to_internal(data)
-                data = item.data_internal_to_storage(data)
-                # we know it is text and utf-8 - XXX is there a way to get the charset of the form?
-                contenttype_guessed = u'text/plain;charset=utf-8'
-            return meta, data, contenttype_guessed, comment
-
-        extra_template_args = {'rows_data': str(ROWS_DATA)}
-
-    # text/plain mandates crlf - but in memory, we want lf only
-    def data_internal_to_form(self, text):
-        """ convert data from memory format to form format """
-        return text.replace(u'\n', u'\r\n')
-
-    def data_form_to_internal(self, data):
-        """ convert data from form format to memory format """
-        return data.replace(u'\r\n', u'\n')
-
-    def data_internal_to_storage(self, text):
-        """ convert data from memory format to storage format """
-        return text.replace(u'\n', u'\r\n').encode(config.charset)
-
-    def data_storage_to_internal(self, data):
-        """ convert data from storage format to memory format """
-        return data.decode(config.charset).replace(u'\r\n', u'\n')
-
-    def _get_data_diff_html(self, oldrev, newrev, template):
-        from MoinMoin.util.diff_html import diff
-        old_text = self.data_storage_to_internal(oldrev.data.read())
-        new_text = self.data_storage_to_internal(newrev.data.read())
-        storage_item = flaskg.storage[self.name]
-        diffs = [(d[0], Markup(d[1]), d[2], Markup(d[3])) for d in diff(old_text, new_text)]
-        return render_template(template,
-                               item_name=self.name,
-                               oldrev=oldrev,
-                               newrev=newrev,
-                               diffs=diffs,
-                               )
-
-    def _render_data_diff_atom(self, oldrev, newrev):
-        """ renders diff in HTML for atom feed """
-        return self._get_data_diff_html(oldrev, newrev, 'diff_text_atom.html')
-
-    def _render_data_diff(self, oldrev, newrev):
-        return self._get_data_diff_html(oldrev, newrev, 'diff_text.html')
-
-    def _render_data_diff_text(self, oldrev, newrev):
-        from MoinMoin.util import diff_text
-        oldlines = self.data_storage_to_internal(oldrev.data.read()).split('\n')
-        newlines = self.data_storage_to_internal(newrev.data.read()).split('\n')
-        difflines = diff_text.diff(oldlines, newlines)
-        return '\n'.join(difflines)
-
-    _render_data_diff_raw = _render_data_diff
-
-    def _render_data_highlight(self):
-        from MoinMoin.converter import default_registry as reg
-        data_text = self.data_storage_to_internal(self.data)
-        # TODO: use registry as soon as it is in there
-        from MoinMoin.converter.pygments_in import Converter as PygmentsConverter
-        pygments_conv = PygmentsConverter(contenttype=self.contenttype)
-        doc = pygments_conv(data_text)
-        # TODO: Real output format
-        html_conv = reg.get(type_moin_document, Type('application/x-xhtml-moin-page'))
-        doc = html_conv(doc)
-        return conv_serialize(doc, {html.namespace: ''})
-
-item_registry.register(Text._factory, Type('text/*'))
-
-
-class MarkupItem(Text):
+class Ticket(Contentful):
     """
-    some kind of item with markup
-    (internal links and transcluded items)
+    Stub for ticket item class.
     """
 
-
-class MoinWiki(MarkupItem):
-    """ MoinMoin wiki markup """
-
-item_registry.register(MoinWiki._factory, Type('text/x.moin.wiki'))
+item_registry.register(Ticket._factory, u'ticket')
 
 
-class CreoleWiki(MarkupItem):
-    """ Creole wiki markup """
-
-item_registry.register(CreoleWiki._factory, Type('text/x.moin.creole'))
-
-
-class MediaWiki(MarkupItem):
-    """ MediaWiki markup """
-
-item_registry.register(MediaWiki._factory, Type('text/x-mediawiki'))
-
-
-class ReST(MarkupItem):
-    """ ReStructured Text markup """
-
-item_registry.register(ReST._factory, Type('text/x-rst'))
-
-
-class HTML(Text):
+class Userprofile(Item):
     """
-    HTML markup
-
-    Note: As we use html_in converter to convert this to DOM and later some
-          output converterter to produce output format (e.g. html_out for html
-          output), all(?) unsafe stuff will get lost.
-
-    Note: If raw revision data is accessed, unsafe stuff might be present!
+    Currently userprofile is implemented as a contenttype. This is a stub of an
+    itemtype implementation of userprofile.
     """
-    template = "modify_text_html.html"
-
-item_registry.register(HTML._factory, Type('text/html'))
-
-
-class DocBook(MarkupItem):
-    """ DocBook Document """
-    def _convert(self, doc):
-        from emeraldtree import ElementTree as ET
-        from MoinMoin.converter import default_registry as reg
-
-        doc = self._expand_document(doc)
-
-        # We convert the internal representation of the document
-        # into a DocBook document
-        conv = reg.get(type_moin_document, Type('application/docbook+xml'))
-
-        doc = conv(doc)
 
-        # We determine the different namespaces of the output form
-        output_namespaces = {
-             docbook.namespace: '',
-             xlink.namespace: 'xlink',
-         }
-
-        # We convert the result into a StringIO object
-        # With the appropriate namespace
-        # TODO: Some other operation should probably be done here too
-        # like adding a doctype
-        file_to_send = StringIO()
-        tree = ET.ElementTree(doc)
-        tree.write(file_to_send, namespaces=output_namespaces)
-
-        # We determine the different parameters for the reply
-        mt = MimeType(mimestr='application/docbook+xml;charset=utf-8')
-        content_type = mt.content_type()
-        as_attachment = mt.as_attachment(app.cfg)
-        # After creation of the StringIO, we are at the end of the file
-        # so position is the size the file.
-        # and then we should move it back at the beginning of the file
-        content_length = file_to_send.tell()
-        file_to_send.seek(0)
-        # Important: empty filename keeps flask from trying to autodetect filename,
-        # as this would not work for us, because our file's are not necessarily fs files.
-        return send_file(file=file_to_send,
-                         mimetype=content_type,
-                         as_attachment=as_attachment, attachment_filename=None,
-                         cache_timeout=10, # wiki data can change rapidly
-                         add_etags=False, etag=None, conditional=True)
-
-item_registry.register(DocBook._factory, Type('application/docbook+xml'))
-
-
-class Draw(TarMixin, Image):
-    """
-    Base class for *Draw that use special Java/Javascript applets to modify and store data in a tar file.
-    """
-    class ModifyForm(Binary.ModifyForm):
-        pass
-
-    def handle_post():
-        raise NotImplementedError
-
-    def do_modify(self, contenttype, template_name):
-        # XXX as the "saving" POSTs come from *Draw applets (not the form),
-        # they need to be handled specially for each applet. Besides, editing
-        # meta_text doesn't work
-        if request.method == 'POST':
-            try:
-                self.handle_post()
-            except AccessDenied:
-                abort(403)
-            else:
-                # *Draw Applets POSTs more than once, redirecting would break them
-                return "OK"
-        else:
-            return super(Draw, self).do_modify(contenttype, template_name)
+item_registry.register(Userprofile._factory, u'userprofile')
 
 
-class TWikiDraw(Draw):
-    """
-    drawings by TWikiDraw applet. It creates three files which are stored as tar file.
-    """
-    modify_help = ""
-    template = "modify_twikidraw.html"
-
-    def handle_post(self):
-        # called from modify UI/POST
-        file_upload = request.files.get('filepath')
-        filename = request.form['filename']
-        basepath, basename = os.path.split(filename)
-        basename, ext = os.path.splitext(basename)
-
-        filecontent = file_upload.stream
-        content_length = None
-        if ext == '.draw': # TWikiDraw POSTs this first
-            filecontent = filecontent.read() # read file completely into memory
-            filecontent = filecontent.replace("\r", "")
-        elif ext == '.map':
-            filecontent = filecontent.read() # read file completely into memory
-            filecontent = filecontent.strip()
-        elif ext == '.png':
-            #content_length = file_upload.content_length
-            # XXX gives -1 for wsgiref, gives 0 for werkzeug :(
-            # If this is fixed, we could use the file obj, without reading it into memory completely:
-            filecontent = filecontent.read()
-
-        self.put_member('drawing' + ext, filecontent, content_length,
-                        expected_members=set(['drawing.draw', 'drawing.map', 'drawing.png']))
-
-    def _render_data(self):
-        # TODO: this could be a converter -> dom, then transcluding this kind
-        # of items and also rendering them with the code in base class could work
-        item_name = self.name
-        drawing_url = url_for('frontend.get_item', item_name=item_name, member='drawing.draw', rev=self.rev.revid)
-        png_url = url_for('frontend.get_item', item_name=item_name, member='drawing.png', rev=self.rev.revid)
-        title = _('Edit drawing %(filename)s (opens in new window)', filename=item_name)
-
-        mapfile = self.get_member('drawing.map')
-        try:
-            image_map = mapfile.read()
-            mapfile.close()
-        except (IOError, OSError):
-            image_map = ''
-        if image_map:
-            # we have a image map. inline it and add a map ref to the img tag
-            mapid = 'ImageMapOf' + item_name
-            image_map = image_map.replace('%MAPNAME%', mapid)
-            # add alt and title tags to areas
-            image_map = re.sub(r'href\s*=\s*"((?!%TWIKIDRAW%).+?)"', r'href="\1" alt="\1" title="\1"', image_map)
-            image_map = image_map.replace('%TWIKIDRAW%"', '{0}" alt="{1}" title="{2}"'.format((drawing_url, title, title)))
-            title = _('Clickable drawing: %(filename)s', filename=item_name)
-
-            return Markup(image_map + u'<img src="{0}" alt="{1}" usemap="#{2}" />'.format(png_url, title, mapid))
-        else:
-            return Markup(u'<img src="{0}" alt="{1}" />'.format(png_url, title))
-
-item_registry.register(TWikiDraw._factory, Type('application/x-twikidraw'))
-
-
-class AnyWikiDraw(Draw):
-    """
-    drawings by AnyWikiDraw applet. It creates three files which are stored as tar file.
-    """
-    modify_help = ""
-    template = "modify_anywikidraw.html"
-
-    class ModifyForm(Draw.ModifyForm):
-        def _load(self, item):
-            super(AnyWikiDraw.ModifyForm, self)._load(item)
-            try:
-                drawing_exists = 'drawing.svg' in item.list_members()
-            except tarfile.TarError: # item doesn't exist yet
-                drawing_exists = False
-            self.extra_template_args = {'drawing_exists': drawing_exists}
+class NonExistent(Item):
+    def _convert(self, doc):
+        abort(404)
 
-    def handle_post(self):
-        # called from modify UI/POST
-        file_upload = request.files.get('filepath')
-        filename = request.form['filename']
-        basepath, basename = os.path.split(filename)
-        basename, ext = os.path.splitext(basename)
-        filecontent = file_upload.stream
-        content_length = None
-        if ext == '.svg':
-            filecontent = filecontent.read() # read file completely into memory
-            filecontent = filecontent.replace("\r", "")
-        elif ext == '.map':
-            filecontent = filecontent.read() # read file completely into memory
-            filecontent = filecontent.strip()
-        elif ext == '.png':
-            #content_length = file_upload.content_length
-            # XXX gives -1 for wsgiref, gives 0 for werkzeug :(
-            # If this is fixed, we could use the file obj, without reading it into memory completely:
-            filecontent = filecontent.read()
-        self.put_member('drawing' + ext, filecontent, content_length,
-                        expected_members=set(['drawing.svg', 'drawing.map', 'drawing.png']))
-
-    def _render_data(self):
-        # TODO: this could be a converter -> dom, then transcluding this kind
-        # of items and also rendering them with the code in base class could work
-        item_name = self.name
-        drawing_url = url_for('frontend.get_item', item_name=item_name, member='drawing.svg', rev=self.rev.revid)
-        png_url = url_for('frontend.get_item', item_name=item_name, member='drawing.png', rev=self.rev.revid)
-        title = _('Edit drawing %(filename)s (opens in new window)', filename=self.name)
+    def do_modify(self):
+        # First, check if the current user has the required privileges
+        if not flaskg.user.may.create(self.name):
+            abort(403)
 
-        mapfile = self.get_member('drawing.map')
-        try:
-            image_map = mapfile.read()
-            mapfile.close()
-        except (IOError, OSError):
-            image_map = ''
-        if image_map:
-            # ToDo mapid must become uniq
-            # we have a image map. inline it and add a map ref to the img tag
-            # we have also to set a unique ID
-            mapid = 'ImageMapOf' + self.name
-            image_map = image_map.replace(u'id="drawing.svg"', '')
-            image_map = image_map.replace(u'name="drawing.svg"', u'name="{0}"'.format(mapid))
-            # unxml, because 4.01 concrete will not validate />
-            image_map = image_map.replace(u'/>', u'>')
-            title = _('Clickable drawing: %(filename)s', filename=self.name)
-            return Markup(image_map + u'<img src="{0}" alt="{1}" usemap="#{2}" />'.format(png_url, title, mapid))
-        else:
-            return Markup(u'<img src="{0}" alt="{1}" />'.format(png_url, title))
-
-item_registry.register(AnyWikiDraw._factory, Type('application/x-anywikidraw'))
-
+        # 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.
+        ITEMTYPES = [
+            (u'default', u'Default', 'Wiki item'),
+            (u'ticket', u'Ticket', 'Ticket item'),
+        ]
 
-class SvgDraw(Draw):
-    """ drawings by svg-edit. It creates two files (svg, png) which are stored as tar file. """
-    modify_help = ""
-    template = "modify_svg-edit.html"
+        return render_template('modify_show_itemtype_selection.html',
+                               item_name=self.name,
+                               itemtypes=ITEMTYPES,
+                              )
 
-    def handle_post(self):
-        # called from modify UI/POST
-        png_upload = request.values.get('png_data')
-        svg_upload = request.values.get('filepath')
-        filename = request.form['filename']
-        png_content = png_upload.decode('base_64')
-        png_content = base64.urlsafe_b64decode(png_content.split(',')[1])
-        svg_content = svg_upload.decode('base_64')
-        content_length = None
-        self.put_member("drawing.svg", svg_content, content_length,
-                        expected_members=set(['drawing.svg', 'drawing.png']))
-        self.put_member("drawing.png", png_content, content_length,
-                        expected_members=set(['drawing.svg', 'drawing.png']))
-
-    def _render_data(self):
-        # TODO: this could be a converter -> dom, then transcluding this kind
-        # of items and also rendering them with the code in base class could work
-        item_name = self.name
-        drawing_url = url_for('frontend.get_item', item_name=item_name, member='drawing.svg', rev=self.rev.revid)
-        png_url = url_for('frontend.get_item', item_name=item_name, member='drawing.png', rev=self.rev.revid)
-        return Markup(u'<img src="{0}" alt="{1}" />'.format(png_url, drawing_url))
-
-item_registry.register(SvgDraw._factory, Type('application/x-svgdraw'))
+item_registry.register(NonExistent._factory, u'nonexistent')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/items/_tests/test_Content.py	Sun Jul 29 02:34:27 2012 +0530
@@ -0,0 +1,252 @@
+# Copyright: 2012 MoinMoin:CheerXiao
+# Copyright: 2009 MoinMoin:ThomasWaldmann
+# TODO: Split tests for Content subclasses after the subclasses themselves are
+# split
+
+"""
+    MoinMoin - MoinMoin.items.content Tests
+"""
+
+import pytest
+
+from flask import g as flaskg
+from flask import Markup
+
+from werkzeug import escape
+
+from MoinMoin.util import diff_html
+
+from MoinMoin._tests import become_trusted, update_item
+from MoinMoin.items import Item
+from MoinMoin.items.content import ApplicationXTar, Binary, Text, Image, TransformableBitmapImage, MarkupItem
+from MoinMoin.config import CONTENTTYPE, ADDRESS, COMMENT, HOSTNAME, USERID, ACTION
+
+
+# TODO: When testing Content and derived classes, instead of creating a full
+# item with Item.create and test against item.content, implement
+# Content.create and test for returned Content instance directly. Item.create
+# will still be needed when it is required to create new items in the storage
+# to set up the test environment, but its use should be restricted to that
+# only.
+class TestContent(object):
+    """ Test for arbitrary content """
+
+    def test_get_templates(self):
+        item_name1 = u'Template_Item1'
+        item1 = Item.create(item_name1)
+        contenttype1 = u'text/plain'
+        meta = {CONTENTTYPE: contenttype1, 'tags': ['template']}
+        item1._save(meta)
+        item1 = Item.create(item_name1)
+
+        item_name2 = u'Template_Item2'
+        item2 = Item.create(item_name2)
+        contenttype1 = u'text/plain'
+        meta = {CONTENTTYPE: contenttype1, 'tags': ['template']}
+        item2._save(meta)
+        item2 = Item.create(item_name2)
+
+        item_name3 = u'Template_Item3'
+        item3 = Item.create(item_name3)
+        contenttype2 = u'image/png'
+        meta = {CONTENTTYPE: contenttype2, 'tags': ['template']}
+        item3._save(meta)
+        item3 = Item.create(item_name3)
+        # two items of same content type
+        result1 = item1.content.get_templates(contenttype1)
+        assert result1 == [item_name1, item_name2]
+        # third of different content type
+        result2 = item1.content.get_templates(contenttype2)
+        assert result2 == [item_name3]
+
+class TestTarItems(object):
+    """
+    tests for the container items
+    """
+
+    def testCreateContainerRevision(self):
+        """
+        creates a container and tests the content saved to the container
+        """
+        item_name = u'ContainerItem1'
+        item = Item.create(item_name, itemtype=u'default', contenttype=u'application/x-tar')
+        filecontent = 'abcdefghij'
+        content_length = len(filecontent)
+        members = set(['example1.txt', 'example2.txt'])
+        item.content.put_member('example1.txt', filecontent, content_length, expected_members=members)
+        item.content.put_member('example2.txt', filecontent, content_length, expected_members=members)
+
+        item = Item.create(item_name, itemtype=u'default', contenttype=u'application/x-tar')
+        tf_names = set(item.content.list_members())
+        assert tf_names == members
+        assert item.content.get_member('example1.txt').read() == filecontent
+
+    def testRevisionUpdate(self):
+        """
+        creates two revisions of a container item
+        """
+        item_name = u'ContainerItem2'
+        item = Item.create(item_name, itemtype=u'default', contenttype=u'application/x-tar')
+        filecontent = 'abcdefghij'
+        content_length = len(filecontent)
+        members = set(['example1.txt'])
+        item.content.put_member('example1.txt', filecontent, content_length, expected_members=members)
+        filecontent = 'AAAABBBB'
+        content_length = len(filecontent)
+        item.content.put_member('example1.txt', filecontent, content_length, expected_members=members)
+
+        item = Item.create(item_name, contenttype=u'application/x-tar')
+        assert item.content.get_member('example1.txt').read() == filecontent
+
+class TestZipMixin(object):
+    """ Test for zip-like items """
+
+    def test_put_member(self):
+        item_name = u'Zip_file'
+        item = Item.create(item_name, itemtype=u'default', contenttype='application/zip')
+        filecontent = 'test_contents'
+        content_length = len(filecontent)
+        members = set(['example1.txt', 'example2.txt'])
+        with pytest.raises(NotImplementedError):
+            item.content.put_member('example1.txt', filecontent, content_length, expected_members=members)
+
+class TestTransformableBitmapImage(object):
+
+    def test__transform(self):
+        item_name = u'image_Item'
+        item = Item.create(item_name)
+        contenttype = u'image/jpeg'
+        meta = {CONTENTTYPE: contenttype}
+        item._save(meta)
+        item = Item.create(item_name)
+        try:
+            from PIL import Image as PILImage
+            with pytest.raises(ValueError):
+                result = TransformableBitmapImage._transform(item.content, 'text/plain')
+        except ImportError:
+            result = TransformableBitmapImage._transform(item.content, contenttype)
+            assert result == (u'image/jpeg', '')
+
+    def test__render_data_diff(self):
+        item_name = u'image_Item'
+        item = Item.create(item_name)
+        contenttype = u'image/jpeg'
+        meta = {CONTENTTYPE: contenttype}
+        item._save(meta)
+        item1 = Item.create(item_name)
+        try:
+            from PIL import Image as PILImage
+            result = Markup(TransformableBitmapImage._render_data_diff(item1.content, item.rev, item1.rev))
+            # On Werkzeug 0.8.2+, urls with '+' are automatically encoded to '%2B'
+            # The assert statement works with both older and newer versions of Werkzeug
+            # Probably not an intentional change on the werkzeug side, see issue:
+            # https://github.com/mitsuhiko/werkzeug/issues/146
+            assert str(result).startswith('<img src="/+diffraw/image_Item?rev') or \
+                    str(result).startswith('<img src="/%2Bdiffraw/image_Item?rev')
+        except ImportError:
+            # no PIL
+            pass
+
+    def test__render_data_diff_text(self):
+        item_name = u'image_Item'
+        item = Item.create(item_name)
+        contenttype = u'image/jpeg'
+        meta = {CONTENTTYPE: contenttype}
+        item._save(meta)
+        item1 = Item.create(item_name)
+        data = 'test_data'
+        comment = u'next revision'
+        item1._save(meta, data, comment=comment)
+        item2 = Item.create(item_name)
+        try:
+            from PIL import Image as PILImage
+            result = TransformableBitmapImage._render_data_diff_text(item1.content, item1.rev, item2.rev)
+            expected = u'The items have different data.'
+            assert result == expected
+        except ImportError:
+            pass
+
+class TestText(object):
+
+    def test_data_conversion(self):
+        item_name = u'Text_Item'
+        item = Item.create(item_name, u'default', u'text/plain')
+        test_text = u'This \n is \n a \n Test'
+        # test for data_internal_to_form
+        result = Text.data_internal_to_form(item.content, test_text)
+        expected = u'This \r\n is \r\n a \r\n Test'
+        assert result == expected
+        # test for data_form_to_internal
+        test_form = u'This \r\n is \r\n a \r\n Test'
+        result = Text.data_form_to_internal(item.content, test_text)
+        expected = test_text
+        assert result == expected
+        # test for data_internal_to_storage
+        result = Text.data_internal_to_storage(item.content, test_text)
+        expected = 'This \r\n is \r\n a \r\n Test'
+        assert result == expected
+        # test for data_storage_to_internal
+        data_storage = 'This \r\n is \r\n a \r\n Test'
+        result = Text.data_storage_to_internal(item.content, data_storage)
+        expected = test_text
+        assert result == expected
+
+    def test__render_data_diff(self):
+        item_name = u'Html_Item'
+        empty_html = u'<span></span>'
+        html = u'<span>\ud55c</span>'
+        meta = {CONTENTTYPE: u'text/html;charset=utf-8'}
+        item = Item.create(item_name)
+        item._save(meta, empty_html)
+        item = Item.create(item_name)
+        # Unicode test, html escaping
+        rev1 = update_item(item_name, meta, html)
+        rev2 = update_item(item_name, {}, u'     ')
+        result = Text._render_data_diff(item.content, rev1, rev2)
+        assert escape(html) in result
+        # Unicode test, whitespace
+        rev1 = update_item(item_name, {}, u'\n\n')
+        rev2 = update_item(item_name, {}, u'\n     \n')
+        result = Text._render_data_diff(item.content, rev1, rev2)
+        assert '<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>' in result
+        # If fairly similar diffs are correctly spanned or not, also check indent
+        rev1 = update_item(item_name, {}, u'One Two Three Four\nSix\n\ud55c')
+        rev2 = update_item(item_name, {}, u'Two Three Seven Four\nSix\n\ud55c')
+        result = Text._render_data_diff(item.content, rev1, rev2)
+        assert '<span>One </span>Two Three Four' in result
+        assert 'Two Three <span>Seven </span>Four' in result
+        # Check for diff_html.diff return types
+        assert reduce(lambda x, y: x and y, [isinstance(i[1], unicode) and isinstance(i[3], unicode) for i in diff_html.diff(u'One Two Three Four\nSix\n', u'Two Three Seven Four\nSix Seven\n')], True)
+
+    def test__render_data_diff_text(self):
+        item_name = u'Text_Item'
+        item = Item.create(item_name)
+        contenttype = u'text/plain'
+        meta = {CONTENTTYPE: contenttype}
+        item._save(meta)
+        item1 = Item.create(item_name)
+        data = 'test_data'
+        comment = u'next revision'
+        item1._save(meta, data, comment=comment)
+        item2 = Item.create(item_name)
+        result = Text._render_data_diff_text(item1.content, item1.rev, item2.rev)
+        expected = u'- \n+ test_data'
+        assert result == expected
+        assert item2.content.data == ''
+
+    def test__render_data_highlight(self):
+        item_name = u'Text_Item'
+        item = Item.create(item_name)
+        contenttype = u'text/plain'
+        meta = {CONTENTTYPE: contenttype}
+        item._save(meta)
+        item1 = Item.create(item_name)
+        data = 'test_data\nnext line'
+        comment = u'next revision'
+        item1._save(meta, data, comment=comment)
+        item2 = Item.create(item_name)
+        result = Text._render_data_highlight(item2.content)
+        assert u'<pre class="highlight">test_data\n' in result
+        assert item2.content.data == ''
+
+coverage_modules = ['MoinMoin.items.content']
--- a/MoinMoin/items/_tests/test_Item.py	Fri Jul 27 19:08:35 2012 +0530
+++ b/MoinMoin/items/_tests/test_Item.py	Sun Jul 29 02:34:27 2012 +0530
@@ -1,3 +1,4 @@
+# Copyright: 2012 MoinMoin:CheerXiao
 # Copyright: 2009 MoinMoin:ThomasWaldmann
 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
 
@@ -5,8 +6,6 @@
     MoinMoin - MoinMoin.items Tests
 """
 
-# TODO: split the tests into multiple files after the items/__init__.py is split.
-
 import pytest
 
 from flask import g as flaskg
@@ -17,7 +16,8 @@
 from MoinMoin.util import diff_html
 
 from MoinMoin._tests import become_trusted, update_item
-from MoinMoin.items import Item, ApplicationXTar, NonExistent, Binary, Text, Image, TransformableBitmapImage, MarkupItem
+from MoinMoin.items import Item, NonExistent
+from MoinMoin.items.content import Binary, Text, Image, TransformableBitmapImage, MarkupItem
 from MoinMoin.config import CONTENTTYPE, ADDRESS, COMMENT, HOSTNAME, USERID, ACTION
 
 class TestItem(object):
@@ -25,10 +25,11 @@
     def testNonExistent(self):
         item = Item.create(u'DoesNotExist')
         assert isinstance(item, NonExistent)
-        meta, data = item.meta, item.data
+        meta, data = item.meta, item.content.data
         assert meta == {CONTENTTYPE: u'application/x-nonexistent'}
         assert data == ''
 
+    # TODO move this to testContent after implementing Content.create
     def testClassFinder(self):
         for contenttype, ExpectedClass in [
                 (u'application/x-foobar', Binary),
@@ -38,7 +39,7 @@
                 (u'image/png', TransformableBitmapImage),
             ]:
             item = Item.create(u'foo', contenttype=contenttype)
-            assert isinstance(item, ExpectedClass)
+            assert isinstance(item.content, ExpectedClass)
 
     def testCRUD(self):
         name = u'NewItem'
@@ -52,7 +53,7 @@
         item._save(meta, data, comment=comment)
         # check save result
         item = Item.create(name)
-        saved_meta, saved_data = item.meta, item.data
+        saved_meta, saved_data = item.meta, item.content.data
         assert saved_meta[CONTENTTYPE] == contenttype
         assert saved_meta[COMMENT] == comment
         assert saved_data == data
@@ -63,7 +64,7 @@
         item._save(meta, data, comment=comment)
         # check save result
         item = Item.create(name)
-        saved_meta, saved_data = dict(item.meta), item.data
+        saved_meta, saved_data = dict(item.meta), item.content.data
         assert saved_meta[CONTENTTYPE] == contenttype
         assert saved_meta[COMMENT] == comment
         assert saved_data == data
@@ -74,7 +75,7 @@
         item._save(meta, data, comment=comment)
         # check save result
         item = Item.create(name)
-        saved_meta, saved_data = dict(item.meta), item.data
+        saved_meta, saved_data = dict(item.meta), item.content.data
         assert saved_meta[CONTENTTYPE] == contenttype
         assert saved_meta[COMMENT] == comment
         assert saved_data == data
@@ -206,7 +207,7 @@
         assert item.name == u'Test_new_Item'
         assert item.meta['name_old'] == u'Test_Item'
         assert item.meta['comment'] == u'renamed'
-        assert item.data == u'test_data'
+        assert item.content.data == u'test_data'
 
     def test_rename_recursion(self):
         update_item(u'Page', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Page 1')
@@ -232,19 +233,19 @@
         assert item.name == u'Renamed_Page'
         assert item.meta['name_old'] == u'Page'
         assert item.meta['comment'] == u'renamed'
-        assert item.data == u'Page 1'
+        assert item.content.data == u'Page 1'
 
         item = Item.create(u'Renamed_Page/Child')
         assert item.name == u'Renamed_Page/Child'
         assert item.meta['name_old'] == u'Page/Child'
         assert item.meta['comment'] == u'renamed'
-        assert item.data == u'this is child'
+        assert item.content.data == u'this is child'
 
         item = Item.create(u'Renamed_Page/Child/Another')
         assert item.name == u'Renamed_Page/Child/Another'
         assert item.meta['name_old'] == u'Page/Child/Another'
         assert item.meta['comment'] == u'renamed'
-        assert item.data == u'another child'
+        assert item.content.data == u'another child'
 
     def test_delete(self):
         name = u'Test_Item2'
@@ -298,228 +299,6 @@
         with pytest.raises(KeyError):
             item.meta['test_key']
         assert item.meta['another_test_key'] == another_meta['another_test_key']
-        assert item.data == another_data
-
-
-class TestBinary(object):
-    """ Test for arbitrary binary items """
-
-    def test_get_templates(self):
-        item_name1 = u'Template_Item1'
-        item1 = Binary.create(item_name1)
-        contenttype1 = u'text/plain'
-        meta = {CONTENTTYPE: contenttype1, 'tags': ['template']}
-        item1._save(meta)
-        item1 = Binary.create(item_name1)
-
-        item_name2 = u'Template_Item2'
-        item2 = Binary.create(item_name2)
-        contenttype1 = u'text/plain'
-        meta = {CONTENTTYPE: contenttype1, 'tags': ['template']}
-        item2._save(meta)
-        item2 = Binary.create(item_name2)
-
-        item_name3 = u'Template_Item3'
-        item3 = Binary.create(item_name3)
-        contenttype2 = u'image/png'
-        meta = {CONTENTTYPE: contenttype2, 'tags': ['template']}
-        item3._save(meta)
-        item3 = Binary.create(item_name3)
-        # two items of same content type
-        result1 = item1.get_templates(contenttype1)
-        assert result1 == [item_name1, item_name2]
-        # third of different content type
-        result2 = item1.get_templates(contenttype2)
-        assert result2 == [item_name3]
-
-class TestTarItems(object):
-    """
-    tests for the container items
-    """
-
-    def testCreateContainerRevision(self):
-        """
-        creates a container and tests the content saved to the container
-        """
-        item_name = u'ContainerItem1'
-        item = Item.create(item_name, contenttype=u'application/x-tar')
-        filecontent = 'abcdefghij'
-        content_length = len(filecontent)
-        members = set(['example1.txt', 'example2.txt'])
-        item.put_member('example1.txt', filecontent, content_length, expected_members=members)
-        item.put_member('example2.txt', filecontent, content_length, expected_members=members)
-
-        item = Item.create(item_name, contenttype=u'application/x-tar')
-        tf_names = set(item.list_members())
-        assert tf_names == members
-        assert item.get_member('example1.txt').read() == filecontent
-
-    def testRevisionUpdate(self):
-        """
-        creates two revisions of a container item
-        """
-        item_name = u'ContainerItem2'
-        item = Item.create(item_name, contenttype=u'application/x-tar')
-        filecontent = 'abcdefghij'
-        content_length = len(filecontent)
-        members = set(['example1.txt'])
-        item.put_member('example1.txt', filecontent, content_length, expected_members=members)
-        filecontent = 'AAAABBBB'
-        content_length = len(filecontent)
-        item.put_member('example1.txt', filecontent, content_length, expected_members=members)
-
-        item = Item.create(item_name, contenttype=u'application/x-tar')
-        assert item.get_member('example1.txt').read() == filecontent
-
-class TestZipMixin(object):
-    """ Test for zip-like items """
-
-    def test_put_member(self):
-        item_name = u'Zip_file'
-        item = Item.create(item_name, contenttype='application/zip')
-        filecontent = 'test_contents'
-        content_length = len(filecontent)
-        members = set(['example1.txt', 'example2.txt'])
-        with pytest.raises(NotImplementedError):
-            item.put_member('example1.txt', filecontent, content_length, expected_members=members)
-
-class TestTransformableBitmapImage(object):
-
-    def test__transform(self):
-        item_name = u'image_Item'
-        item = Binary.create(item_name)
-        contenttype = u'image/jpeg'
-        meta = {CONTENTTYPE: contenttype}
-        item._save(meta)
-        item = Binary.create(item_name)
-        try:
-            from PIL import Image as PILImage
-            with pytest.raises(ValueError):
-                result = TransformableBitmapImage._transform(item, 'text/plain')
-        except ImportError:
-            result = TransformableBitmapImage._transform(item, contenttype)
-            assert result == (u'image/jpeg', '')
-
-    def test__render_data_diff(self):
-        item_name = u'image_Item'
-        item = Binary.create(item_name)
-        contenttype = u'image/jpeg'
-        meta = {CONTENTTYPE: contenttype}
-        item._save(meta)
-        item1 = Binary.create(item_name)
-        try:
-            from PIL import Image as PILImage
-            result = Markup(TransformableBitmapImage._render_data_diff(item1, item.rev, item1.rev))
-            # On Werkzeug 0.8.2+, urls with '+' are automatically encoded to '%2B'
-            # The assert statement works with both older and newer versions of Werkzeug
-            # Probably not an intentional change on the werkzeug side, see issue:
-            # https://github.com/mitsuhiko/werkzeug/issues/146
-            assert str(result).startswith('<img src="/+diffraw/image_Item?rev') or \
-                    str(result).startswith('<img src="/%2Bdiffraw/image_Item?rev')
-        except ImportError:
-            # no PIL
-            pass
-
-    def test__render_data_diff_text(self):
-        item_name = u'image_Item'
-        item = Binary.create(item_name)
-        contenttype = u'image/jpeg'
-        meta = {CONTENTTYPE: contenttype}
-        item._save(meta)
-        item1 = Binary.create(item_name)
-        data = 'test_data'
-        comment = u'next revision'
-        item1._save(meta, data, comment=comment)
-        item2 = Binary.create(item_name)
-        try:
-            from PIL import Image as PILImage
-            result = TransformableBitmapImage._render_data_diff_text(item1, item1.rev, item2.rev)
-            expected = u'The items have different data.'
-            assert result == expected
-        except ImportError:
-            pass
-
-class TestText(object):
-
-    def test_data_conversion(self):
-        item_name = u'Text_Item'
-        item = Text.create(item_name, u'text/plain')
-        test_text = u'This \n is \n a \n Test'
-        # test for data_internal_to_form
-        result = Text.data_internal_to_form(item, test_text)
-        expected = u'This \r\n is \r\n a \r\n Test'
-        assert result == expected
-        # test for data_form_to_internal
-        test_form = u'This \r\n is \r\n a \r\n Test'
-        result = Text.data_form_to_internal(item, test_text)
-        expected = test_text
-        assert result == expected
-        # test for data_internal_to_storage
-        result = Text.data_internal_to_storage(item, test_text)
-        expected = 'This \r\n is \r\n a \r\n Test'
-        assert result == expected
-        # test for data_storage_to_internal
-        data_storage = 'This \r\n is \r\n a \r\n Test'
-        result = Text.data_storage_to_internal(item, data_storage)
-        expected = test_text
-        assert result == expected
-
-    def test__render_data_diff(self):
-        item_name = u'Html_Item'
-        empty_html = u'<span></span>'
-        html = u'<span>\ud55c</span>'
-        meta = {CONTENTTYPE: u'text/html;charset=utf-8'}
-        item = Text.create(item_name)
-        item._save(meta, empty_html)
-        item = Text.create(item_name)
-        # Unicode test, html escaping
-        rev1 = update_item(item_name, meta, html)
-        rev2 = update_item(item_name, {}, u'     ')
-        result = Text._render_data_diff(item, rev1, rev2)
-        assert escape(html) in result
-        # Unicode test, whitespace
-        rev1 = update_item(item_name, {}, u'\n\n')
-        rev2 = update_item(item_name, {}, u'\n     \n')
-        result = Text._render_data_diff(item, rev1, rev2)
-        assert '<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>' in result
-        # If fairly similar diffs are correctly spanned or not, also check indent
-        rev1 = update_item(item_name, {}, u'One Two Three Four\nSix\n\ud55c')
-        rev2 = update_item(item_name, {}, u'Two Three Seven Four\nSix\n\ud55c')
-        result = Text._render_data_diff(item, rev1, rev2)
-        assert '<span>One </span>Two Three Four' in result
-        assert 'Two Three <span>Seven </span>Four' in result
-        # Check for diff_html.diff return types
-        assert reduce(lambda x, y: x and y, [isinstance(i[1], unicode) and isinstance(i[3], unicode) for i in diff_html.diff(u'One Two Three Four\nSix\n', u'Two Three Seven Four\nSix Seven\n')], True)
-
-    def test__render_data_diff_text(self):
-        item_name = u'Text_Item'
-        item = Text.create(item_name)
-        contenttype = u'text/plain'
-        meta = {CONTENTTYPE: contenttype}
-        item._save(meta)
-        item1 = Text.create(item_name)
-        data = 'test_data'
-        comment = u'next revision'
-        item1._save(meta, data, comment=comment)
-        item2 = Text.create(item_name)
-        result = Text._render_data_diff_text(item1, item1.rev, item2.rev)
-        expected = u'- \n+ test_data'
-        assert result == expected
-        assert item2.data == ''
-
-    def test__render_data_highlight(self):
-        item_name = u'Text_Item'
-        item = Text.create(item_name)
-        contenttype = u'text/plain'
-        meta = {CONTENTTYPE: contenttype}
-        item._save(meta)
-        item1 = Text.create(item_name)
-        data = 'test_data\nnext line'
-        comment = u'next revision'
-        item1._save(meta, data, comment=comment)
-        item2 = Text.create(item_name)
-        result = Text._render_data_highlight(item2)
-        assert u'<pre class="highlight">test_data\n' in result
-        assert item2.data == ''
+        assert item.content.data == another_data
 
 coverage_modules = ['MoinMoin.items']
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/items/content.py	Sun Jul 29 02:34:27 2012 +0530
@@ -0,0 +1,1036 @@
+# Copyright: 2012 MoinMoin:CheerXiao
+# Copyright: 2009 MoinMoin:ThomasWaldmann
+# Copyright: 2009-2011 MoinMoin:ReimarBauer
+# Copyright: 2009 MoinMoin:ChristopherDenter
+# Copyright: 2008,2009 MoinMoin:BastianBlank
+# Copyright: 2010 MoinMoin:ValentinJaniaut
+# Copyright: 2010 MoinMoin:DiogenesAugusto
+# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
+
+"""
+    MoinMoin - item contents
+
+    Classes handling the content part of items (ie. minus metadata). The
+    content part is sometimes called the "data" part in other places, but is
+    always called content in this module to avoid confusion.
+
+    Each class in this module corresponds to a contenttype value.
+"""
+
+import os, re, base64
+import tarfile
+import zipfile
+import tempfile
+from StringIO import StringIO
+from array import array
+
+from werkzeug import is_resource_modified
+
+from flatland import Form, String
+
+from whoosh.query import Term, And
+
+from MoinMoin.forms import File
+
+from MoinMoin.util.mimetype import MimeType
+from MoinMoin.util.mime import Type, type_moin_document
+from MoinMoin.util.tree import moin_page, html, xlink, docbook
+from MoinMoin.util.iri import Iri
+from MoinMoin.util.crypto import cache_key
+from MoinMoin.storage.middleware.protecting import AccessDenied
+
+try:
+    import PIL
+    from PIL import Image as PILImage
+    from PIL.ImageChops import difference as PILdiff
+except ImportError:
+    PIL = None
+
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
+from flask import current_app as app
+from flask import g as flaskg
+
+from flask import request, url_for, Response, abort, escape
+
+from jinja2 import Markup
+
+from MoinMoin.i18n import _, L_
+from MoinMoin.themes import render_template
+from MoinMoin import wikiutil, config
+from MoinMoin.util.send_file import send_file
+from MoinMoin.util.interwiki import url_for_item
+from MoinMoin.storage.error import StorageError
+from MoinMoin.util.registry import RegistryBase
+from MoinMoin.constants.keys import (
+    NAME, NAME_EXACT, WIKINAME, CONTENTTYPE, SIZE, TAGS, HASH_ALGORITHM
+    )
+
+
+COLS = 80
+ROWS_DATA = 20
+
+
+# XXX Too much boilerplate in Entry implementation. Maybe use namedtuple
+# as a starting point?
+class RegistryContent(RegistryBase):
+    class Entry(object):
+        def __init__(self, factory, content_type, priority):
+            self.factory = factory
+            self.content_type = content_type
+            self.priority = priority
+
+        def __call__(self, name, content_type, kw):
+            if self.content_type.issupertype(content_type):
+                return self.factory(name, content_type, **kw)
+
+        def __eq__(self, other):
+            if isinstance(other, self.__class__):
+                return (self.factory == other.factory and
+                        self.content_type == other.content_type and
+                        self.priority == other.priority)
+            return NotImplemented
+
+        def __lt__(self, other):
+            if isinstance(other, self.__class__):
+                if self.priority < other.priority:
+                    return True
+                if self.content_type != other.content_type:
+                    return other.content_type.issupertype(self.content_type)
+                return False
+            return NotImplemented
+
+        def __repr__(self):
+            return '<{0}: {1}, prio {2} [{3!r}]>'.format(self.__class__.__name__,
+                    self.content_type,
+                    self.priority,
+                    self.factory)
+
+    def get(self, name, content_type, **kw):
+        for entry in self._entries:
+            item = entry(name, content_type, kw)
+            if item is not None:
+                return item
+
+    def register(self, factory, content_type, priority=RegistryBase.PRIORITY_MIDDLE):
+        """
+        Register a factory
+
+        :param factory: Factory to register. Callable, must return an object.
+        """
+        return self._register(self.Entry(factory, content_type, priority))
+
+
+content_registry = RegistryContent()
+
+
+def conv_serialize(doc, namespaces, method='polyglot'):
+    out = array('u')
+    flaskg.clock.start('conv_serialize')
+    doc.write(out.fromunicode, namespaces=namespaces, method=method)
+    out = out.tounicode()
+    flaskg.clock.stop('conv_serialize')
+    return out
+
+
+class Content(object):
+    """
+    Base for content classes defining some helpers, agnostic about content
+    data.
+    """
+    @classmethod
+    def _factory(cls, name=u'', contenttype=None, **kw):
+        return cls(name, contenttype=unicode(contenttype), **kw)
+
+    def __init__(self, item, contenttype=None):
+        self.item = item
+        # TODO gradually remove self.contenttype as theoretically there is
+        # one-to-one correspondance of contenttype and Content type
+        # (pun intended :)
+        self.contenttype = contenttype
+
+    # XXX For backward-compatibility (so code can be moved from Item
+    # untouched), remove soon
+    @property
+    def rev(self):
+        return self.item.rev
+
+    @property
+    def name(self):
+        return self.item.name
+
+    def get_data(self):
+        return '' # TODO create a better method for binary stuff
+    data = property(fget=get_data)
+
+    def internal_representation(self, converters=['smiley']):
+        """
+        Return the internal representation of a document using a DOM Tree
+        """
+        flaskg.clock.start('conv_in_dom')
+        hash_name = HASH_ALGORITHM
+        hash_hexdigest = self.rev.meta.get(hash_name)
+        if hash_hexdigest:
+            cid = cache_key(usage="internal_representation",
+                            hash_name=hash_name,
+                            hash_hexdigest=hash_hexdigest)
+            doc = app.cache.get(cid)
+        else:
+            # likely a non-existing item
+            doc = cid = None
+        if doc is None:
+            # We will see if we can perform the conversion:
+            # FROM_mimetype --> DOM
+            # if so we perform the transformation, otherwise we don't
+            from MoinMoin.converter import default_registry as reg
+            input_conv = reg.get(Type(self.contenttype), type_moin_document)
+            if not input_conv:
+                raise TypeError("We cannot handle the conversion from {0} to the DOM tree".format(self.contenttype))
+            smiley_conv = reg.get(type_moin_document, type_moin_document,
+                    icon='smiley')
+
+            # We can process the conversion
+            links = Iri(scheme='wiki', authority='', path='/' + self.name)
+            doc = input_conv(self.rev, self.contenttype)
+            # XXX is the following assuming that the top element of the doc tree
+            # is a moin_page.page element? if yes, this is the wrong place to do that
+            # as not every doc will have that element (e.g. for images, we just get
+            # moin_page.object, for a tar item, we get a moin_page.table):
+            doc.set(moin_page.page_href, unicode(links))
+            for conv in converters:
+                if conv == 'smiley':
+                    doc = smiley_conv(doc)
+            if cid:
+                app.cache.set(cid, doc)
+        flaskg.clock.stop('conv_in_dom')
+        return doc
+
+    def _expand_document(self, doc):
+        from MoinMoin.converter import default_registry as reg
+        include_conv = reg.get(type_moin_document, type_moin_document, includes='expandall')
+        macro_conv = reg.get(type_moin_document, type_moin_document, macros='expandall')
+        link_conv = reg.get(type_moin_document, type_moin_document, links='extern')
+        flaskg.clock.start('conv_include')
+        doc = include_conv(doc)
+        flaskg.clock.stop('conv_include')
+        flaskg.clock.start('conv_macro')
+        doc = macro_conv(doc)
+        flaskg.clock.stop('conv_macro')
+        flaskg.clock.start('conv_link')
+        doc = link_conv(doc)
+        flaskg.clock.stop('conv_link')
+        return doc
+
+    def _render_data(self):
+        from MoinMoin.converter import default_registry as reg
+        # TODO: Real output format
+        doc = self.internal_representation()
+        doc = self._expand_document(doc)
+        flaskg.clock.start('conv_dom_html')
+        html_conv = reg.get(type_moin_document, Type('application/x-xhtml-moin-page'))
+        doc = html_conv(doc)
+        flaskg.clock.stop('conv_dom_html')
+        rendered_data = conv_serialize(doc, {html.namespace: ''})
+        return rendered_data
+
+    def _render_data_xml(self):
+        doc = self.internal_representation()
+        return conv_serialize(doc,
+                              {moin_page.namespace: '',
+                               xlink.namespace: 'xlink',
+                               html.namespace: 'html',
+                              },
+                              'xml')
+
+    def _render_data_highlight(self):
+        # override this in child classes
+        return ''
+
+    def get_templates(self, contenttype=None):
+        """ create a list of templates (for some specific contenttype) """
+        terms = [Term(WIKINAME, app.cfg.interwikiname), Term(TAGS, u'template')]
+        if contenttype is not None:
+            terms.append(Term(CONTENTTYPE, contenttype))
+        query = And(terms)
+        revs = flaskg.storage.search(query, sortedby=NAME_EXACT, limit=None)
+        return [rev.meta[NAME] for rev in revs]
+
+
+class NonExistentContent(Content):
+    """Dummy Content to use with NonExistent."""
+    def do_get(self, force_attachment=False, mimetype=None):
+        abort(404)
+
+    def _convert(self, doc):
+        abort(404)
+
+
+content_registry.register(NonExistentContent._factory, Type('application/x-nonexistent'))
+
+
+class Binary(Content):
+    """ An arbitrary binary item, fallback class for every item mimetype. """
+
+    # XXX reads item rev data into memory!
+    def get_data(self):
+        if self.rev is not None:
+            return self.rev.data.read()
+        else:
+            return ''
+    data = property(fget=get_data)
+
+    class ModifyForm(Form):
+        template = 'modify_binary.html'
+        help = """\
+There is no help, you're doomed!
+"""
+        data_file = File.using(optional=True, label=L_('Upload file:'))
+
+        def _load(self, item):
+            pass
+
+        def _dump(self, item):
+            data_file = self['data_file'].value
+            if data_file:
+                data = data_file.stream
+                # this is likely a guess by the browser, based on the filename
+                contenttype_guessed = data_file.content_type # comes from form multipart data
+                return data, contenttype_guessed
+            else:
+                return None, None
+
+    def _render_data_diff(self, oldrev, newrev):
+        hash_name = HASH_ALGORITHM
+        if oldrev.meta[hash_name] == newrev.meta[hash_name]:
+            return _("The items have the same data hash code (that means they very likely have the same data).")
+        else:
+            return _("The items have different data.")
+
+    _render_data_diff_text = _render_data_diff
+    _render_data_diff_raw = _render_data_diff
+
+    def _render_data_diff_atom(self, oldrev, newrev):
+        return render_template('atom.html',
+                               oldrev=oldrev, newrev=newrev, get='binary',
+                               content=Markup(self._render_data()))
+
+    def _convert(self, doc):
+        return _("Impossible to convert the data to the contenttype: %(contenttype)s",
+                 contenttype=request.values.get('contenttype'))
+
+    def do_get(self, force_attachment=False, mimetype=None):
+        hash = self.rev.meta.get(HASH_ALGORITHM)
+        if is_resource_modified(request.environ, hash): # use hash as etag
+            return self._do_get_modified(hash, force_attachment=force_attachment, mimetype=mimetype)
+        else:
+            return Response(status=304)
+
+    def _do_get_modified(self, hash, force_attachment=False, mimetype=None):
+        member = request.values.get('member')
+        return self._do_get(hash, member, force_attachment=force_attachment, mimetype=mimetype)
+
+    def _do_get(self, hash, member=None, force_attachment=False, mimetype=None):
+        if member: # content = file contained within a archive item revision
+            path, filename = os.path.split(member)
+            mt = MimeType(filename=filename)
+            content_length = None
+            file_to_send = self.get_member(member)
+        else: # content = item revision
+            rev = self.rev
+            filename = rev.item.name
+            try:
+                mimestr = rev.meta[CONTENTTYPE]
+            except KeyError:
+                mt = MimeType(filename=filename)
+            else:
+                mt = MimeType(mimestr=mimestr)
+            content_length = rev.meta[SIZE]
+            file_to_send = rev.data
+        if mimetype:
+            content_type = mimetype
+        else:
+            content_type = mt.content_type()
+        as_attachment = force_attachment or mt.as_attachment(app.cfg)
+        return send_file(file=file_to_send,
+                         mimetype=content_type,
+                         as_attachment=as_attachment, attachment_filename=filename,
+                         cache_timeout=10, # wiki data can change rapidly
+                         add_etags=True, etag=hash, conditional=True)
+
+content_registry.register(Binary._factory, Type('*/*'))
+
+
+class RenderableBinary(Binary):
+    """ Base class for some binary stuff that renders with a object tag. """
+
+
+class Application(Binary):
+    """ Base class for application/* """
+
+
+class TarMixin(object):
+    """
+    TarMixin offers additional functionality for tar-like items to list and
+    access member files and to create new revisions by multiple posts.
+    """
+    def list_members(self):
+        """
+        list tar file contents (member file names)
+        """
+        self.rev.data.seek(0)
+        tf = tarfile.open(fileobj=self.rev.data, mode='r')
+        return tf.getnames()
+
+    def get_member(self, name):
+        """
+        return a file-like object with the member file data
+
+        :param name: name of the data in the container file
+        """
+        self.rev.data.seek(0)
+        tf = tarfile.open(fileobj=self.rev.data, mode='r')
+        return tf.extractfile(name)
+
+    def put_member(self, name, content, content_length, expected_members):
+        """
+        puts a new member file into a temporary tar container.
+        If all expected members have been put, it saves the tar container
+        to a new item revision.
+
+        :param name: name of the data in the container file
+        :param content: the data to store into the tar file (str or file-like)
+        :param content_length: byte-length of content (for str, None can be given)
+        :param expected_members: set of expected member file names
+        """
+        if not name in expected_members:
+            raise StorageError("tried to add unexpected member {0!r} to container item {1!r}".format(name, self.name))
+        if isinstance(name, unicode):
+            name = name.encode('utf-8')
+        temp_fname = os.path.join(tempfile.gettempdir(), 'TarContainer_' +
+                                  cache_key(usage='TarContainer', name=self.name))
+        tf = tarfile.TarFile(temp_fname, mode='a')
+        ti = tarfile.TarInfo(name)
+        if isinstance(content, str):
+            if content_length is None:
+                content_length = len(content)
+            content = StringIO(content) # we need a file obj
+        elif not hasattr(content, 'read'):
+            logging.error("unsupported content object: {0!r}".format(content))
+            raise StorageError("unsupported content object: {0!r}".format(content))
+        assert content_length >= 0  # we don't want -1 interpreted as 4G-1
+        ti.size = content_length
+        tf.addfile(ti, content)
+        tf_members = set(tf.getnames())
+        tf.close()
+        if tf_members - expected_members:
+            msg = "found unexpected members in container item {0!r}".format(self.name)
+            logging.error(msg)
+            os.remove(temp_fname)
+            raise StorageError(msg)
+        if tf_members == expected_members:
+            # everything we expected has been added to the tar file, save the container as revision
+            meta = {CONTENTTYPE: self.contenttype}
+            data = open(temp_fname, 'rb')
+            self.item._save(meta, data, name=self.name, action=u'SAVE', comment='')
+            data.close()
+            os.remove(temp_fname)
+
+
+class ApplicationXTar(TarMixin, Application):
+    """
+    Tar items
+    """
+
+content_registry.register(ApplicationXTar._factory, Type('application/x-tar'))
+content_registry.register(ApplicationXTar._factory, Type('application/x-gtar'))
+
+
+class ZipMixin(object):
+    """
+    ZipMixin offers additional functionality for zip-like items to list and
+    access member files.
+    """
+    def list_members(self):
+        """
+        list zip file contents (member file names)
+        """
+        self.rev.data.seek(0)
+        zf = zipfile.ZipFile(self.rev.data, mode='r')
+        return zf.namelist()
+
+    def get_member(self, name):
+        """
+        return a file-like object with the member file data
+
+        :param name: name of the data in the zip file
+        """
+        self.rev.data.seek(0)
+        zf = zipfile.ZipFile(self.rev.data, mode='r')
+        return zf.open(name, mode='r')
+
+    def put_member(self, name, content, content_length, expected_members):
+        raise NotImplementedError
+
+
+class ApplicationZip(ZipMixin, Application):
+    """
+    Zip items
+    """
+
+content_registry.register(ApplicationZip._factory, Type('application/zip'))
+
+
+class PDF(Application):
+    """ PDF """
+
+content_registry.register(PDF._factory, Type('application/pdf'))
+
+
+class Video(Binary):
+    """ Base class for video/* """
+
+content_registry.register(Video._factory, Type('video/*'))
+
+
+class Audio(Binary):
+    """ Base class for audio/* """
+
+content_registry.register(Audio._factory, Type('audio/*'))
+
+
+class Image(Binary):
+    """ Base class for image/* """
+
+content_registry.register(Image._factory, Type('image/*'))
+
+
+class RenderableImage(RenderableBinary):
+    """ Base class for renderable Image mimetypes """
+
+
+class SvgImage(RenderableImage):
+    """ SVG images use <object> tag mechanism from RenderableBinary base class """
+
+content_registry.register(SvgImage._factory, Type('image/svg+xml'))
+
+
+class RenderableBitmapImage(RenderableImage):
+    """ PNG/JPEG/GIF images use <img> tag (better browser support than <object>) """
+    # if mimetype is also transformable, please register in TransformableImage ONLY!
+
+
+class TransformableBitmapImage(RenderableBitmapImage):
+    """ We can transform (resize, rotate, mirror) some image types """
+    def _transform(self, content_type, size=None, transpose_op=None):
+        """ resize to new size (optional), transpose according to exif infos,
+            result data should be content_type.
+        """
+        try:
+            from PIL import Image as PILImage
+        except ImportError:
+            # no PIL, we can't do anything, we just output the revision data as is
+            return content_type, self.rev.data.read()
+
+        if content_type == 'image/jpeg':
+            output_type = 'JPEG'
+        elif content_type == 'image/png':
+            output_type = 'PNG'
+        elif content_type == 'image/gif':
+            output_type = 'GIF'
+        else:
+            raise ValueError("content_type {0!r} not supported".format(content_type))
+
+        # revision obj has read() seek() tell(), thus this works:
+        image = PILImage.open(self.rev.data)
+        image.load()
+
+        try:
+            # if we have EXIF data, we can transpose (e.g. rotate left),
+            # so the rendered image is correctly oriented:
+            transpose_op = transpose_op or 1 # or self.exif['Orientation']
+        except KeyError:
+            transpose_op = 1 # no change
+
+        if size is not None:
+            image = image.copy() # create copy first as thumbnail works in-place
+            image.thumbnail(size, PILImage.ANTIALIAS)
+
+        transpose_func = {
+            1: lambda image: image,
+            2: lambda image: image.transpose(PILImage.FLIP_LEFT_RIGHT),
+            3: lambda image: image.transpose(PILImage.ROTATE_180),
+            4: lambda image: image.transpose(PILImage.FLIP_TOP_BOTTOM),
+            5: lambda image: image.transpose(PILImage.ROTATE_90).transpose(PILImage.FLIP_TOP_BOTTOM),
+            6: lambda image: image.transpose(PILImage.ROTATE_270),
+            7: lambda image: image.transpose(PILImage.ROTATE_90).transpose(PILImage.FLIP_LEFT_RIGHT),
+            8: lambda image: image.transpose(PILImage.ROTATE_90),
+        }
+        image = transpose_func[transpose_op](image)
+
+        outfile = StringIO()
+        image.save(outfile, output_type)
+        data = outfile.getvalue()
+        outfile.close()
+        return content_type, data
+
+    def _do_get_modified(self, hash, force_attachment=False, mimetype=None):
+        try:
+            width = int(request.values.get('w'))
+        except (TypeError, ValueError):
+            width = None
+        try:
+            height = int(request.values.get('h'))
+        except (TypeError, ValueError):
+            height = None
+        try:
+            transpose = int(request.values.get('t'))
+            assert 1 <= transpose <= 8
+        except (TypeError, ValueError, AssertionError):
+            transpose = 1
+        if width or height or transpose != 1:
+            # resize requested, XXX check ACL behaviour! XXX
+            hash_name = HASH_ALGORITHM
+            hash_hexdigest = self.rev.meta[hash_name]
+            cid = cache_key(usage="ImageTransform",
+                            hash_name=hash_name,
+                            hash_hexdigest=hash_hexdigest,
+                            width=width, height=height, transpose=transpose)
+            c = app.cache.get(cid)
+            if c is None:
+                if mimetype:
+                    content_type = mimetype
+                else:
+                    content_type = self.rev.meta[CONTENTTYPE]
+                size = (width or 99999, height or 99999)
+                content_type, data = self._transform(content_type, size=size, transpose_op=transpose)
+                headers = wikiutil.file_headers(content_type=content_type, content_length=len(data))
+                app.cache.set(cid, (headers, data))
+            else:
+                # XXX TODO check ACL behaviour
+                headers, data = c
+            return Response(data, headers=headers)
+        else:
+            return self._do_get(hash, force_attachment=force_attachment, mimetype=mimetype)
+
+    def _render_data_diff_atom(self, oldrev, newrev):
+        if PIL is None:
+            # no PIL, we can't do anything, we just call the base class method
+            return super(TransformableBitmapImage, self)._render_data_diff_atom(oldrev, newrev)
+        url = url_for('frontend.diffraw', _external=True, item_name=self.name, rev1=oldrev.revid, rev2=newrev.revid)
+        return render_template('atom.html',
+                               oldrev=oldrev, newrev=newrev, get='binary',
+                               content=Markup(u'<img src="{0}" />'.format(escape(url))))
+
+    def _render_data_diff(self, oldrev, newrev):
+        if PIL is None:
+            # no PIL, we can't do anything, we just call the base class method
+            return super(TransformableBitmapImage, self)._render_data_diff(oldrev, newrev)
+        url = url_for('frontend.diffraw', item_name=self.name, rev1=oldrev.revid, rev2=newrev.revid)
+        return Markup(u'<img src="{0}" />'.format(escape(url)))
+
+    def _render_data_diff_raw(self, oldrev, newrev):
+        hash_name = HASH_ALGORITHM
+        cid = cache_key(usage="ImageDiff",
+                        hash_name=hash_name,
+                        hash_old=oldrev.meta[hash_name],
+                        hash_new=newrev.meta[hash_name])
+        c = app.cache.get(cid)
+        if c is None:
+            if PIL is None:
+                abort(404) # TODO render user friendly error image
+
+            content_type = newrev.meta[CONTENTTYPE]
+            if content_type == 'image/jpeg':
+                output_type = 'JPEG'
+            elif content_type == 'image/png':
+                output_type = 'PNG'
+            elif content_type == 'image/gif':
+                output_type = 'GIF'
+            else:
+                raise ValueError("content_type {0!r} not supported".format(content_type))
+
+            try:
+                oldimage = PILImage.open(oldrev.data)
+                newimage = PILImage.open(newrev.data)
+                oldimage.load()
+                newimage.load()
+                diffimage = PILdiff(newimage, oldimage)
+                outfile = StringIO()
+                diffimage.save(outfile, output_type)
+                data = outfile.getvalue()
+                outfile.close()
+                headers = wikiutil.file_headers(content_type=content_type, content_length=len(data))
+                app.cache.set(cid, (headers, data))
+            except (IOError, ValueError) as err:
+                logging.exception("error during PILdiff: {0}".format(err.message))
+                abort(404) # TODO render user friendly error image
+        else:
+            # XXX TODO check ACL behaviour
+            headers, data = c
+        return Response(data, headers=headers)
+
+    def _render_data_diff_text(self, oldrev, newrev):
+        return super(TransformableBitmapImage, self)._render_data_diff_text(oldrev, newrev)
+
+content_registry.register(TransformableBitmapImage._factory, Type('image/png'))
+content_registry.register(TransformableBitmapImage._factory, Type('image/jpeg'))
+content_registry.register(TransformableBitmapImage._factory, Type('image/gif'))
+
+
+class Text(Binary):
+    """ Base class for text/* """
+
+    class ModifyForm(Binary.ModifyForm):
+        template = 'modify_text.html'
+        data_text = String.using(strip=False, optional=True).with_properties(placeholder=L_("Type your text here"))
+        rows = ROWS_DATA
+        cols = COLS
+
+        def _load(self, item):
+            super(Text.ModifyForm, self)._load(item)
+            data = item.data
+            data = item.data_storage_to_internal(data)
+            data = item.data_internal_to_form(data)
+            self['data_text'] = data
+
+        def _dump(self, item):
+            data, contenttype_guessed = super(Text.ModifyForm, self)._dump(item)
+            if data is None:
+                data = self['data_text'].value
+                data = item.data_form_to_internal(data)
+                data = item.data_internal_to_storage(data)
+                # we know it is text and utf-8 - XXX is there a way to get the charset of the form?
+                contenttype_guessed = u'text/plain;charset=utf-8'
+            return data, contenttype_guessed
+
+    # text/plain mandates crlf - but in memory, we want lf only
+    def data_internal_to_form(self, text):
+        """ convert data from memory format to form format """
+        return text.replace(u'\n', u'\r\n')
+
+    def data_form_to_internal(self, data):
+        """ convert data from form format to memory format """
+        return data.replace(u'\r\n', u'\n')
+
+    def data_internal_to_storage(self, text):
+        """ convert data from memory format to storage format """
+        return text.replace(u'\n', u'\r\n').encode(config.charset)
+
+    def data_storage_to_internal(self, data):
+        """ convert data from storage format to memory format """
+        return data.decode(config.charset).replace(u'\r\n', u'\n')
+
+    def _get_data_diff_html(self, oldrev, newrev, template):
+        from MoinMoin.util.diff_html import diff
+        old_text = self.data_storage_to_internal(oldrev.data.read())
+        new_text = self.data_storage_to_internal(newrev.data.read())
+        storage_item = flaskg.storage[self.name]
+        diffs = [(d[0], Markup(d[1]), d[2], Markup(d[3])) for d in diff(old_text, new_text)]
+        return render_template(template,
+                               item_name=self.name,
+                               oldrev=oldrev,
+                               newrev=newrev,
+                               diffs=diffs,
+                               )
+
+    def _render_data_diff_atom(self, oldrev, newrev):
+        """ renders diff in HTML for atom feed """
+        return self._get_data_diff_html(oldrev, newrev, 'diff_text_atom.html')
+
+    def _render_data_diff(self, oldrev, newrev):
+        return self._get_data_diff_html(oldrev, newrev, 'diff_text.html')
+
+    def _render_data_diff_text(self, oldrev, newrev):
+        from MoinMoin.util import diff_text
+        oldlines = self.data_storage_to_internal(oldrev.data.read()).split('\n')
+        newlines = self.data_storage_to_internal(newrev.data.read()).split('\n')
+        difflines = diff_text.diff(oldlines, newlines)
+        return '\n'.join(difflines)
+
+    _render_data_diff_raw = _render_data_diff
+
+    def _render_data_highlight(self):
+        from MoinMoin.converter import default_registry as reg
+        data_text = self.data_storage_to_internal(self.data)
+        # TODO: use registry as soon as it is in there
+        from MoinMoin.converter.pygments_in import Converter as PygmentsConverter
+        pygments_conv = PygmentsConverter(contenttype=self.contenttype)
+        doc = pygments_conv(data_text)
+        # TODO: Real output format
+        html_conv = reg.get(type_moin_document, Type('application/x-xhtml-moin-page'))
+        doc = html_conv(doc)
+        return conv_serialize(doc, {html.namespace: ''})
+
+content_registry.register(Text._factory, Type('text/*'))
+
+
+class MarkupItem(Text):
+    """
+    some kind of item with markup
+    (internal links and transcluded items)
+    """
+
+
+class MoinWiki(MarkupItem):
+    """ MoinMoin wiki markup """
+
+content_registry.register(MoinWiki._factory, Type('text/x.moin.wiki'))
+
+
+class CreoleWiki(MarkupItem):
+    """ Creole wiki markup """
+
+content_registry.register(CreoleWiki._factory, Type('text/x.moin.creole'))
+
+
+class MediaWiki(MarkupItem):
+    """ MediaWiki markup """
+
+content_registry.register(MediaWiki._factory, Type('text/x-mediawiki'))
+
+
+class ReST(MarkupItem):
+    """ ReStructured Text markup """
+
+content_registry.register(ReST._factory, Type('text/x-rst'))
+
+
+class HTML(Text):
+    """
+    HTML markup
+
+    Note: As we use html_in converter to convert this to DOM and later some
+          output converterter to produce output format (e.g. html_out for html
+          output), all(?) unsafe stuff will get lost.
+
+    Note: If raw revision data is accessed, unsafe stuff might be present!
+    """
+    class ModifyForm(Text.ModifyForm):
+        template = "modify_text_html.html"
+
+content_registry.register(HTML._factory, Type('text/html'))
+
+
+class DocBook(MarkupItem):
+    """ DocBook Document """
+    def _convert(self, doc):
+        from emeraldtree import ElementTree as ET
+        from MoinMoin.converter import default_registry as reg
+
+        doc = self._expand_document(doc)
+
+        # We convert the internal representation of the document
+        # into a DocBook document
+        conv = reg.get(type_moin_document, Type('application/docbook+xml'))
+
+        doc = conv(doc)
+
+        # We determine the different namespaces of the output form
+        output_namespaces = {
+             docbook.namespace: '',
+             xlink.namespace: 'xlink',
+         }
+
+        # We convert the result into a StringIO object
+        # With the appropriate namespace
+        # TODO: Some other operation should probably be done here too
+        # like adding a doctype
+        file_to_send = StringIO()
+        tree = ET.ElementTree(doc)
+        tree.write(file_to_send, namespaces=output_namespaces)
+
+        # We determine the different parameters for the reply
+        mt = MimeType(mimestr='application/docbook+xml;charset=utf-8')
+        content_type = mt.content_type()
+        as_attachment = mt.as_attachment(app.cfg)
+        # After creation of the StringIO, we are at the end of the file
+        # so position is the size the file.
+        # and then we should move it back at the beginning of the file
+        content_length = file_to_send.tell()
+        file_to_send.seek(0)
+        # Important: empty filename keeps flask from trying to autodetect filename,
+        # as this would not work for us, because our file's are not necessarily fs files.
+        return send_file(file=file_to_send,
+                         mimetype=content_type,
+                         as_attachment=as_attachment, attachment_filename=None,
+                         cache_timeout=10, # wiki data can change rapidly
+                         add_etags=False, etag=None, conditional=True)
+
+content_registry.register(DocBook._factory, Type('application/docbook+xml'))
+
+
+class Draw(TarMixin, Image):
+    """
+    Base class for *Draw that use special Java/Javascript applets to modify and store data in a tar file.
+    """
+    class ModifyForm(Binary.ModifyForm):
+        # Set the workaround flag respected in modify.html
+        is_draw = True
+
+    def handle_post():
+        raise NotImplementedError
+
+
+class TWikiDraw(Draw):
+    """
+    drawings by TWikiDraw applet. It creates three files which are stored as tar file.
+    """
+
+    class ModifyForm(Draw.ModifyForm):
+        template = "modify_twikidraw.html"
+        help = ""
+
+    def handle_post(self):
+        # called from modify UI/POST
+        file_upload = request.files.get('filepath')
+        filename = request.form['filename']
+        basepath, basename = os.path.split(filename)
+        basename, ext = os.path.splitext(basename)
+
+        filecontent = file_upload.stream
+        content_length = None
+        if ext == '.draw': # TWikiDraw POSTs this first
+            filecontent = filecontent.read() # read file completely into memory
+            filecontent = filecontent.replace("\r", "")
+        elif ext == '.map':
+            filecontent = filecontent.read() # read file completely into memory
+            filecontent = filecontent.strip()
+        elif ext == '.png':
+            #content_length = file_upload.content_length
+            # XXX gives -1 for wsgiref, gives 0 for werkzeug :(
+            # If this is fixed, we could use the file obj, without reading it into memory completely:
+            filecontent = filecontent.read()
+
+        self.put_member('drawing' + ext, filecontent, content_length,
+                        expected_members=set(['drawing.draw', 'drawing.map', 'drawing.png']))
+
+    def _render_data(self):
+        # TODO: this could be a converter -> dom, then transcluding this kind
+        # of items and also rendering them with the code in base class could work
+        item_name = self.name
+        drawing_url = url_for('frontend.get_item', item_name=item_name, member='drawing.draw', rev=self.rev.revid)
+        png_url = url_for('frontend.get_item', item_name=item_name, member='drawing.png', rev=self.rev.revid)
+        title = _('Edit drawing %(filename)s (opens in new window)', filename=item_name)
+
+        mapfile = self.get_member('drawing.map')
+        try:
+            image_map = mapfile.read()
+            mapfile.close()
+        except (IOError, OSError):
+            image_map = ''
+        if image_map:
+            # we have a image map. inline it and add a map ref to the img tag
+            mapid = 'ImageMapOf' + item_name
+            image_map = image_map.replace('%MAPNAME%', mapid)
+            # add alt and title tags to areas
+            image_map = re.sub(r'href\s*=\s*"((?!%TWIKIDRAW%).+?)"', r'href="\1" alt="\1" title="\1"', image_map)
+            image_map = image_map.replace('%TWIKIDRAW%"', '{0}" alt="{1}" title="{2}"'.format((drawing_url, title, title)))
+            title = _('Clickable drawing: %(filename)s', filename=item_name)
+
+            return Markup(image_map + u'<img src="{0}" alt="{1}" usemap="#{2}" />'.format(png_url, title, mapid))
+        else:
+            return Markup(u'<img src="{0}" alt="{1}" />'.format(png_url, title))
+
+content_registry.register(TWikiDraw._factory, Type('application/x-twikidraw'))
+
+
+class AnyWikiDraw(Draw):
+    """
+    drawings by AnyWikiDraw applet. It creates three files which are stored as tar file.
+    """
+
+    class ModifyForm(Draw.ModifyForm):
+        template = "modify_anywikidraw.html"
+        help = ""
+        def _load(self, item):
+            super(AnyWikiDraw.ModifyForm, self)._load(item)
+            try:
+                drawing_exists = 'drawing.svg' in item.list_members()
+            except tarfile.TarError: # item doesn't exist yet
+                drawing_exists = False
+            self.drawing_exists = drawing_exists
+
+    def handle_post(self):
+        # called from modify UI/POST
+        file_upload = request.files.get('filepath')
+        filename = request.form['filename']
+        basepath, basename = os.path.split(filename)
+        basename, ext = os.path.splitext(basename)
+        filecontent = file_upload.stream
+        content_length = None
+        if ext == '.svg':
+            filecontent = filecontent.read() # read file completely into memory
+            filecontent = filecontent.replace("\r", "")
+        elif ext == '.map':
+            filecontent = filecontent.read() # read file completely into memory
+            filecontent = filecontent.strip()
+        elif ext == '.png':
+            #content_length = file_upload.content_length
+            # XXX gives -1 for wsgiref, gives 0 for werkzeug :(
+            # If this is fixed, we could use the file obj, without reading it into memory completely:
+            filecontent = filecontent.read()
+        self.put_member('drawing' + ext, filecontent, content_length,
+                        expected_members=set(['drawing.svg', 'drawing.map', 'drawing.png']))
+
+    def _render_data(self):
+        # TODO: this could be a converter -> dom, then transcluding this kind
+        # of items and also rendering them with the code in base class could work
+        item_name = self.name
+        drawing_url = url_for('frontend.get_item', item_name=item_name, member='drawing.svg', rev=self.rev.revid)
+        png_url = url_for('frontend.get_item', item_name=item_name, member='drawing.png', rev=self.rev.revid)
+        title = _('Edit drawing %(filename)s (opens in new window)', filename=self.name)
+
+        mapfile = self.get_member('drawing.map')
+        try:
+            image_map = mapfile.read()
+            mapfile.close()
+        except (IOError, OSError):
+            image_map = ''
+        if image_map:
+            # ToDo mapid must become uniq
+            # we have a image map. inline it and add a map ref to the img tag
+            # we have also to set a unique ID
+            mapid = 'ImageMapOf' + self.name
+            image_map = image_map.replace(u'id="drawing.svg"', '')
+            image_map = image_map.replace(u'name="drawing.svg"', u'name="{0}"'.format(mapid))
+            # unxml, because 4.01 concrete will not validate />
+            image_map = image_map.replace(u'/>', u'>')
+            title = _('Clickable drawing: %(filename)s', filename=self.name)
+            return Markup(image_map + u'<img src="{0}" alt="{1}" usemap="#{2}" />'.format(png_url, title, mapid))
+        else:
+            return Markup(u'<img src="{0}" alt="{1}" />'.format(png_url, title))
+
+content_registry.register(AnyWikiDraw._factory, Type('application/x-anywikidraw'))
+
+
+class SvgDraw(Draw):
+    """ drawings by svg-edit. It creates two files (svg, png) which are stored as tar file. """
+
+    class ModifyForm(Draw.ModifyForm):
+        template = "modify_svg-edit.html"
+        help = ""
+
+    def handle_post(self):
+        # called from modify UI/POST
+        png_upload = request.values.get('png_data')
+        svg_upload = request.values.get('filepath')
+        filename = request.form['filename']
+        png_content = png_upload.decode('base_64')
+        png_content = base64.urlsafe_b64decode(png_content.split(',')[1])
+        svg_content = svg_upload.decode('base_64')
+        content_length = None
+        self.put_member("drawing.svg", svg_content, content_length,
+                        expected_members=set(['drawing.svg', 'drawing.png']))
+        self.put_member("drawing.png", png_content, content_length,
+                        expected_members=set(['drawing.svg', 'drawing.png']))
+
+    def _render_data(self):
+        # TODO: this could be a converter -> dom, then transcluding this kind
+        # of items and also rendering them with the code in base class could work
+        item_name = self.name
+        drawing_url = url_for('frontend.get_item', item_name=item_name, member='drawing.svg', rev=self.rev.revid)
+        png_url = url_for('frontend.get_item', item_name=item_name, member='drawing.png', rev=self.rev.revid)
+        return Markup(u'<img src="{0}" alt="{1}" />'.format(png_url, drawing_url))
+
+content_registry.register(SvgDraw._factory, Type('application/x-svgdraw'))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/templates/modify.html	Sun Jul 29 02:34:27 2012 +0530
@@ -0,0 +1,65 @@
+{% import "forms.html" as forms %}
+{% import "utils.html" as utils %}
+{% extends theme("layout.html") %}
+
+{# Import macros data_editor and extra_head from content_form's template.
+   extra_head is optional, so instead of a simple "import from" we need to do
+   this manually #}
+{% import form['content_form'].template as content_template %}
+{% set extra_head = content_template.extra_head %}
+{% set data_editor = content_template.data_editor %}
+
+{% set title = _("Modifying %(item_name)s", item_name=item_name) %}
+
+{% block head %}
+    {{ super() }}
+    {% if extra_head %}
+        {{ extra_head() }}
+    {% endif %}
+{% endblock %}
+
+{% block subitem_navigation %}
+    {% call(fullname, shortname, contenttype, has_children) utils.render_subitem_navigation(item_name, True) %}
+        {% set shortname = shortname|json_dumps %}
+        {% set fullname = fullname|json_dumps %}
+        <button class="link-action" onclick='linkSubitem({{ shortname }}, {{ fullname }})'
+            title="{{ _('Link to Subitem') }}">{{ _('Link') }}</button>
+        <button class="transclude-action"
+            onclick='transcludeSubitem({{ shortname }}, {{ fullname }})'
+            title="{{ _('Transclude Subitem') }}">{{ _('Transclude') }}</button>
+    {% endcall %}
+{% endblock %}
+
+
+{% block content %}
+<h1>{{ title }}</h1>
+<div class="moin-form">
+    {{ gen.form.open(form, method='post', enctype='multipart/form-data') }}
+    {{ forms.render_errors(form) }}
+    {#
+       Workaround:
+       For *Draw content, hide form['submit'] and form['comment'], since *Draw
+       POSTs originate from their respective applets.
+    #}
+    {% if not form['content_form'].is_draw %}
+        {{ forms.render(form['submit']) }}
+        <dl>
+            {{ forms.render_textcha(gen, form) }}
+            {{ forms.render(form['comment']) }}
+        </dl>
+    {% endif %}
+    {{ data_editor(form['content_form']) }}
+    {% if form['content_form'].help %}
+        <pre id="moin-editor-help">{{ form['content_form'].help }}</pre>
+    {% endif %}
+    <dl>
+        {{ forms.render(form['meta_text']) }}
+    </dl>
+    {#
+    {{ gen.textarea(form['meta_text'], lang='en', dir='ltr', rows=rows_meta, cols=cols) }}
+    <br />
+    {{ forms.render_errors(form['meta_text']) }}
+    #}
+    {{ gen.form.close() }}
+</div>
+{% endblock %}
--- a/MoinMoin/templates/modify_anywikidraw.html	Fri Jul 27 19:08:35 2012 +0530
+++ b/MoinMoin/templates/modify_anywikidraw.html	Sun Jul 29 02:34:27 2012 +0530
@@ -1,11 +1,10 @@
-{% extends "modify_applet.html" %}
-{% block data_editor %}
+{% macro data_editor(form) %}
 <p>
 <applet code="org.anywikidraw.moinmoin.MoinMoinDrawingApplet.class" codebase="."
         archive="{{ url_for('serve.files', name='anywikidraw', filename='AnyWikiDrawForMoinMoin.jar') }}"
         width="800" height="620">
 <param name="DrawingName" value="drawing.svg" />
-{% if drawing_exists %}
+{% if form.drawing_exists %}
 <param name="DrawingURL" value="{{ url_for('frontend.get_item', item_name=item_name, member='drawing.svg') }}" />
 {% endif %}
 <param name="PageURL" value="{{ url_for('frontend.show_item', item_name=item_name) }}" />
@@ -19,4 +18,4 @@
 </applet>
 </p>
 <br />
-{% endblock %}
+{% endmacro %}
--- a/MoinMoin/templates/modify_applet.html	Fri Jul 27 19:08:35 2012 +0530
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-{% import "forms.html" as forms %}
-{% extends theme("layout.html") %}
-
-{% set title = _("Modifying %(item_name)s", item_name=item_name) %}
-
-{% block content %}
-<h1>{{ title }}</h1>
-<div class="moin-form">
-    {{ gen.form.open(form, method='post', enctype='multipart/form-data') }}
-    {{ forms.render_errors(form) }}
-    {% block extra_form %}{% endblock %}
-    {% block data_editor %}{% endblock %}
-    <dl>
-        {{ forms.render(form['data_file']) }}
-    </dl>
-    <pre id="moin-editor-help">{{ help }}</pre>
-    {{ gen.textarea(form['meta_text'], lang='en', dir='ltr', rows=rows_meta, cols=cols) }}
-    <br />
-    {{ forms.render_errors(form['meta_text']) }}
-    {{ gen.form.close() }}
-</div>
-{% endblock %}
--- a/MoinMoin/templates/modify_binary.html	Fri Jul 27 19:08:35 2012 +0530
+++ b/MoinMoin/templates/modify_binary.html	Sun Jul 29 02:34:27 2012 +0530
@@ -1,9 +1,7 @@
 {% import "forms.html" as forms %}
-{% extends "modify_applet.html" %}
-{% block extra_form %}
-{{ forms.render(form['submit']) }}
-<dl>
-    {{ forms.render_textcha(gen, form) }}
-    {{ forms.render(form['comment']) }}
-</dl>
-{% endblock %}
+
+{% macro data_editor(form) %}
+    <dl>
+        {{ forms.render(form['data_file']) }}
+    </dl>
+{% endmacro %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/templates/modify_show_contenttype_selection.html	Sun Jul 29 02:34:27 2012 +0530
@@ -0,0 +1,22 @@
+{% extends theme("layout.html") %}
+{% block content %}
+<h1>{{ _("Create new item?") }}</h1>
+<p>
+{# XXX should show itemtype's display name #}
+{{ _("Please select the contenttype of the new %(itemtype)s item.", itemtype=itemtype) }}
+</p>
+<table class="zebra">
+    {% for gname, contenttypes in contenttype_groups %}
+    <tr>
+        <th>{{ gname }}</th>
+    </tr>
+    <tr>
+        <td>
+        {% for ctname, ctlabel in contenttypes %}
+            <a href="{{ url_for('frontend.modify_item', item_name=item_name, itemtype=itemtype, contenttype=ctname) }}">{{ ctlabel }}</a> -
+        {% endfor %}
+        </td>
+    </tr>
+    {% endfor %}
+</table>
+{% endblock %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/templates/modify_show_itemtype_selection.html	Sun Jul 29 02:34:27 2012 +0530
@@ -0,0 +1,16 @@
+{% extends theme("layout.html") %}
+{% block content %}
+<h1>{{ _("Create new item?") }}</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.") }}
+</p>
+<table class="zebra">
+    {% for itname, itlabel, itdesc in itemtypes %}
+    <tr>
+        <td>
+            <a href="{{ url_for('frontend.modify_item', item_name=item_name, itemtype=itname) }}">{{ itlabel }}</a> - {{ itdesc }}
+        </td>
+    </tr>
+    {% endfor %}
+</table>
+{% endblock %}
--- a/MoinMoin/templates/modify_show_template_selection.html	Fri Jul 27 19:08:35 2012 +0530
+++ b/MoinMoin/templates/modify_show_template_selection.html	Sun Jul 29 02:34:27 2012 +0530
@@ -3,13 +3,13 @@
 <h1>{{ _("Create new item?") }}</h1>
 <p>
 {{ _("You can either <a href='%(modifyhref)s'>create the item from scratch</a> or select a template.",
-modifyhref=url_for('frontend.modify_item', item_name=item_name, contenttype=contenttype, template='') ) }}
+modifyhref=url_for('frontend.modify_item', item_name=item_name, itemtype=itemtype, contenttype=contenttype, template='') ) }}
 </p>
 <h2>{{ _("Available template items") }}</h2>
 <ul>
     {% for template in templates %}
     <li>
-    <a href="{{ url_for('frontend.modify_item', item_name=item_name, contenttype=contenttype, template=template) }}">{{ template }}</a>
+    <a href="{{ url_for('frontend.modify_item', item_name=item_name, itemtype=itemtype, contenttype=contenttype, template=template) }}">{{ template }}</a>
     </li>
     {% endfor %}
 </ul>
--- a/MoinMoin/templates/modify_show_type_selection.html	Fri Jul 27 19:08:35 2012 +0530
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,21 +0,0 @@
-{% extends theme("layout.html") %}
-{% block content %}
-<h1>{{ _("Create new item?") }}</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.") }}
-</p>
-<table class="zebra">
-    {% for gname, contenttypes in contenttype_groups %}
-    <tr>
-        <th>{{ gname }}</th>
-    </tr>
-    <tr>
-        <td>
-        {% for ctname, ctlabel in contenttypes %}
-            <a href="{{ url_for('frontend.modify_item', item_name=item_name, contenttype=ctname) }}">{{ ctlabel }}</a> -
-        {% endfor %}
-        </td>
-    </tr>
-    {% endfor %}
-</table>
-{% endblock %}
--- a/MoinMoin/templates/modify_svg-edit.html	Fri Jul 27 19:08:35 2012 +0530
+++ b/MoinMoin/templates/modify_svg-edit.html	Sun Jul 29 02:34:27 2012 +0530
@@ -1,8 +1,7 @@
-{% extends "modify_applet.html" %}
-{% block data_editor %}
+{% macro data_editor(form) %}
 <p>
 <object data="{{ url_for('serve.files', name='svgedit_moin', filename='editor/svg-editor.html') }}?paramurl={{ url_for('frontend.get_item', item_name=item_name, member='drawing.svg') }}" width="100%" height="600">
 </object>
 </p>
 <br />
-{% endblock %}
+{% endmacro %}
--- a/MoinMoin/templates/modify_text.html	Fri Jul 27 19:08:35 2012 +0530
+++ b/MoinMoin/templates/modify_text.html	Sun Jul 29 02:34:27 2012 +0530
@@ -1,23 +1,11 @@
-{% extends "modify_binary.html" %}
-{% import "utils.html" as utils %}
+{% from "modify_binary.html" import data_editor as base_editor %}
 
-{% block subitem_navigation %}
-        {% call(fullname, shortname, contenttype, has_children) utils.render_subitem_navigation(item_name, True) %}
-            {% set shortname = shortname|json_dumps %}
-            {% set fullname = fullname|json_dumps %}
-            <button class="link-action" onclick='linkSubitem({{ shortname }}, {{ fullname }})'
-                title="{{ _('Link to Subitem') }}">{{ _('Link') }}</button>
-            <button class="transclude-action"
-                onclick='transcludeSubitem({{ shortname }}, {{ fullname }})'
-                title="{{ _('Transclude Subitem') }}">{{ _('Transclude') }}</button>
-        {% endcall %}
-{% endblock %}
-
-{% block data_editor %}
+{% macro data_editor(form) %}
+{{ base_editor(form) }}
 {% if direction %}
-    {{ gen.textarea(form['data_text'], lang=lang, dir=direction, rows=rows_data, cols=cols) }}
+    {{ gen.textarea(form['data_text'], lang=lang, dir=direction, rows=form.rows|string, cols=form.cols|string) }}
 {% else %}
-    {{ gen.textarea(form['data_text'], lang=lang, rows=rows_data, cols=cols) }}
+    {{ gen.textarea(form['data_text'], lang=lang, rows=form.rows|string, cols=form.cols|string) }}
 {% endif %}
 <br />
-{% endblock %}
+{% endmacro %}
--- a/MoinMoin/templates/modify_text_html.html	Fri Jul 27 19:08:35 2012 +0530
+++ b/MoinMoin/templates/modify_text_html.html	Sun Jul 29 02:34:27 2012 +0530
@@ -1,13 +1,10 @@
-{% extends "modify_binary.html" %}
+{% from 'modify_text.html' import data_editor as base_editor %}
 
-{% block head %}
-{{ super() }}
+{% macro extra_head() %}
 <script type="text/javascript" src="{{ url_for('serve.files', name='ckeditor', filename='ckeditor.js') }}"></script>
 <link rel="stylesheet" href="{{ url_for('serve.files', name='ckeditor', filename='contents.css') }}" />
-{% endblock %}
+{% endmacro %}
 
-{% block data_editor %}
-<p>
-{{ gen.textarea(form['data_text'], class='ckeditor', lang=lang, dir=direction, rows=rows_data, cols=cols) }}
-</p>
-{% endblock %}
+{% macro data_editor(form) %}
+{{ gen.textarea(form['data_text'], class='ckeditor', lang=lang, dir=direction, rows=form.rows|string, cols=form.cols|string) }}
+{% endmacro %}
--- a/MoinMoin/templates/modify_twikidraw.html	Fri Jul 27 19:08:35 2012 +0530
+++ b/MoinMoin/templates/modify_twikidraw.html	Sun Jul 29 02:34:27 2012 +0530
@@ -1,5 +1,4 @@
-{% extends "modify_applet.html" %}
-{% block data_editor %}
+{% macro data_editor(form) %}
 <p>
 <applet code="CH.ifa.draw.twiki.TWikiDraw.class"
         archive="{{ url_for('serve.files', name='twikidraw_moin', filename='twikidraw_moin.jar') }}"
@@ -14,4 +13,4 @@
 </applet>
 </p>
 <br />
-{% endblock %}
+{% endmacro %}