changeset 2863:244f9e89fb15

merged main
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Sun, 23 Sep 2007 23:11:40 +0200
parents 8c1d40768df7 (current diff) 60f95d2b090b (diff)
children 69234509b7cf
files MoinMoin/converter/text_html_text_moin_wiki.py
diffstat 4 files changed, 656 insertions(+), 157 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/converter/_tests/test_text_html_text_moin_wiki.py	Sun Sep 23 23:11:18 2007 +0200
+++ b/MoinMoin/converter/_tests/test_text_html_text_moin_wiki.py	Sun Sep 23 23:11:40 2007 +0200
@@ -1177,7 +1177,7 @@
 class TestConvertImportFromOOo(TestBase):
     def do(self, text, output):
         text = text.strip('\n')
-        output = output.strip()
+        output = output.strip('\n')
         request = MinimalRequest(self.request)
         page = MinimalPage()
         out = self.do_convert_real([request, page.page_name, text])
@@ -1186,23 +1186,37 @@
 
     def testOOoTable01(self):
         # tests cut and paste from OOo to gui (empty cells do have a <br \>
-        test = u"""<p class="line874">
-        <meta content="text/html; charset=utf-8" http-equiv="CONTENT-TYPE" />
-        <title></title><meta content="OpenOffice.org 2.0  (Linux)" name="GENERATOR" />
-        <style type="text/css">
-        <!--
-        BODY,DIV,TABLE,THEAD,TBODY,TFOOT,TR,TH,TD,P { font-family:"Albany AMT"; font-size:x-small }
-         -->
-        </style>
-        <table rules="none" frame="void" cols="4" cellspacing="0" border="0">     <colgroup><col width="86"></col><col width="86"></col><col width="86"></col><col width="86"></col></colgroup>
-        <tbody>
-        <tr>             <td width="86" height="19" align="left"><font size="3">a</font></td>             <td width="86" valign="middle" align="left" sdnum="1031;0;@"><font size="3" face="Times New Roman">b</font></td>             <td width="86" valign="middle" align="left" sdnum="1031;0;@"><font size="3" face="Times New Roman">c</font></td>             <td width="86" valign="middle" align="left" sdnum="1031;0;@"><font size="3" face="Times New Roman"><br /></font></td>         </tr>
-        <tr>             <td valign="middle" height="19" align="left" sdnum="1031;0;@"><font size="3" face="Times New Roman">a</font></td>             <td valign="middle" align="center" sdnum="1031;0;@" colspan="2"><font size="3" face="Times New Roman">b</font></td>             <td valign="middle" align="left" sdnum="1031;0;@"><font size="3" face="Times New Roman">d</font></td>         </tr>
-        </tbody>
-        </table>  </p>"""
+        # readed from guis source box, blanks removed, changed to readable lines
+        # this tests could get broken on any parser change
+        test = u"""
+<p class="line874">
+<meta content="text/html; charset=utf-8" http-equiv="CONTENT-TYPE" />
+<title></title><meta content="OpenOffice.org 2.2  (Linux)" name="GENERATOR" />
+<style type="text/css">
+<!--
+    BODY,DIV,TABLE,THEAD,TBODY,TFOOT,TR,TH,TD,P { font-family:"DejaVu Sans"; font-size:x-small }
+-->
+</style>
+<table rules="none" frame="void" cols="2" cellspacing="0" border="0">
+<colgroup><col width="86"></col><col width="86"></col></colgroup>
+<tbody>
+<tr> <td width="86" height="16" align="left">a</td>
+     <td width="86" align="left"><br /></td>
+</tr>
+<tr>
+     <td height="16" align="left"><br /></td>
+     <td valign="middle" align="center">b</td>
+</tr>
+</tbody>
+</table>
+</p>
+"""
 
-        output = u"""||<( width="86px" height="19px">a||<( width="86px">b||<( width="86px">c||<( width="86px"> <<BR>> ||
-||<( height="19px">a||||b||<(>d||"""
+        output = u"""
+||<( width="86px" height="16px">a||<( width="86px"> ||
+||<( height="16px"> ||<:>b||
+"""
+
 
         self.do(test, output)
 
--- a/MoinMoin/converter/text_html_text_moin_wiki.py	Sun Sep 23 23:11:18 2007 +0200
+++ b/MoinMoin/converter/text_html_text_moin_wiki.py	Sun Sep 23 23:11:40 2007 +0200
@@ -767,11 +767,6 @@
             self.text.append(text)                          # so we just drop the header markup and keep the text
             return
 
-        # if we get br from e.g. cut and paste from OOo to the gui it should be appended as <<BR>>
-        if name == 'br':
-            self.text.append(' <<BR>> ')
-            return
-
         func = getattr(self, "process_%s" % name, None)
         if func:
             func(node)
@@ -1073,8 +1068,14 @@
         if not found:
             for i in node.childNodes:
                 if i.nodeType == Node.ELEMENT_NODE:
-                    self.process_inline(i)
-                    found = True
+                    if name == 'br':
+                        # if we get a br for an empty cell from e.g. cut and paste from OOo
+                        # to the gui it should be appended as white_space.
+                        self.text.append(self.white_space)
+                        found = True
+                    else:
+                        self.process_inline(i)
+                        found = True
                 elif i.nodeType == Node.TEXT_NODE:
                     data = i.data.strip('\n').replace('\n', ' ')
                     if data:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/parser/_tests/test_text_creole.py	Sun Sep 23 23:11:40 2007 +0200
@@ -0,0 +1,429 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - MoinMoin.parser.text_creole Tests
+
+    TODO: these are actually parser+formatter tests. We should have
+    parser only tests here.
+
+    @copyright: 2003-2004 by Juergen Hermann <jh@web.de>
+    @license: GNU GPL, see COPYING for details.
+"""
+
+import re
+from StringIO import StringIO
+
+import py
+
+from MoinMoin.Page import Page
+from MoinMoin.parser.text_creole import Parser as CreoleParser
+from MoinMoin.formatter.text_html import Formatter as HtmlFormatter
+
+PAGENAME = u'ThisPageDoesNotExistsAndWillNeverBeReally'
+
+class ParserTestCase(object):
+    """ Helper class that provide a parsing method """
+
+    def parse(self, body):
+        """Parse body and return html
+
+        Create a page with body, then parse it and format using html formatter
+        """
+        request = self.request
+        assert body is not None
+        request.reset()
+        page = Page(request, PAGENAME)
+        page.hilite_re = None
+        page.set_raw_body(body)
+        formatter = HtmlFormatter(request)
+        formatter.setPage(page)
+        page.formatter = formatter
+        request.formatter = formatter
+        parser = CreoleParser(body, request, line_anchors=False)
+        formatter.startContent('') # needed for _include_stack init
+        output = request.redirectedOutput(parser.format, formatter)
+        formatter.endContent('')
+        return output
+
+
+class TestParagraphs(ParserTestCase):
+    """ Test paragraphs creating
+
+    All tests ignoring white space in output.
+    We do not test for </p> as it is not there currently.
+    """
+
+    def testFirstParagraph(self):
+        """ parser.wiki: first paragraph should be in <p> """
+        result = self.parse('First')
+        assert re.search(r'<p.*?>\s*First\s*', result)
+
+    def testEmptyLineBetweenParagraphs(self):
+        """ parser.wiki: empty line separates paragraphs """
+        result = self.parse('First\n\nSecond')
+        assert re.search(r'<p.*?>\s*Second\s*', result)
+
+    def testParagraphAfterBlockMarkup(self):
+        """ parser.wiki: create paragraph after block markup """
+
+        markup = (
+            '----\n',
+            '| table |\n',
+            '= heading 1 =\n',
+            '== heading 2 ==\n',
+            '=== heading 3 ===\n',
+            '==== heading 4 ====\n',
+            '===== heading 5 =====\n',
+            )
+        for item in markup:
+            text = item + 'Paragraph'
+            result = self.parse(text)
+            assert re.search(r'<p.*?>\s*Paragraph\s*', result)
+
+
+class TestHeadings(ParserTestCase):
+    """ Test various heading problems """
+
+    def class_setup(self):
+        """ Require show_section_numbers = 0 to workaround counter
+        global state saved in request.
+        """
+        self.config = self.TestConfig(show_section_numbers=0)
+
+    def class_teardown(self):
+        del self.config
+
+    def testIgnoreWhiteSpaceAroundHeadingText(self):
+        """ parser.wiki: ignore white space around heading text
+
+        See bug: TableOfContentsBreakOnExtraSpaces.
+
+        Does not test mapping of '=' to h number, or valid html markup.
+        """
+        tests = (
+            '=  head =\n', # leading
+            '= head  =\n', # trailing
+            '=  head  =\n' # both
+                 )
+        expected = self.parse('= head =')
+        for test in tests:
+            result = self.parse(test)
+            assert result == expected
+
+
+class TestTOC(ParserTestCase):
+
+    def class_setup(self):
+        """ Require show_section_numbers = 0 to workaround counter
+        global state saved in request.
+        """
+        self.config = self.TestConfig(show_section_numbers=0)
+
+    def class_teardown(self):
+        del self.config
+
+    def testHeadingWithWhiteSpace(self):
+        """ parser.wiki: TOC links to headings with white space
+
+        See bug: TableOfContentsBreakOnExtraSpaces.
+
+        Does not test TOC or heading formating, just verify that spaces
+        around heading text does not matter.
+        """
+        standard = """
+<<TableOfContents>>
+= heading =
+Text
+"""
+        withWhitespace = """
+<<TableOfContents>>
+=   heading   =
+Text
+"""
+        expected = self.parse(standard)
+        result = self.parse(withWhitespace)
+        assert  result == expected
+
+
+class TestTextFormatingTestCase(ParserTestCase):
+    """ Test wiki markup """
+
+    text = 'AAA %s AAA'
+    needle = re.compile(text % r'(.+)')
+    _tests = (
+        # test,                     expected
+        ('no format',               'no format'),
+        ("//em//",                  '<em>em</em>'),
+        ("**bold**",              '<strong>bold</strong>'),
+        ("//**Mix** at start//",  '<em><strong>Mix</strong> at start</em>'),
+        ("**//Mix// at start**",  '<strong><em>Mix</em> at start</strong>'),
+        ("**Mix at //end//**",    '<strong>Mix at <em>end</em></strong>'),
+        ("//Mix at **end**//",    '<em>Mix at <strong>end</strong></em>'),
+        )
+
+    def testTextFormating(self):
+        """ parser.wiki: text formating """
+        for test, expected in self._tests:
+            html = self.parse(self.text % test)
+            result = self.needle.search(html).group(1)
+            assert result == expected
+
+
+class TestCloseInlineTestCase(ParserTestCase):
+
+    def testCloseOneInline(self):
+        """ parser.wiki: close open inline tag when block close """
+        cases = (
+            # test, expected
+            ("text**text\n", r"<p>text<strong>text\s*</strong></p>"),
+            ("text//text\n", r"<p>text<em>text\s*</em></p>"),
+            ("text //em **em strong", r"text <em>em <strong>em strong"),
+        )
+        for test, expected in cases:
+            result = self.parse(test)
+            assert re.search(expected, result)
+
+
+class TestInlineCrossing(ParserTestCase):
+    """
+    This test case fail with current parser/formatter and should be fixed in 2.0
+    """
+
+    def disabled_testInlineCrossing(self):
+        """ parser.wiki: prevent inline crossing <a><b></a></b> """
+
+        expected = (r"<p><em>a<strong>ab</strong></em><strong>b</strong>\s*</p>")
+        test = "//a**ab//b**\n"
+        result = self.parse(test)
+        assert re.search(expected, result)
+
+
+class TestEscapeHTML(ParserTestCase):
+
+    def testEscapeInTT(self):
+        """ parser.wiki: escape html markup in {{{tt}}} """
+        test = 'text {{{<escape-me>}}} text\n'
+        self._test(test)
+
+    def testEscapeInPre(self):
+        """ parser.wiki: escape html markup in pre """
+        test = '''{{{
+<escape-me>
+}}}
+'''
+        self._test(test)
+
+    def testEscapeInPreHashbang(self):
+        """ parser.wiki: escape html markup in pre with hashbang """
+        test = '''{{{#!
+<escape-me>
+}}}
+'''
+        self._test(test)
+
+    def testEscapeInPythonCodeArea(self):
+        """ parser.wiki: escape html markup in python code area """
+        test = '''{{{#!python
+#<escape-me>
+}}}
+'''
+        self._test(test)
+
+    def testEscapeInGetTextMacro(self):
+        """ parser.wiki: escape html markup in GetText macro """
+        test = "text <<GetText(<escape-me>)>> text"
+        self._test(test)
+
+# Getting double escaping
+#
+#    def testEscapeInGetTextFormatted(self):
+#        """ parser.wiki: escape html markup in getText formatted call """
+#        test = self.request.getText('<escape-me>', formatted=1)
+#        self._test(test)
+#
+#    def testEscapeInGetTextFormatedLink(self):
+#        """ parser.wiki: escape html markup in getText formatted call with link """
+#        test = self.request.getText('[[<escape-me>]]', formatted=1)
+#        self._test(test)
+
+    def testEscapeInGetTextUnFormatted(self):
+        """ parser.wiki: escape html markup in getText non formatted call """
+        test = self.request.getText('<escape-me>', formatted=0)
+        self._test(test)
+
+    def _test(self, test):
+        expected = r'&lt;escape-me&gt;'
+        result = self.parse(test)
+        assert re.search(expected, result)
+
+
+
+class TestRule(ParserTestCase):
+    """ Test rules markup """
+
+    def testNotRule(self):
+        """ parser.wiki: --- is no rule """
+        result = self.parse('---')
+        expected = '---' # inside <p>
+        assert expected in result
+
+    def testStandardRule(self):
+        """ parser.wiki: ---- is standard rule """
+        result = self.parse('----')
+        assert re.search(r'<hr.*?>', result)
+
+    def testLongRule(self):
+        """ parser.wiki: ----- is no rule """
+        test = '-----'
+        result = self.parse(test)
+        assert re.search(r'-----', result)
+
+
+class TestBlock(ParserTestCase):
+    cases = (
+        # test, block start
+        ('----\n', '<hr'),
+        ('= Heading =\n', '<h1'),
+        ('{{{\nPre\n}}}\n', '<pre'),
+        ('{{{\n#!python\nPre\n}}}\n', '<div'),
+        ('| Table |\n', '<div'),
+        (' * unordered list\n', '<ul'),
+        (' # ordered list\n', '<ol'),
+        )
+
+    def testParagraphBeforeBlock(self):
+        """ parser.wiki: paragraph closed before block element """
+        text = """AAA
+%s
+"""
+        for test, blockstart in self.cases:
+            # We dont test here formatter white space generation
+            expected = r'<p.*?>AAA\s*</p>\s*%s' % blockstart
+            needle = re.compile(expected, re.MULTILINE)
+            result = self.parse(text % test)
+            assert needle.search(result)
+
+    def testUrlAfterBlock(self):
+        """ parser.wiki: tests url after block element """
+        case = 'some text {{{some block text\n}}} and a URL http://moinmo.in/'
+
+        result = self.parse(case)
+        assert result.find('and a URL <a ') > -1
+
+    def testColorizedPythonParserAndNestingPreBrackets(self):
+        """ tests nested {{{ }}} for the python colorized parser
+        """
+
+        raw = """{{{
+#!python
+import re
+pattern = re.compile(r'{{{This is some nested text}}}')
+}}}"""
+        output = self.parse(raw)
+        output = ''.join(output)
+        assert "r'{{{This is some nested text}}}'" in output
+
+    def testColorizedPythonParserAndNestingPreBracketsWithLinebreak(self):
+        """ tests nested {{{ }}} for the python colorized parser
+        """
+
+        raw = """{{{
+#!python
+import re
+pattern = re.compile(r'{{{This is some nested text}}}')
+}}}"""
+        output = self.parse(raw)
+        output = ''.join(output)
+        assert "r'{{{This is some nested text}}}'" in output
+
+    def testNestingPreBracketsWithLinebreak(self):
+        """ tests nested {{{ }}} for the wiki parser
+        """
+
+        raw = """{{{
+Example
+You can use {{{brackets}}}
+}}}"""
+        output = self.parse(raw)
+        output = ''.join(output)
+        print output
+        assert 'You can use {{{brackets}}}' in output
+
+    def testTextBeforeNestingPreBrackets(self):
+        """ tests text before nested {{{ }}} for the wiki parser
+        """
+        raw = """Example
+{{{
+You can use {{{brackets}}}
+}}}"""
+        output = self.parse(raw)
+        output = ''.join(output)
+        assert 'Example </p><pre>You can use {{{brackets}}}</pre>' in output
+
+    def testManyNestingPreBrackets(self):
+        """ tests two nestings  ({{{ }}} and {{{ }}}) in one line for the wiki parser
+        """
+
+        raw = """{{{
+Test {{{brackets}}} and test {{{brackets}}}
+}}}"""
+        output = self.parse(raw)
+        output = ''.join(output)
+        expected = '<pre>Test {{{brackets}}} and test {{{brackets}}}'
+        assert expected in output
+
+    def testMultipleShortPreSections(self):
+        """
+        tests two single {{{ }}} in one line
+        """
+        raw = 'def {{{ghi}}} jkl {{{mno}}}'
+        output = ''.join(self.parse(raw))
+        expected = 'def <tt>ghi</tt> jkl <tt>mno</tt>'
+        assert expected in output
+
+class TestLinkingMarkup(ParserTestCase):
+    """ Test wiki link markup """
+
+    text = 'AAA %s AAA'
+    needle = re.compile(text % r'(.+)')
+    _tests = [
+        # test,           expected
+        ('[[SomeNonExistentPage]]', '<a class="nonexistent" href="./SomeNonExistentPage">SomeNonExistentPage</a>'),
+        ('[[SomeNonExistentPage#anchor]]', '<a class="nonexistent" href="./SomeNonExistentPage#anchor">SomeNonExistentPage#anchor</a>'),
+        ('[[something]]', '<a class="nonexistent" href="./something">something</a>'),
+        ('[[some thing]]', '<a class="nonexistent" href="./some%20thing">some thing</a>'),
+        ('[[something|some text]]', '<a class="nonexistent" href="./something">some text</a>'),
+        ('[[../something]]', '<a class="nonexistent" href="./something">../something</a>'),
+        ('[[/something]]', '<a class="nonexistent" href="./%s/something">/something</a>' % PAGENAME),
+        ('[[something#anchor]]', '<a class="nonexistent" href="./something#anchor">something#anchor</a>'),
+        ('[[MoinMoin:something]]', '<a class="interwiki" href="http://moinmoin.wikiwikiweb.de/something" title="MoinMoin">something</a>'),
+        ('[[MoinMoin:something|some text]]', '<a class="interwiki" href="http://moinmoin.wikiwikiweb.de/something" title="MoinMoin">some text</a>'),
+        ('[[MoinMoin:with space]]', '<a class="interwiki" href="http://moinmoin.wikiwikiweb.de/with%20space" title="MoinMoin">with space</a>'),
+        ('[[MoinMoin:with space|some text]]', '<a class="interwiki" href="http://moinmoin.wikiwikiweb.de/with%20space" title="MoinMoin">some text</a>'),
+        ('[[http://google.com/|google]]', '<a class="http" href="http://google.com/">google</a>'),
+        ]
+
+    def testLinkFormating(self):
+        """ parser.wiki: link formating """
+        for test, expected in self._tests:
+            html = self.parse(self.text % test)
+            result = self.needle.search(html).group(1)
+            assert result == expected
+
+    def testLinkAttachment(self):
+        html = self.parse("[[attachment:some file.txt]]")
+        assert '<a ' in html
+        assert 'href="' in html
+        assert 'class="attachment nonexistent"' in html
+        assert 'action=AttachFile' in html
+        assert 'some+file.txt' in html
+
+    def testLinkAttachmentImage(self):
+        html = self.parse("[[attachment:some file.png]]")
+        assert '<a ' in html # must create a link
+        assert 'href="' in html
+        assert 'class="attachment nonexistent"' in html
+        assert 'action=AttachFile' in html
+        assert 'some+file.png' in html
+
+coverage_modules = ['MoinMoin.parser.text_creole']
+
--- a/MoinMoin/parser/text_creole.py	Sun Sep 23 23:11:18 2007 +0200
+++ b/MoinMoin/parser/text_creole.py	Sun Sep 23 23:11:40 2007 +0200
@@ -10,10 +10,11 @@
     * No markup allowed in table headings.
       Creole 1.0 does not require us to support this.
     * No (non-bracketed) generic url recognition: this is "mission impossible"
-      except if you want to risk lots of false positives.
-    * We do not allow : before // italic markup to avoid urls with unrecognized
-      schemes (like wtf://server/path) triggering italic rendering for the rest
-      of the paragraph.
+      except if you want to risk lots of false positives. Only known protocols
+      are recognized.
+    * We do not allow ":" before "//" italic markup to avoid urls with
+      unrecognized schemes (like wtf://server/path) triggering italic rendering
+      for the rest of the paragraph.
 
     @copyright: 2007 MoinMoin:RadomirDopieralski (creole 0.5 implementation),
                 2007 MoinMoin:ThomasWaldmann (updates)
@@ -23,137 +24,161 @@
 import re
 import StringIO
 from MoinMoin import config, macro, wikiutil
-from MoinMoin.support.python_compatibility import rsplit
+from MoinMoin.support.python_compatibility import rsplit # Needed for python 2.3
 
 Dependencies = []
 
+# Whether the parser should convert \n into <br>.
+bloglike_lines = False
 
 class Parser:
     """
-    The class to glue the DocParser and DocEmitter with the
+    Glue the DocParser and DocEmitter with the
     MoinMoin current API.
     """
+
     # Enable caching
     caching = 1
-    Dependencies = []
+    Dependencies = Dependencies
 
     def __init__(self, raw, request, **kw):
         """Create a minimal Parser object with required attributes."""
+
         self.request = request
         self.form = request.form
         self.raw = raw
 
     def format(self, formatter):
         """Create and call the true parser and emitter."""
+
         document = DocParser(self.raw, self.request).parse()
         result = DocEmitter(document, formatter, self.request).emit()
         self.request.write(result)
 
-
-def _get_rule(rule, tab):
-    return r'(?P<%s>%s)' % (rule, tab.get(rule, ''))
-
-
 class DocParser:
     """
     Parse the raw text and create a document object
     that can be converted into output using Emitter.
     """
 
-    # The parsing rules
+    class Rules:
+        """Hold all the rules and generate regular expressions."""
 
-    # Whether the parser should convert \n into <br>.
-    bloglike_lines = False
+        # For the inline elements:
+        proto = r'http|https|ftp|nntp|news|mailto|telnet|file|irc'
+        url =  r'''(?P<url>
+                (^ | (?<=\s | [.,:;!?()/=]))
+                (?P<escaped_url>~)?
+                (?P<url_target> (?P<url_proto> %s ):\S+? )
+                ($ | (?=\s | [,.:;!?()] (\s | $)))
+            )''' % proto
+        link = r'''(?P<link>
+                \[\[
+                (?P<link_target>.+?) \s*
+                ([|] \s* (?P<link_text>.+?) \s*)?
+                ]]
+            )'''
+        image = r'''(?P<image>
+                {{
+                (?P<image_target>.+?) \s*
+                ([|] \s* (?P<image_text>.+?) \s*)?
+                }}
+            )'''
+        macro = r'''(?P<macro>
+                <<
+                (?P<macro_name> \w+)
+                (\( (?P<macro_args> .*?) \))? \s*
+                ([|] \s* (?P<macro_text> .+?) \s* )?
+                >>
+            )'''
+        code = r'(?P<code> {{{ (?P<code_text>.*?) }}} )'
+        emph = r'(?P<emph> (?<!:)// )' # there must be no : in front of the //
+                                       # avoids italic rendering in urls with
+                                       # unknown protocols
+        strong = r'(?P<strong> \*\* )'
+        linebreak = r'(?P<break> \\\\ )'
+        escape = r'(?P<escape> ~ (?P<escaped_char>\S) )'
+        char =  r'(?P<char> . )'
+
+        # For the block elements:
+        separator = r'(?P<rule> ^ \s* ---- \s* $ )' # horizontal line
+        line = r'(?P<line> ^ \s* $ )' # empty line that separates paragraphs
+        head = r'''(?P<head>
+                ^ \s*
+                (?P<head_head>=+) \s*
+                (?P<head_text> .*? ) \s*
+                (?P<head_tail>=*) \s*
+                $
+            )'''
+        if bloglike_lines:
+            text = r'(?P<text> .+ ) (?P<break> (?<!\\)$\n(?!\s*$) )?'
+        else:
+            text = r'(?P<text> .+ )'
+        list = r'''(?P<list>
+                ^ [ \t]* ([*][^*\#]|[\#][^\#*]).* $
+                ( \n[ \t]* [*\#]+.* $ )*
+            )''' # Matches the whole list, separate items are parsed later. The
+                 # list *must* start with a single bullet.
+        item = r'''(?P<item>
+                ^ \s*
+                (?P<item_head> [\#*]+) \s*
+                (?P<item_text> .*?)
+                $
+            )''' # Matches single list items
+        pre = r'''(?P<pre>
+                ^{{{ \s* $
+                (\n)?
+                (?P<pre_text>
+                    ([\#]!(?P<pre_kind>\w*?)(\s+.*)?$)?
+                    (.|\n)+?
+                )
+                (\n)?
+                ^}}} \s*$
+            )'''
+        pre_escape = r' ^(?P<indent>\s*) ~ (?P<rest> \}\}\} \s*) $'
+        table = r'''(?P<table>
+                ^ \s*
+                [|].*? \s*
+                [|]? \s*
+                $
+            )'''
+
+        # For the link targets:
+        extern = r'(?P<extern_addr>(?P<extern_proto>%s):.*)' % proto
+        attach = r'''
+                (?P<attach_scheme> attachment | inline | drawing | figure):
+                (?P<attach_addr> .* )
+            '''
+        interwiki = r'''
+                (?P<inter_wiki> [A-Z][a-zA-Z]+ ) :
+                (?P<inter_page> .* )
+            '''
+        page = r'(?P<page_name> .* )'
+
+        # For splitting table cells:
+        cell = r'''
+                \| \s*
+                (
+                    (?P<head> [=][^|]+ ) |
+                    (?P<cell> (  %s | [^|])+ )
+                ) \s*
+            ''' % '|'.join([link, macro, image, code])
 
     # For pre escaping, in creole 1.0 done with ~:
-    pre_escape_re = re.compile(r'^(?P<indent>\s*)~(?P<rest>\}\}\}\s*)$', re.M)
-
-    # For the inline elements:
-    inline_tab = {
-        'url': r'''(^|(?<=\s|[.,:;!?()/=]))(?P<escaped_url>~)?(?P<url_target>(?P<url_proto>http|ftp|mailto|irc|https|ftps|news|gopher|file|telnet|nntp):\S+?)($|(?=\s|[,.:;!?()](\s|$)))''',
-        'link': r'\[\[(?P<link_target>.+?)\s*(\|\s*(?P<link_text>.+?)\s*)?]]',
-        'image': r'{{(?P<image_target>.+?)\s*(\|\s*(?P<image_text>.+?)\s*)?}}',
-        'macro': r'<<(?P<macro_target>.+?)\s*(\|\s*(?P<macro_text>.+?)\s*)?>>',
-        'code': r'{{{(?P<code_text>.*?)}}}',
-        'emph': r'(?<!:)//', # there must be no : in front of the // - avoids
-                             # italic rendering in urls with unknown protocols
-        'strong': r'\*\*',
-        'break': r'\\\\',
-        'escape': r'~(?P<escaped_char>[^\s])', # tilde is the escape char
-        'char': r'.',
-    }
-
-    # For the block elements:
-    rule_rule = r'(?P<rule>^\s*----\s*$)'
-    line_rule = r'(?P<line>^\s*$)'
-    head_rule = r'(?P<head>^\s*(?P<head_head>=+)\s*(?P<head_text>[^*].*?)\s*(?P<head_tail>=*)\s*$)'
-    if bloglike_lines:
-        text_rule = r'(?P<text>.+)(?P<break>(?<!\\)$\n(?!\s*$))?'
-    else:
-        text_rule = r'(?P<text>.+)'
-    list_rule = r'(?P<list> ^[ \t]* ([*][^*]|[\#][^\#]) .*$  (\n[ \t]*[*\#]+.*$)*  )'
-    pre_rule = r'(?P<pre>^{{{\s*$(\n)?(?P<pre_text>(^[\#]!(?P<pre_kind>.*?)(\s+.*)?$)?(.|\n)+?)(\n)?^}}}\s*$)'
-    table_rule = r'(?P<table>^\s*\|.*?\s*\|?\s*$)'
-
-    # For the link targets:
-    extern_rule = r'(?P<extern_addr>(?P<extern_proto>http|https|ftp|nntp|news|mailto|telnet|file|irc):.*)'
-    attach_rule = r'(?P<attach_scheme>attachment|inline|drawing|figure):(?P<attach_addr>.*)'
-    inter_rule = r'(?P<inter_wiki>[A-Z][a-zA-Z]+):(?P<inter_page>.*)'
-    #u'|'.join(wikimacro.getNames(config))
-    macro_rule = r'(?P<macro_name>%s)\((-|(?P<macro_param>.*))\)' % r'\w+'
-    page_rule = r'(?P<page_name>.*)'
-
-    # For splitting table cells:
-    cell_rule = r'\|\s* ( (?P<head> [=][^|]+) | (?P<cell> ((%(link)s) |(%(macro)s) |(%(image)s) |(%(code)s) | [^|] )+)  )\s*' % inline_tab
-    cell_re = re.compile(cell_rule, re.X|re.U)
-
-    # For link descriptions:
-    link_rules = r'|'.join([
-            _get_rule('image', inline_tab),
-            _get_rule('break', inline_tab),
-            _get_rule('char', inline_tab),
-    ])
-    link_re = re.compile(link_rules, re.X|re.U)
-
-    # For lists:
-    item_rule = r'(?P<item> ^\s* (?P<item_head> [\#*]+ ) \s* (?P<item_text>.*?) $)'
-    item_re = re.compile(item_rule, re.X|re.U|re.M)
-
+    pre_escape_re = re.compile(Rules.pre_escape, re.M | re.X)
+    link_re = re.compile('|'.join([Rules.image, Rules.linebreak, Rules.char]), re.X | re.U) # for link descriptions
+    item_re = re.compile(Rules.item, re.X | re.U | re.M) # for list items
+    cell_re = re.compile(Rules.cell, re.X | re.U) # for table cells
+    addr_re = re.compile('|'.join([Rules.extern, Rules.attach,
+        Rules.interwiki, Rules.page]), re.X | re.U) # for addresses
     # For block elements:
-    block_rules = '|'.join([
-            line_rule,
-            head_rule,
-            rule_rule,
-            pre_rule,
-            list_rule,
-            table_rule,
-            text_rule,
-    ])
-    block_re = re.compile(block_rules, re.X|re.U|re.M)
-
-    addr_rules = r'|'.join([
-        macro_rule,
-        extern_rule,
-        attach_rule,
-        inter_rule,
-        page_rule,
-    ])
-    addr_re = re.compile(addr_rules, re.X|re.U)
-
-    inline_rules = r'|'.join([
-            _get_rule('link', inline_tab),
-            _get_rule('url', inline_tab),
-            _get_rule('macro', inline_tab),
-            _get_rule('code', inline_tab),
-            _get_rule('image', inline_tab),
-            _get_rule('strong', inline_tab),
-            _get_rule('emph', inline_tab),
-            _get_rule('break', inline_tab),
-            _get_rule('escape', inline_tab),
-            _get_rule('char', inline_tab),
-    ])
-    inline_re = re.compile(inline_rules, re.X|re.U)
-    del inline_tab
+    block_re = re.compile('|'.join([Rules.line, Rules.head, Rules.separator,
+        Rules.pre, Rules.list, Rules.table, Rules.text]), re.X | re.U | re.M)
+    # For inline elements:
+    inline_re = re.compile('|'.join([Rules.link, Rules.url, Rules.macro,
+        Rules.code, Rules.image, Rules.strong, Rules.emph, Rules.linebreak,
+        Rules.escape, Rules.char]), re.X | re.U)
+    del Rules # We have the regexps compiled, rules not needed anymore
 
     def __init__(self, raw, request):
         self.request = request
@@ -208,7 +233,10 @@
                 node.proto = m.group('extern_proto')
             elif m.group('inter_wiki'):
                 node = DocNode('interwiki_link', self.cur)
-                node.content = '%s:%s' % (m.group('inter_wiki'), m.group('inter_page'))
+                node.content = '%s:%s' % (
+                    m.group('inter_wiki'), m.group('inter_page'))
+                if not text:
+                    text = m.group('inter_page')
             elif m.group('attach_scheme'):
                 scheme = m.group('attach_scheme')
                 if scheme == 'inline':
@@ -227,18 +255,14 @@
 
     def _macro_repl(self, groups):
         """Handles macros using the placeholder syntax."""
-        target = groups.get('macro_target', '')
+        name = groups.get('macro_name', '')
         text = (groups.get('macro_text', '') or '').strip()
-        m = self.addr_re.match(target)
-        if m and m.group('macro_name'):
-            node = DocNode('macro', self.cur, m.group('macro_name'))
-            node.args = m.group('macro_param')
-        else:
-            node = DocNode('bad_link', self.cur)
-            node.content = target
-            DocNode('text', node, text or target)
+        node = DocNode('macro', self.cur, name)
+        node.args = groups.get('macro_args', '') or ''
+        DocNode('text', node, text or name)
         self.text = None
-    _macro_target_repl = _macro_repl
+    _macro_name_repl = _macro_repl
+    _macro_args_repl = _macro_repl
     _macro_text_repl = _macro_repl
 
     def _image_repl(self, groups):
@@ -279,7 +303,8 @@
             self.cur = lst
         else:
             # Create a new level of list
-            self.cur = self._upto(self.cur, ('list_item', 'document', 'section', 'blockquote'))
+            self.cur = self._upto(self.cur,
+                ('list_item', 'document', 'section', 'blockquote'))
             self.cur = DocNode(kind, self.cur)
             self.cur.level = level
         self.cur = DocNode('list_item', self.cur)
@@ -300,19 +325,23 @@
     _head_text_repl = _head_repl
 
     def _text_repl(self, groups):
-        if self.cur.kind in ('table', 'table_row', 'bullet_list', 'number_list'):
-            self.cur = self._upto(self.cur, ('document', 'section', 'blockquote'))
+        if self.cur.kind in ('table', 'table_row', 'bullet_list',
+            'number_list'):
+            self.cur = self._upto(self.cur,
+                ('document', 'section', 'blockquote'))
         if self.cur.kind in ('document', 'section', 'blockquote'):
             self.cur = DocNode('paragraph', self.cur)
         self.parse_inline(groups.get('text', '')+' ')
-        if groups.get('break') and self.cur.kind in ('paragraph', 'emphasis', 'strong', 'code'):
+        if groups.get('break') and self.cur.kind in ('paragraph',
+            'emphasis', 'strong', 'code'):
             DocNode('break', self.cur, '')
         self.text = None
     _break_repl = _text_repl
 
     def _table_repl(self, groups):
         row = groups.get('table', '|').strip()
-        self.cur = self._upto(self.cur, ('table', 'document', 'section', 'blockquote'))
+        self.cur = self._upto(self.cur, (
+            'table', 'document', 'section', 'blockquote'))
         if self.cur.kind != 'table':
             self.cur = DocNode('table', self.cur)
         tb = self.cur
@@ -386,6 +415,7 @@
 
     def _replace(self, match):
         """Invoke appropriate _*_repl method. Called for every matched group."""
+
         groups = match.groupdict()
         for name, text in groups.iteritems():
             if text is not None:
@@ -395,10 +425,12 @@
 
     def parse_inline(self, raw):
         """Recognize inline elements inside blocks."""
+
         re.sub(self.inline_re, self._replace, raw)
 
     def parse_block(self, raw):
         """Recognize block elements."""
+
         re.sub(self.block_re, self._replace, raw)
 
     def parse(self):
@@ -407,10 +439,12 @@
 
 #################### Helper classes
 
-### The document model, and true parser and emitter follow
+### The document model and emitter follow
 
 class DocNode:
-    """A node in the Document."""
+    """
+    A node in the document.
+    """
 
     def __init__(self, kind='', parent=None, content=None):
         self.children = []
@@ -422,7 +456,10 @@
 
 
 class DocEmitter:
-    """Generate the output for the document tree consisting of DocNodes."""
+    """
+    Generate the output for the document
+    tree consisting of DocNodes.
+    """
 
     def __init__(self, root, formatter, request):
         self.root = root
@@ -433,16 +470,22 @@
 
     def get_image(self, addr, text=''):
         """Return markup for image depending on the address."""
+
         if addr is None:
             addr = ''
         url = wikiutil.url_unquote(addr, want_unicode=True)
         if addr.startswith('http:'):
-            return self.formatter.image(src=url, alt=text, html_class='external_image')
+            return self.formatter.image(
+                src=url, alt=text, html_class='external_image'
+            )
         else:
-            return self.formatter.attachment_image(url, alt=text, html_class='image')
+            return self.formatter.attachment_image(
+                url, alt=text, html_class='image'
+            )
 
     def get_text(self, node):
         """Try to emit whatever text is in the node."""
+
         try:
             return node.children[0].content or ''
         except:
@@ -562,8 +605,9 @@
 
     def header_emit(self, node):
         import sha
-        pntt = self.formatter.page.page_name + self.get_text(node)+'%d' % node.level
-        ident = "head-" + sha.new(pntt.encode(config.charset)).hexdigest()
+        pntt = '%s%s%d' % (self.formatter.page.page_name,
+            self.get_text(node), node.level)
+        ident = "head-%s" % sha.new(pntt.encode(config.charset)).hexdigest()
         return ''.join([
             self.formatter.heading(1, node.level, id=ident),
             self.formatter.text(node.content or ''),
@@ -571,10 +615,17 @@
         ])
 
     def code_emit(self, node):
+# XXX The current formatter will replace all spaces with &nbsp;, so we need
+# to use rawHTML instead, until that is fixed.
+#        return ''.join([
+#            self.formatter.code(1),
+#            self.formatter.text(node.content or ''),
+#            self.formatter.code(0),
+#        ])
         return ''.join([
-            self.formatter.code(1),
+            self.formatter.rawHTML('<tt>'),
             self.formatter.text(node.content or ''),
-            self.formatter.code(0),
+            self.formatter.rawHTML('</tt>'),
         ])
 
     def abbr_emit(self, node):
@@ -587,8 +638,11 @@
     def page_link_emit(self, node):
         word = node.content
         # handle relative links
-        if word.startswith(wikiutil.CHILD_PREFIX):
-            word = self.formatter.page.page_name + '/' + word[wikiutil.CHILD_PREFIX_LEN:]
+        if word.startswith(wikiutil.PARENT_PREFIX):
+            word = word[wikiutil.PARENT_PREFIX_LEN:]
+        elif word.startswith(wikiutil.CHILD_PREFIX):
+            word = "%s/%s" % (self.formatter.page.page_name,
+                word[wikiutil.CHILD_PREFIX_LEN:])
         # handle anchors
         parts = rsplit(word, "#", 1)
         anchor = ""
@@ -602,7 +656,7 @@
 
     def external_link_emit(self, node):
         return ''.join([
-            self.formatter.url(1, node.content, css='www %s' % node.proto),
+            self.formatter.url(1, node.content, css=node.proto),
             self.emit_children(node),
             self.formatter.url(0),
         ])
@@ -620,7 +674,7 @@
             wikiutil.resolve_interwiki(self.request, wiki, page)
         href = wikiutil.join_wiki(wikiurl, wikitail)
         return ''.join([
-            self.formatter.interwikilink(1, wikitag, wikitail),
+            self.formatter.interwikilink(1, wikitag, wikitail, title=wikitag),
             self.emit_children(node),
             self.formatter.interwikilink(0),
         ])
@@ -740,13 +794,14 @@
         return output
 
 
-    # Private helpers ------------------------------------------------------------
+# Private helpers ------------------------------------------------------------
 
     def setParser(self, name):
         """ Set parser to parser named 'name' """
         # XXX this is done by the formatter as well
         try:
-            self.parser = wikiutil.searchAndImportPlugin(self.request.cfg, "parser", name)
+            self.parser = wikiutil.searchAndImportPlugin(self.request.cfg,
+                "parser", name)
         except wikiutil.PluginMissingError:
             self.parser = None