changeset 2858:70a1f88575ce

Creole parser: tests for creole parser
author Radomir Dopieralski <moindev@sheep.art.pl>
date Sun, 23 Sep 2007 22:18:17 +0200
parents b5b3a9d16abf
children 2087924a60f1
files MoinMoin/parser/_tests/test_text_creole.py MoinMoin/parser/text_creole.py
diffstat 2 files changed, 577 insertions(+), 103 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/parser/_tests/test_text_creole.py	Sun Sep 23 22:18:17 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 16:57:41 2007 +0200
+++ b/MoinMoin/parser/text_creole.py	Sun Sep 23 22:18:17 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,123 +24,160 @@
 import re
 import StringIO
 from MoinMoin import config, macro, wikiutil
-from MoinMoin.support.python_compatibility import rsplit
 
 Dependencies = []
 
+# Whether the parser should convert \n into <br>.
+bloglike_lines = False
 
 class Parser:
     """
     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)
 
-
 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:
-    proto_rule = r'http|https|ftp|nntp|news|mailto|telnet|file|irc'
-    url_rule =  r'''(?P<url>(^|(?<=\s|[.,:;!?()/=]))(?P<escaped_url>~)?
-        (?P<url_target>(?P<url_proto>%s):\S+?)
-        ($|(?=\s|[,.:;!?()](\s|$))))''' % proto_rule
-    link_rule = r'''(?P<link>\[\[(?P<link_target>.+?)\s*
-        (\|\s*(?P<link_text>.+?)\s*)?]])'''
-    image_rule = r'''(?P<image>{{(?P<image_target>.+?)\s*
-        (\|\s*(?P<image_text>.+?)\s*)?}})'''
-    macro_rule = r'''(?P<macro><<(?P<macro_target>.+?)\s*
-        (\|\s*(?P<macro_text>.+?)\s*)?>>)'''
-    code_rule = r'(?P<code>{{{(?P<code_text>.*?)}}})'
-    # there must be no : in front of the // - avoids
-    # italic rendering in urls with unknown protocols
-    emph_rule = r'(?P<emph>(?<!:)//)'
-    strong_rule = r'(?P<strong>\*\*)'
-    break_rule = r'(?P<break>\\\\)'
-    escape_rule = r'(?P<escape>~(?P<escaped_char>[^\s]))'
-    char_rule =  r'(?P<char>.)'
-
-    # 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>%s):.*)' % proto_rule
-    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 = '|'.join([
-        image_rule, break_rule, char_rule,
-    ])
-    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 = '|'.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([
-        link_rule, url_rule, macro_rule, code_rule, image_rule, strong_rule,
-        emph_rule, break_rule, escape_rule, char_rule,
-    ])
-    inline_re = re.compile(inline_rules, re.X | re.U)
+    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
@@ -196,6 +234,8 @@
                 node = DocNode('interwiki_link', self.cur)
                 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':
@@ -214,18 +254,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):
@@ -578,10 +614,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):
@@ -594,11 +637,13 @@
     def page_link_emit(self, node):
         word = node.content
         # handle relative links
-        if word.startswith(wikiutil.CHILD_PREFIX):
+        if word.startswith('..%s' % wikiutil.CHILD_PREFIX):
+            word = word[3:] # remove the dots
+        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)
+        parts = word.rsplit("#", 1)
         anchor = ""
         if len(parts) == 2:
             word, anchor = parts
@@ -610,7 +655,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),
         ])
@@ -628,7 +673,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),
         ])