Index: trac/mimeview/api.py
===================================================================
--- trac/mimeview/api.py	(revision 2737)
+++ trac/mimeview/api.py	(working copy)
@@ -188,6 +188,162 @@
         annotation data."""
 
 
+class MimeType:
+    """ Represents a MIME type. The first element of types is the primary MIME
+    type. """
+    def __init__(self, description, types, extensions):
+        self.types, self.extensions, self.description = types, extensions, description
+        self.type = self.types[0]
+
+class IMimeTypeRegistry(Interface):
+    def lookup_mime_type(type):
+        """ Return MimeType object or None if MIME type not found. """
+
+    def lookup_mime_extension(extension):
+        """ Return MimeType object or None if file extension not found. """
+
+    def lookup_mime_content(content):
+        """ Try and determine the MIME type of an object by content. """
+
+class IMimeConverter(Interface):
+    def get_mime_conversions():
+        """ Return an iterator of (input_type, output_type, output_type_description) tuples. """
+
+    def perform_mime_conversion(req, source, input_type, output_type):
+        """ Perform conversion of a source "string" to an alternate MIME type.
+        Returns (content_type, output). """
+
+class MimeSystem(Component):
+    """ Manage MIME types. """
+
+    implements(IMimeTypeRegistry)
+
+    registries = ExtensionPoint(IMimeTypeRegistry)
+    converters = ExtensionPoint(IMimeConverter)
+
+    _mime_registry_hardcoded = (
+        MimeType('Plain Text', ('text/plain',), ('txt',)),
+        MimeType('HTML', ('text/html',), ('html', 'htm')),
+        MimeType('Trac Wiki Text', ('text/x-trac-wiki', 'application/x-trac-wiki'), ())
+    )
+
+    def __init__(self):
+        self._mime_registry_types = {}
+        self._mime_registry_extensions = {}
+
+        for mimetype in self._mime_registry_hardcoded:
+            for type in mimetype.types:
+                self._mime_registry_types[type] = mimetype
+            for ext in mimetype.extensions:
+                self._mime_registry_extensions[ext] = mimetype
+
+    def lookup_extension(self, extension):
+        """ Look up the MIME type for a given extension. """
+        for register in self.registries:
+            type = register.lookup_mime_extension(extension)
+            if type:
+                return type
+        raise TracError("No MIME type for extension %s" % extension)
+
+    def lookup_type(self, type):
+        """ Look up MIME type details for a given type. """
+        for register in self.registries:
+            mimetype = register.lookup_mime_type(type)
+            if mimetype:
+                return mimetype
+        raise TracError("No matching MIME type for %s" % type)
+
+    def lookup_content(self, content):
+        """ Determine MIME type by content alone. """
+        for register in self.registries:
+            mimetype = register.lookup_content(content)
+            if mimetype:
+                return mimetype
+        raise TracError("No matching MIME type for content")
+
+    def lookup_filename(self, filename, content = None):
+        """ Try and determine MIME type details for a filename. """
+        extension = filename.split('.')[-1]
+        try:
+            return self.lookup_extension(extension)
+        except TracError:
+            try:
+                if content: self.lookup_content(content)
+            except TracError:
+                raise TracError("No matching MIME type for filename or content")
+
+    def lookup_conversions_from(self, input_type):
+        """ Return an iterable of types that input_type can be converted to. """
+        for converter in self.converters:
+            for i, o, d in converter.get_mime_conversions():
+                if i == input_type:
+                    yield self.lookup_type(o)
+
+    def lookup_conversions_to(self, output_type):
+        """ Return an iterable of types that output_type can be converted from. """
+        for converter in self.converters:
+            for i, o, desc in converter.get_mime_conversions():
+                if o == output_type:
+                    yield self.lookup_type(i)
+
+    def convert(self, req, source, input_type, output_type):
+        """ Convert source between MIME types. Returns (content_type, output). """
+        for converter in self.converters:
+            for conversion in converter.get_mime_conversions():
+                if conversion[0:2] == (input_type, output_type):
+                    return converter.perform_mime_conversion(req, source, input_type, output_type)
+        raise TracError("No MIME conversion from %s to %s" % (input_type, output_type))
+
+    # IMimeTypeRegistry methods
+
+    def _description_from_type(self, type):
+        description = type
+        # Fall back on the description from the MIME converters
+        for converter in self.converters:
+            for i, o, d in converter.get_mime_conversions():
+                if o == type:
+                    description = d
+                    break
+            if description != type:
+                break
+        return description
+
+    def lookup_mime_type(self, type):
+        try:
+            return self._mime_registry_types[type]
+        except:
+            description = self._description_from_type(type)
+            try:
+                import mimetypes
+                exts = [x[1:] for x in mimetypes.guess_all_extensions(type)]
+                return MimeType(description, (type,), exts)
+            except:
+                return None
+
+    def lookup_mime_extension(self, extension):
+        try:
+            return self._mime_registry_extensions[extension]
+        except:
+            try:
+                import mimetypes
+                type = mimetypes.guess_type('foo.' + extension)[0]
+                description = self._description_from_type(type)
+                return MimeType(description, (type,), (extension,))
+            except:
+                return None
+
+    def lookup_mime_content(self, content):
+        global MODE_RE, MIME_MAP
+        match = re.search(MODE_RE, content[:1000])
+        if match:
+            mode = match.group(1) or match.group(2).lower()
+            if MIME_MAP.has_key(mode):
+                return MIME_MAP[mode]
+        if content and is_binary(content):
+            return 'application/octet-stream'
+        return None
+        
+
 class Mimeview(Component):
     """A generic class to prettify data, typically source code."""
 
Index: trac/wiki/web_ui.py
===================================================================
--- trac/wiki/web_ui.py	(revision 2737)
+++ trac/wiki/web_ui.py	(working copy)
@@ -31,12 +31,13 @@
 from trac.web import IRequestHandler
 from trac.wiki.model import WikiPage
 from trac.wiki.formatter import wiki_to_html, wiki_to_oneliner
+from trac.mimeview.api import MimeSystem, IMimeConverter
 
 
 class WikiModule(Component):
 
     implements(INavigationContributor, IPermissionRequestor, IRequestHandler,
-               ITimelineEventProvider, ISearchSource)
+               ITimelineEventProvider, ISearchSource, IMimeConverter)
 
     # INavigationContributor methods
 
@@ -109,11 +110,17 @@
         elif action == 'history':
             self._render_history(req, db, page)
         else:
-            if req.args.get('format') == 'txt':
+            if req.args.has_key('format'):
+                format = req.args.get('format')
+                # Backwards compatibility
+                if format == 'txt':
+                    format = 'text/plain'
+                mimesystem = MimeSystem(self.env)
+                content_type, output = mimesystem.convert(req, page.text, 'text/x-trac-wiki', format)
                 req.send_response(200)
-                req.send_header('Content-Type', 'text/plain;charset=utf-8')
+                req.send_header('Content-Type', content_type)
                 req.end_headers()
-                req.write(page.text)
+                req.write(output)
                 return
             self._render_view(req, db, page)
 
@@ -357,8 +364,10 @@
             # Ask web spiders to not index old versions
             req.hdf['html.norobots'] = 1
 
-        txt_href = self.env.href.wiki(page.name, version=version, format='txt')
-        add_link(req, 'alternate', txt_href, 'Plain Text', 'text/plain')
+        mimesystem = MimeSystem(self.env)
+        for mimetype in mimesystem.lookup_conversions_from('text/x-trac-wiki'):
+            mime_href = self.env.href.wiki(page.name, version=version, format=mimetype.type)
+            add_link(req, 'alternate', mime_href, mimetype.description, mimetype.type)
 
         req.hdf['wiki'] = {'page_name': page.name, 'exists': page.exists,
                            'version': page.version, 'readonly': page.readonly}
@@ -405,3 +414,15 @@
                    '%s: %s' % (name, shorten_line(text)),
                    date, author,
                    shorten_result(text, query.split()))
+
+    # IMimeConverter methods
+
+    def get_mime_conversions(self):
+        yield ('text/x-trac-wiki', 'text/plain', 'Plain text')
+        yield ('text/x-trac-wiki', 'text/html', 'HTML')
+
+    def perform_mime_conversion(self, req, source, input_type, output_type):
+        if (input_type, output_type) == ('text/x-trac-wiki', 'text/plain'):
+            return ('text/plain;charset=utf-8', source)
+        else:
+            return ('text/html', wiki_to_html(source, self.env, req))
