diff --git a/_test/cases/inc/parser/xhtml_links.test.php b/_test/cases/inc/parser/xhtml_links.test.php
index a9a6dfdbc14c3d0fb8d5b019a4b6f1ce2f4b05f4..cac5e9252beb9aa44e578470f8aa58f07241be7d 100644
--- a/_test/cases/inc/parser/xhtml_links.test.php
+++ b/_test/cases/inc/parser/xhtml_links.test.php
@@ -1,5 +1,6 @@
 <?php
 if (!defined('DOKU_BASE')) define('DOKU_BASE','./');
+require_once DOKU_INC.'inc/init.php';
 require_once DOKU_INC.'inc/parser/xhtml.php';
 require_once DOKU_INC.'inc/pageutils.php';
 
diff --git a/_testing/README b/_testing/README
index 0987cb605411dedc4dd563bfd10d0e7cbd82f4e4..f69eed90b4870a4309c16575cccf741e3241b6ef 100644
--- a/_testing/README
+++ b/_testing/README
@@ -25,9 +25,14 @@ Bad tests are tests that do not run out of the box.
   * inc/indexer_idx_indexlengths
   * inc/mail_send
   * inc/pageutils_resolve_pageid
+  * inc/parser/parser_formatting
+  * inc/parser/xhtml_htmlphp
+  * inc/parser/xhtml_links
 
 
 ==== TODO ====
-  * transform inc/remote.test.php (mocking)
+  * Mocking
+    * inc/remote.test.php
+    * inc/parser/lexer.test.php
   * plugin unit tests
   * integration tests (+plugins)
\ No newline at end of file
diff --git a/_testing/unittests/inc/parser/parser.inc.php b/_testing/unittests/inc/parser/parser.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..06c314ac67e9518c90f80e094e876f3f311db317
--- /dev/null
+++ b/_testing/unittests/inc/parser/parser.inc.php
@@ -0,0 +1,45 @@
+<?php
+/**
+* @version $Id: parser.inc.php,v 1.2 2005/03/25 21:00:22 harryf Exp $
+* @package Doku
+* @subpackage Tests
+*/
+
+/**
+* Includes
+*/
+require_once DOKU_INC . 'inc/init.php';
+require_once DOKU_INC . 'inc/confutils.php';
+require_once DOKU_INC . 'inc/parser/parser.php';
+require_once DOKU_INC . 'inc/parser/handler.php';
+require_once DOKU_INC . 'inc/events.php';
+require_once DOKU_INC . 'inc/mail.php';
+
+/**
+* @package Doku
+* @subpackage Tests
+*/
+abstract class TestOfDoku_Parser extends PHPUnit_Framework_TestCase {
+
+    var $P;
+    var $H;
+
+    function setup() {
+        $this->P = new Doku_Parser();
+        $this->H = new Doku_Handler();
+        $this->P->Handler = & $this->H;
+    }
+
+    function tearDown() {
+        unset($this->P);
+        unset($this->H);
+    }
+}
+
+function stripByteIndex($call) {
+    unset($call[2]);
+    if ($call[0] == "nest") {
+      $call[1][0] = array_map('stripByteIndex',$call[1][0]);
+    }
+    return $call;
+}
diff --git a/_testing/unittests/inc/parser/parser_eol.test.php b/_testing/unittests/inc/parser/parser_eol.test.php
new file mode 100644
index 0000000000000000000000000000000000000000..03569bb623bcf941f53a668bbac6250293f64e31
--- /dev/null
+++ b/_testing/unittests/inc/parser/parser_eol.test.php
@@ -0,0 +1,96 @@
+<?php
+require_once 'parser.inc.php';
+
+class TestOfDoku_Parser_Eol extends TestOfDoku_Parser {
+
+    function testEol() {
+        $this->P->addMode('eol',new Doku_Parser_Mode_Eol());
+        $this->P->parse("Foo\nBar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("Foo".DOKU_PARSER_EOL."Bar")),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testEolMultiple() {
+        $this->P->addMode('eol',new Doku_Parser_Mode_Eol());
+        $this->P->parse("Foo\n\nbar\nFoo");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("Foo")),
+            array('p_close',array()),
+            array('p_open',array()),
+            array('cdata',array("bar".DOKU_PARSER_EOL."Foo")),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+    
+    function testWinEol() {
+        $this->P->addMode('eol',new Doku_Parser_Mode_Eol());
+        $this->P->parse("Foo\r\nBar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("Foo".DOKU_PARSER_EOL."Bar")),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+    
+    function testLinebreak() {
+        $this->P->addMode('linebreak',new Doku_Parser_Mode_Linebreak());
+        $this->P->parse('Foo\\\\ Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nFoo")),
+            array('linebreak',array()),
+            array('cdata',array("Bar")),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+    
+    function testLinebreakPlusEol() {
+        $this->P->addMode('linebreak',new Doku_Parser_Mode_Linebreak());
+        $this->P->addMode('eol',new Doku_Parser_Mode_Eol());
+        $this->P->parse('Foo\\\\'."\n\n".'Bar');
+        
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("Foo")),
+            array('linebreak',array()),
+            array('p_close',array()),
+            array('p_open',array()),
+            array('cdata',array("Bar")),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+    
+    function testLinebreakInvalid() {
+        $this->P->addMode('linebreak',new Doku_Parser_Mode_Linebreak());
+        $this->P->parse('Foo\\\\Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo\\\\Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+}
+
diff --git a/_testing/unittests/inc/parser/parser_footnote.test.php b/_testing/unittests/inc/parser/parser_footnote.test.php
new file mode 100644
index 0000000000000000000000000000000000000000..f3b5ddf56ba1faee4e81e65fa3240747b3e811f7
--- /dev/null
+++ b/_testing/unittests/inc/parser/parser_footnote.test.php
@@ -0,0 +1,388 @@
+<?php
+require_once 'parser.inc.php';
+
+class TestOfDoku_Parser_Footnote extends TestOfDoku_Parser {
+
+    function setup() {
+        parent::setup();
+        $this->P->addMode('footnote',new Doku_Parser_Mode_Footnote());
+    }
+
+    function testFootnote() {
+        $this->P->parse('Foo (( testing )) Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('nest', array ( array (
+              array('footnote_open',array()),
+              array('cdata',array(' testing ')),
+              array('footnote_close',array()),
+            ))),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testNotAFootnote() {
+        $this->P->parse("Foo (( testing\n Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nFoo (( testing\n Bar")),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testFootnoteLinefeed() {
+        $this->P->addMode('eol',new Doku_Parser_Mode_Eol());
+        $this->P->parse("Foo (( testing\ntesting )) Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array('Foo ')),
+            array('nest', array ( array (
+              array('footnote_open',array()),
+              array('cdata',array(" testing\ntesting ")),
+              array('footnote_close',array()),
+            ))),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testFootnoteNested() {
+        $this->P->parse('Foo (( x((y))z )) Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('nest', array ( array (
+              array('footnote_open',array()),
+              array('cdata',array(' x((y')),
+              array('footnote_close',array()),
+            ))),
+            array('cdata',array('z )) Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testFootnoteEol() {
+        $this->P->addMode('eol',new Doku_Parser_Mode_Eol());
+        $this->P->parse("Foo \nX(( test\ning ))Y\n Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array('Foo '.DOKU_PARSER_EOL.'X')),
+            array('nest', array ( array (
+              array('footnote_open',array()),
+              array('cdata',array(" test\ning ")),
+              array('footnote_close',array()),
+            ))),
+            array('cdata',array('Y'.DOKU_PARSER_EOL.' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testFootnoteStrong() {
+        $this->P->addMode('strong',new Doku_Parser_Mode_Formatting('strong'));
+        $this->P->parse('Foo (( **testing** )) Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('nest', array ( array (
+              array('footnote_open',array()),
+              array('cdata',array(' ')),
+              array('strong_open',array()),
+              array('cdata',array('testing')),
+              array('strong_close',array()),
+              array('cdata',array(' ')),
+              array('footnote_close',array()),
+            ))),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testFootnoteHr() {
+        $this->P->addMode('hr',new Doku_Parser_Mode_HR());
+        $this->P->parse("Foo (( \n ---- \n )) Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('nest', array ( array (
+              array('footnote_open',array()),
+              array('cdata',array(' ')),
+              array('hr',array()),
+              array('cdata',array("\n ")),
+              array('footnote_close',array()),
+            ))),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testFootnoteCode() {
+        $this->P->addMode('code',new Doku_Parser_Mode_Code());
+        $this->P->parse("Foo (( <code>Test</code> )) Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('nest', array ( array (
+              array('footnote_open',array()),
+              array('cdata',array(' ')),
+              array('code',array('Test',null,null)),
+              array('cdata',array(' ')),
+              array('footnote_close',array()),
+            ))),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testFootnotePreformatted() {
+        $this->P->addMode('preformatted',new Doku_Parser_Mode_Preformatted());
+        $this->P->parse("Foo (( \n  Test\n )) Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('nest', array ( array (
+              array('footnote_open',array()),
+              array('cdata',array(' ')),
+              array('preformatted',array('Test')),
+              array('cdata',array(' ')),
+              array('footnote_close',array()),
+            ))),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testFootnotePreformattedEol() {
+        $this->P->addMode('preformatted',new Doku_Parser_Mode_Preformatted());
+        $this->P->addMode('eol',new Doku_Parser_Mode_Eol());
+        $this->P->parse("Foo (( \n  Test\n )) Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array('Foo ')),
+            array('nest', array ( array (
+              array('footnote_open',array()),
+              array('cdata',array(' ')),
+              array('preformatted',array('Test')),
+              array('cdata',array(' ')),
+              array('footnote_close',array()),
+            ))),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testFootnoteUnformatted() {
+        $this->P->addMode('unformatted',new Doku_Parser_Mode_Unformatted());
+        $this->P->parse("Foo (( <nowiki>Test</nowiki> )) Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('nest', array ( array (
+              array('footnote_open',array()),
+              array('cdata',array(' ')),
+              array('unformatted',array('Test')),
+              array('cdata',array(' ')),
+              array('footnote_close',array()),
+            ))),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testFootnoteNotHeader() {
+        $this->P->addMode('unformatted',new Doku_Parser_Mode_Unformatted());
+        $this->P->parse("Foo (( \n====Test====\n )) Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('nest', array ( array (
+              array('footnote_open',array()),
+              array('cdata',array(" \n====Test====\n ")),
+              array('footnote_close',array()),
+            ))),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testFootnoteTable() {
+        $this->P->addMode('table',new Doku_Parser_Mode_Table());
+        $this->P->parse("Foo ((
+| Row 0 Col 1    | Row 0 Col 2     | Row 0 Col 3        |
+| Row 1 Col 1    | Row 1 Col 2     | Row 1 Col 3        |
+ )) Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('nest', array ( array (
+              array('footnote_open',array()),
+              array('table_open',array(3, 2, 8)),
+              array('tablerow_open',array()),
+              array('tablecell_open',array(1,'left',1)),
+              array('cdata',array(' Row 0 Col 1    ')),
+              array('tablecell_close',array()),
+              array('tablecell_open',array(1,'left',1)),
+              array('cdata',array(' Row 0 Col 2     ')),
+              array('tablecell_close',array()),
+              array('tablecell_open',array(1,'left',1)),
+              array('cdata',array(' Row 0 Col 3        ')),
+              array('tablecell_close',array()),
+              array('tablerow_close',array()),
+              array('tablerow_open',array()),
+              array('tablecell_open',array(1,'left',1)),
+              array('cdata',array(' Row 1 Col 1    ')),
+              array('tablecell_close',array()),
+              array('tablecell_open',array(1,'left',1)),
+              array('cdata',array(' Row 1 Col 2     ')),
+              array('tablecell_close',array()),
+              array('tablecell_open',array(1,'left',1)),
+              array('cdata',array(' Row 1 Col 3        ')),
+              array('tablecell_close',array()),
+              array('tablerow_close',array()),
+              array('table_close',array(123)),
+              array('cdata',array(' ')),
+              array('footnote_close',array()),
+            ))),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testFootnoteList() {
+        $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock());
+        $this->P->parse("Foo ((
+  *A
+    * B
+  * C
+ )) Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('nest', array ( array (
+              array('footnote_open',array()),
+              array('listu_open',array()),
+              array('listitem_open',array(1)),
+              array('listcontent_open',array()),
+              array('cdata',array("A")),
+              array('listcontent_close',array()),
+              array('listu_open',array()),
+              array('listitem_open',array(2)),
+              array('listcontent_open',array()),
+              array('cdata',array(' B')),
+              array('listcontent_close',array()),
+              array('listitem_close',array()),
+              array('listu_close',array()),
+              array('listitem_close',array()),
+              array('listitem_open',array(1)),
+              array('listcontent_open',array()),
+              array('cdata',array(' C')),
+              array('listcontent_close',array()),
+              array('listitem_close',array()),
+              array('listu_close',array()),
+              array('cdata',array(' ')),
+              array('footnote_close',array()),
+            ))),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testFootnoteQuote() {
+        $this->P->addMode('quote',new Doku_Parser_Mode_Quote());
+        $this->P->parse("Foo ((
+> def
+>>ghi
+ )) Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('nest', array ( array (
+              array('footnote_open',array()),
+              array('quote_open',array()),
+              array('cdata',array(" def")),
+              array('quote_open',array()),
+              array('cdata',array("ghi")),
+              array('quote_close',array()),
+              array('quote_close',array()),
+              array('cdata',array(' ')),
+              array('footnote_close',array()),
+            ))),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testFootnoteNesting() {
+        $this->P->addMode('strong',new Doku_Parser_Mode_Formatting('strong'));
+        $this->P->parse("(( a ** (( b )) ** c ))");
+
+        $calls = array(
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n")),
+            array('nest', array ( array (
+              array('footnote_open',array()),
+              array('cdata',array(' a ')),
+              array('strong_open',array()),
+              array('cdata',array(' (( b ')),
+              array('footnote_close',array()),
+            ))),
+            array('cdata',array(" ")),
+            array('strong_close',array()),
+            array('cdata',array(" c ))")),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+}
+
diff --git a/_testing/unittests/inc/parser/parser_headers.test.php b/_testing/unittests/inc/parser/parser_headers.test.php
new file mode 100644
index 0000000000000000000000000000000000000000..d7e5f0e42b7bd7f50902eb566192ab0e2edd7147
--- /dev/null
+++ b/_testing/unittests/inc/parser/parser_headers.test.php
@@ -0,0 +1,282 @@
+<?php
+require_once 'parser.inc.php';
+
+class TestOfDoku_Parser_Headers extends TestOfDoku_Parser {
+
+    function testHeader1() {
+        $this->P->addMode('header',new Doku_Parser_Mode_Header());
+        $this->P->parse("abc \n ====== Header ====== \n def");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nabc ")),
+            array('p_close',array()),
+            array('header',array('Header',1,6)),
+            array('section_open',array(1)),
+            array('p_open',array()),
+            array('cdata',array("\n def")),
+            array('p_close',array()),
+            array('section_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+    
+    function testHeader2() {
+        $this->P->addMode('header',new Doku_Parser_Mode_Header());
+        $this->P->parse("abc \n  ===== Header ===== \n def");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nabc ")),
+            array('p_close',array()),
+            array('header',array('Header',2,6)),
+            array('section_open',array(2)),
+            array('p_open',array()),
+            array('cdata',array("\n def")),
+            array('p_close',array()),
+            array('section_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+    
+    function testHeader3() {
+        $this->P->addMode('header',new Doku_Parser_Mode_Header());
+        $this->P->parse("abc \n ==== Header ==== \n def");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nabc ")),
+            array('p_close',array()),
+            array('header',array('Header',3,6)),
+            array('section_open',array(3)),
+            array('p_open',array()),
+            array('cdata',array("\n def")),
+            array('p_close',array()),
+            array('section_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+    
+    function testHeader4() {
+        $this->P->addMode('header',new Doku_Parser_Mode_Header());
+        $this->P->parse("abc \n === Header === \n def");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nabc ")),
+            array('p_close',array()),
+            array('header',array('Header',4,6)),
+            array('section_open',array(4)),
+            array('p_open',array()),
+            array('cdata',array("\n def")),
+            array('p_close',array()),
+            array('section_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+    
+    function testHeader5() {
+        $this->P->addMode('header',new Doku_Parser_Mode_Header());
+        $this->P->parse("abc \n  == Header ==  \n def");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nabc ")),
+            array('p_close',array()),
+            array('header',array('Header',5,6)),
+            array('section_open',array(5)),
+            array('p_open',array()),
+            array('cdata',array("\n def")),
+            array('p_close',array()),
+            array('section_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testHeader2UnevenSmaller() {
+        $this->P->addMode('header',new Doku_Parser_Mode_Header());
+        $this->P->parse("abc \n  ===== Header ==  \n def");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nabc ")),
+            array('p_close',array()),
+            array('header',array('Header',2,6)),
+            array('section_open',array(2)),
+            array('p_open',array()),
+            array('cdata',array("\n def")),
+            array('p_close',array()),
+            array('section_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+    
+    function testHeader2UnevenBigger() {
+        $this->P->addMode('header',new Doku_Parser_Mode_Header());
+        $this->P->parse("abc \n  ===== Header ===========  \n def");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nabc ")),
+            array('p_close',array()),
+            array('header',array('Header',2,6)),
+            array('section_open',array(2)),
+            array('p_open',array()),
+            array('cdata',array("\n def")),
+            array('p_close',array()),
+            array('section_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+    
+    function testHeaderLarge() {
+        $this->P->addMode('header',new Doku_Parser_Mode_Header());
+        $this->P->parse("abc \n ======= Header ======= \n def");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nabc ")),
+            array('p_close',array()),
+            array('header',array('Header',1,6)),
+            array('section_open',array(1)),
+            array('p_open',array()),
+            array('cdata',array("\n def")),
+            array('p_close',array()),
+            array('section_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+    
+    function testHeaderSmall() {
+        $this->P->addMode('header',new Doku_Parser_Mode_Header());
+        $this->P->parse("abc \n= Header =\n def");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nabc \n= Header =\n def")),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+    
+    
+    function testHeader1Mixed() {
+        $this->P->addMode('header',new Doku_Parser_Mode_Header());
+        $this->P->parse("abc \n====== == Header == ======\n def");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nabc ")),
+            array('p_close',array()),
+            array('header',array('== Header ==',1,6)),
+            array('section_open',array(1)),
+            array('p_open',array()),
+            array('cdata',array("\n def")),
+            array('p_close',array()),
+            array('section_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testHeader5Mixed() {
+        $this->P->addMode('header',new Doku_Parser_Mode_Header());
+        $this->P->parse("abc \n== ====== Header ====== ==\n def");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nabc ")),
+            array('p_close',array()),
+            array('header',array('====== Header ======',5,6)),
+            array('section_open',array(5)),
+            array('p_open',array()),
+            array('cdata',array("\n def")),
+            array('p_close',array()),
+            array('section_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+    
+    function testHeaderMultiline() {
+        $this->P->addMode('header',new Doku_Parser_Mode_Header());
+        $this->P->parse("abc \n== ====== Header\n ====== ==\n def");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nabc \n== ====== Header")),
+            array('p_close',array()),
+            array('header',array('',1,23)),
+            array('section_open',array(1)),
+            array('p_open',array()),
+            array('cdata',array("\n def")),
+            array('p_close',array()),
+            array('section_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+    
+#    function testNoToc() {
+#        $this->P->addMode('notoc',new Doku_Parser_Mode_NoToc());
+#        $this->P->parse('abc ~~NOTOC~~ def');
+#        $this->assertFalse($this->H->meta['toc']);
+#    }
+    
+    function testHeader1Eol() {
+        $this->P->addMode('header',new Doku_Parser_Mode_Header());
+        $this->P->addMode('eol',new Doku_Parser_Mode_Eol());
+        $this->P->parse("abc \n ====== Header ====== \n def");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array('abc ')),
+            array('p_close',array()),
+            array('header',array('Header',1, 6)),
+            array('section_open',array(1)),
+            array('p_open',array()),
+            array('cdata',array(' def')),
+            array('p_close',array()),
+            array('section_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+        
+    }
+
+    function testHeaderMulti2() {
+        $this->P->addMode('header',new Doku_Parser_Mode_Header());
+        $this->P->parse("abc \n ====== Header ====== \n def abc \n ===== Header2 ===== \n def");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nabc ")),
+            array('p_close',array()),
+            array('header',array('Header',1,6)),
+            array('section_open',array(1)),
+            array('p_open',array()),
+            array('cdata',array("\n def abc ")),
+            array('p_close',array()),
+            array('section_close',array()),
+            array('header',array('Header2',2,39)),
+            array('section_open',array(2)),
+            array('p_open',array()),
+            array('cdata',array("\n def")),
+            array('p_close',array()),
+            array('section_close',array()),
+            array('document_end',array())
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+    
+}
+
diff --git a/_testing/unittests/inc/parser/parser_i18n.test.php b/_testing/unittests/inc/parser/parser_i18n.test.php
new file mode 100644
index 0000000000000000000000000000000000000000..096f2e227cec364d2660b446e354a1995d0e7fde
--- /dev/null
+++ b/_testing/unittests/inc/parser/parser_i18n.test.php
@@ -0,0 +1,162 @@
+<?php
+require_once 'parser.inc.php';
+
+class TestOfDoku_Parser_i18n extends TestOfDoku_Parser {
+
+    function testFormatting() {
+        $formats = array (
+            'strong', 'emphasis', 'underline', 'monospace',
+            'subscript', 'superscript', 'deleted',
+        );
+        foreach ( $formats as $format ) {
+            $this->P->addMode($format,new Doku_Parser_Mode_Formatting($format));
+        }
+        $this->P->parse("I**ñ**t__ë__r//n//â<sup>t</sup>i<sub>ô</sub>n''à''liz<del>æ</del>tiøn");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nI")),
+            array('strong_open',array()),
+            array('cdata',array('ñ')),
+            array('strong_close',array()),
+            array('cdata',array('t')),
+            array('underline_open',array()),
+            array('cdata',array('ë')),
+            array('underline_close',array()),
+            array('cdata',array('r')),
+            array('emphasis_open',array()),
+            array('cdata',array('n')),
+            array('emphasis_close',array()),
+            array('cdata',array('â')),
+            array('superscript_open',array()),
+            array('cdata',array('t')),
+            array('superscript_close',array()),
+            array('cdata',array('i')),
+            array('subscript_open',array()),
+            array('cdata',array('ô')),
+            array('subscript_close',array()),
+            array('cdata',array('n')),
+            array('monospace_open',array()),
+            array('cdata',array('à')),
+            array('monospace_close',array()),
+            array('cdata',array('liz')),
+            array('deleted_open',array()),
+            array('cdata',array('æ')),
+            array('deleted_close',array()),
+            array('cdata',array("tiøn")),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testHeader() {
+        $this->P->addMode('header',new Doku_Parser_Mode_Header());
+        $this->P->parse("Foo\n ==== Iñtërnâtiônàlizætiøn ==== \n Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nFoo")),
+            array('p_close',array()),
+            array('header',array('Iñtërnâtiônàlizætiøn',3,5)),
+            array('section_open',array(3)),
+            array('p_open',array()),
+            array('cdata',array("\n Bar")),
+            array('p_close',array()),
+            array('section_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testTable() {
+        $this->P->addMode('table',new Doku_Parser_Mode_Table());
+        $this->P->parse('
+abc
+| Row 0 Col 1    | Iñtërnâtiônàlizætiøn     | Row 0 Col 3        |
+| Row 1 Col 1    | Iñtërnâtiônàlizætiøn     | Row 1 Col 3        |
+def');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n\nabc")),
+            array('p_close',array()),
+            array('table_open',array(3, 2, 6)),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 0 Col 1    ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Iñtërnâtiônàlizætiøn     ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 0 Col 3        ')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 1    ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Iñtërnâtiônàlizætiøn     ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 3        ')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('table_close',array(153)),
+            array('p_open',array()),
+            array('cdata',array('def')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testAcronym() {
+        $t = array('Iñtërnâtiônàlizætiøn');
+        $this->P->addMode('acronym',new Doku_Parser_Mode_Acronym($t));
+        $this->P->parse("Foo Iñtërnâtiônàlizætiøn Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nFoo ")),
+            array('acronym',array('Iñtërnâtiônàlizætiøn')),
+            array('cdata',array(" Bar")),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testInterwiki() {
+        $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink());
+        $this->P->parse("Foo [[wp>Iñtërnâtiônàlizætiøn|Iñtërnâtiônàlizætiøn]] Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('interwikilink',array('wp>Iñtërnâtiônàlizætiøn','Iñtërnâtiônàlizætiøn','wp','Iñtërnâtiônàlizætiøn')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testInternalLink() {
+        $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink());
+        $this->P->parse("Foo [[x:Iñtërnâtiônàlizætiøn:y:foo_bar:z|Iñtërnâtiônàlizætiøn]] Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('internallink',array('x:Iñtërnâtiônàlizætiøn:y:foo_bar:z','Iñtërnâtiônàlizætiøn')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+}
+
diff --git a/_testing/unittests/inc/parser/parser_links.test.php b/_testing/unittests/inc/parser/parser_links.test.php
new file mode 100644
index 0000000000000000000000000000000000000000..529efac6e6141936db37cfc6fc3900681a7bcf3f
--- /dev/null
+++ b/_testing/unittests/inc/parser/parser_links.test.php
@@ -0,0 +1,691 @@
+<?php
+require_once 'parser.inc.php';
+
+class TestOfDoku_Parser_Links extends TestOfDoku_Parser {
+
+    function testExternalLinkSimple() {
+        $this->P->addMode('externallink',new Doku_Parser_Mode_ExternalLink());
+        $this->P->parse("Foo http://www.google.com Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('externallink',array('http://www.google.com', NULL)),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testExternalLinkCase() {
+        $this->P->addMode('externallink',new Doku_Parser_Mode_ExternalLink());
+        $this->P->parse("Foo HTTP://WWW.GOOGLE.COM Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('externallink',array('HTTP://WWW.GOOGLE.COM', NULL)),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testExternalIPv4() {
+        $this->P->addMode('externallink',new Doku_Parser_Mode_ExternalLink());
+        $this->P->parse("Foo http://123.123.3.21/foo Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('externallink',array('http://123.123.3.21/foo', NULL)),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testExternalIPv6() {
+        $this->P->addMode('externallink',new Doku_Parser_Mode_ExternalLink());
+        $this->P->parse("Foo http://[3ffe:2a00:100:7031::1]/foo Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('externallink',array('http://[3ffe:2a00:100:7031::1]/foo', NULL)),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testExternalMulti(){
+        $this->teardown();
+
+        $links = array(
+            'http://www.google.com',
+            'HTTP://WWW.GOOGLE.COM',
+            'http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html',
+            'http://[1080:0:0:0:8:800:200C:417A]/index.html',
+            'http://[3ffe:2a00:100:7031::1]',
+            'http://[1080::8:800:200C:417A]/foo',
+            'http://[::192.9.5.5]/ipng',
+            'http://[::FFFF:129.144.52.38]:80/index.html',
+            'http://[2010:836B:4179::836B:4179]',
+        );
+        $titles = array(false,null,'foo bar');
+        foreach($links as $link){
+            foreach($titles as $title){
+                if($title === false){
+                    $source = $link;
+                    $name   = null;
+                }elseif($title === null){
+                    $source = "[[$link]]";
+                    $name   = null;
+                }else{
+                    $source = "[[$link|$title]]";
+                    $name   = $title;
+                }
+                $this->setup();
+                $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink());
+                $this->P->addMode('externallink',new Doku_Parser_Mode_ExternalLink());
+                $this->P->parse("Foo $source Bar");
+                $calls = array (
+                    array('document_start',array()),
+                    array('p_open',array()),
+                    array('cdata',array("\n".'Foo ')),
+                    array('externallink',array($link, $name)),
+                    array('cdata',array(' Bar')),
+                    array('p_close',array()),
+                    array('document_end',array()),
+                );
+                $this->assertEquals(array_map('stripByteIndex',$this->H->calls), $calls, $source);
+                $this->teardown();
+            }
+        }
+
+        $this->setup();
+    }
+
+    function testExternalLinkJavascript() {
+        $this->P->addMode('externallink',new Doku_Parser_Mode_ExternalLink());
+        $this->P->parse("Foo javascript:alert('XSS'); Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nFoo javascript:alert('XSS'); Bar")),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testExternalWWWLink() {
+        $this->P->addMode('externallink',new Doku_Parser_Mode_ExternalLink());
+        $this->P->parse("Foo www.google.com Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('externallink',array('http://www.google.com', 'www.google.com')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testExternalFTPLink() {
+        $this->P->addMode('externallink',new Doku_Parser_Mode_ExternalLink());
+        $this->P->parse("Foo ftp.sunsite.com Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('externallink',array('ftp://ftp.sunsite.com', 'ftp.sunsite.com')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+    function testEmail() {
+        $this->P->addMode('emaillink',new Doku_Parser_Mode_Emaillink());
+        $this->P->parse("Foo <bugs@php.net> Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('emaillink',array('bugs@php.net', NULL)),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testEmailRFC2822() {
+        $this->P->addMode('emaillink',new Doku_Parser_Mode_Emaillink());
+        $this->P->parse("Foo <~fix+bug's.for/ev{e}r@php.net> Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('emaillink',array("~fix+bug's.for/ev{e}r@php.net", NULL)),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testEmailCase() {
+        $this->P->addMode('emaillink',new Doku_Parser_Mode_Emaillink());
+        $this->P->parse("Foo <bugs@pHp.net> Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('emaillink',array('bugs@pHp.net', NULL)),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+
+    function testInternalLinkOneChar() {
+        $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink());
+        $this->P->parse("Foo [[l]] Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('internallink',array('l',NULL)),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testInternalLinkNoChar() {
+        $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink());
+        $this->P->parse("Foo [[]] Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('internallink',array('',NULL)),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testInternalLinkNamespaceNoTitle() {
+        $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink());
+        $this->P->parse("Foo [[foo:bar]] Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('internallink',array('foo:bar',NULL)),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testInternalLinkNamespace() {
+        $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink());
+        $this->P->parse("Foo [[x:1:y:foo_bar:z|Test]] Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('internallink',array('x:1:y:foo_bar:z','Test')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testInternalLinkSectionRef() {
+        $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink());
+        $this->P->parse("Foo [[wiki:syntax#internal|Syntax]] Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('internallink',array('wiki:syntax#internal','Syntax')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testExternalInInternalLink() {
+        $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink());
+        $this->P->parse("Foo [[http://www.google.com|Google]] Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('externallink',array('http://www.google.com','Google')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testInterwikiLink() {
+        $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink());
+        $this->P->parse("Foo [[iw>somepage|Some Page]] Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('interwikilink',array('iw>somepage','Some Page','iw','somepage')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testInterwikiLinkCase() {
+        $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink());
+        $this->P->parse("Foo [[IW>somepage|Some Page]] Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('interwikilink',array('IW>somepage','Some Page','iw','somepage')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testInterwikiPedia() {
+        $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink());
+        $this->P->parse("Foo [[wp>Callback_(computer_science)|callbacks]] Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('interwikilink',array('wp>Callback_(computer_science)','callbacks','wp','Callback_(computer_science)')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testCamelCase() {
+        $this->P->addMode('camelcaselink',new Doku_Parser_Mode_CamelCaseLink());
+        $this->P->parse("Foo FooBar Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('camelcaselink',array('FooBar')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testFileLink() {
+        $this->P->addMode('filelink',new Doku_Parser_Mode_FileLink());
+        $this->P->parse('Foo file://temp/file.txt Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('filelink',array('file://temp/file.txt ',NULL)),
+            array('cdata',array('Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testFileLinkInternal() {
+        $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink());
+        $this->P->parse('Foo [[file://temp/file.txt|Some File]] Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('externallink',array('file://temp/file.txt','Some File')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testWindowsShareLink() {
+        $this->P->addMode('windowssharelink',new Doku_Parser_Mode_WindowsShareLink());
+        $this->P->parse('Foo \\\server\share Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('windowssharelink',array('\\\server\share',NULL)),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+    
+    function testWindowsShareLinkHyphen() {
+        $this->P->addMode('windowssharelink',new Doku_Parser_Mode_WindowsShareLink());
+        $this->P->parse('Foo \\\server\share-hyphen Bar');
+        $calls = array (
+        array('document_start',array()),
+        array('p_open',array()),
+        array('cdata',array("\n".'Foo ')),
+        array('windowssharelink',array('\\\server\share-hyphen',NULL)),
+        array('cdata',array(' Bar')),
+        array('p_close',array()),
+        array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testWindowsShareLinkInternal() {
+        $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink());
+        $this->P->parse('Foo [[\\\server\share|My Documents]] Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('windowssharelink',array('\\\server\share','My Documents')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testMediaInternal() {
+        $this->P->addMode('media',new Doku_Parser_Mode_Media());
+        $this->P->parse('Foo {{img.gif}} Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('internalmedia',array('img.gif',NULL,NULL,NULL,NULL,'cache','details')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testMediaInternalLinkOnly() {
+        $this->P->addMode('media',new Doku_Parser_Mode_Media());
+        $this->P->parse('Foo {{img.gif?linkonly}} Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('internalmedia',array('img.gif',NULL,NULL,NULL,NULL,'cache','linkonly')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testMediaNotImage() {
+        $this->P->addMode('media',new Doku_Parser_Mode_Media());
+        $this->P->parse('Foo {{foo.txt?10x10|Some File}} Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('internalmedia',array('foo.txt','Some File',null,10,10,'cache','details')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testMediaInternalLAlign() {
+        $this->P->addMode('media',new Doku_Parser_Mode_Media());
+        $this->P->parse('Foo {{img.gif }} Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('internalmedia',array('img.gif',NULL,'left',NULL,NULL,'cache','details')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testMediaInternalRAlign() {
+        $this->P->addMode('media',new Doku_Parser_Mode_Media());
+        $this->P->parse('Foo {{ img.gif}} Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('internalmedia',array('img.gif',NULL,'right',NULL,NULL,'cache','details')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testMediaInternalCenter() {
+        $this->P->addMode('media',new Doku_Parser_Mode_Media());
+        $this->P->parse('Foo {{ img.gif }} Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('internalmedia',array('img.gif',NULL,'center',NULL,NULL,'cache','details')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testMediaInternalParams() {
+        $this->P->addMode('media',new Doku_Parser_Mode_Media());
+        $this->P->parse('Foo {{img.gif?50x100nocache}} Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('internalmedia',array('img.gif',NULL,NULL,'50','100','nocache','details')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testMediaInternalTitle() {
+        $this->P->addMode('media',new Doku_Parser_Mode_Media());
+        $this->P->parse('Foo {{img.gif?50x100|Some Image}} Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('internalmedia',array('img.gif','Some Image',NULL,'50','100','cache','details')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testMediaExternal() {
+        $this->P->addMode('media',new Doku_Parser_Mode_Media());
+        $this->P->parse('Foo {{http://www.google.com/img.gif}} Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('externalmedia',array('http://www.google.com/img.gif',NULL,NULL,NULL,NULL,'cache','details')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testMediaExternalParams() {
+        $this->P->addMode('media',new Doku_Parser_Mode_Media());
+        $this->P->parse('Foo {{http://www.google.com/img.gif?50x100nocache}} Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('externalmedia',array('http://www.google.com/img.gif',NULL,NULL,'50','100','nocache','details')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testMediaExternalTitle() {
+        $this->P->addMode('media',new Doku_Parser_Mode_Media());
+        $this->P->parse('Foo {{http://www.google.com/img.gif?50x100|Some Image}} Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('externalmedia',
+            array('http://www.google.com/img.gif','Some Image',NULL,'50','100','cache','details')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testMediaInInternalLink() {
+        $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink());
+        $this->P->parse("Foo [[x:1:y:foo_bar:z|{{img.gif?10x20nocache|Some Image}}]] Bar");
+
+        $image = array(
+            'type'=>'internalmedia',
+            'src'=>'img.gif',
+            'title'=>'Some Image',
+            'align'=>NULL,
+            'width'=>10,
+            'height'=>20,
+            'cache'=>'nocache',
+            'linking'=>'details',
+        );
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('internallink',array('x:1:y:foo_bar:z',$image)),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testMediaNoImageInInternalLink() {
+        $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink());
+        $this->P->parse("Foo [[x:1:y:foo_bar:z|{{foo.txt?10x20nocache|Some Image}}]] Bar");
+
+        $image = array(
+            'type'=>'internalmedia',
+            'src'=>'foo.txt',
+            'title'=>'Some Image',
+            'align'=>NULL,
+            'width'=>10,
+            'height'=>20,
+            'cache'=>'nocache',
+            'linking'=>'details',
+        );
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('internallink',array('x:1:y:foo_bar:z',$image)),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testMediaInEmailLink() {
+        $this->P->addMode('internallink',new Doku_Parser_Mode_InternalLink());
+        $this->P->parse("Foo [[foo@example.com|{{img.gif?10x20nocache|Some Image}}]] Bar");
+
+        $image = array(
+            'type'=>'internalmedia',
+            'src'=>'img.gif',
+            'title'=>'Some Image',
+            'align'=>NULL,
+            'width'=>10,
+            'height'=>20,
+            'cache'=>'nocache',
+            'linking'=>'details',
+        );
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('emaillink',array('foo@example.com',$image)),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+    function testNestedMedia() {
+        $this->P->addMode('media',new Doku_Parser_Mode_Media());
+        $this->P->parse('Foo {{img.gif|{{foo.gif|{{bar.gif|Bar}}}}}} Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('internalmedia',
+            array('img.gif','{{foo.gif|{{bar.gif|Bar',NULL,NULL,NULL,'cache','details')),
+            array('cdata',array('}}}} Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+
+}
+
diff --git a/_testing/unittests/inc/parser/parser_lists.test.php b/_testing/unittests/inc/parser/parser_lists.test.php
new file mode 100644
index 0000000000000000000000000000000000000000..e4ef1f83e5dc162b72567f76f3d739f0f76acb91
--- /dev/null
+++ b/_testing/unittests/inc/parser/parser_lists.test.php
@@ -0,0 +1,393 @@
+<?php
+require_once 'parser.inc.php';
+
+class TestOfDoku_Parser_Lists extends TestOfDoku_Parser {
+
+    function testUnorderedList() {
+        $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock());
+        $this->P->parse('
+  *A
+    * B
+  * C
+');
+        $calls = array (
+            array('document_start',array()),
+            array('listu_open',array()),
+            array('listitem_open',array(1)),
+            array('listcontent_open',array()),
+            array('cdata',array("A")),
+            array('listcontent_close',array()),
+            array('listu_open',array()),
+            array('listitem_open',array(2)),
+            array('listcontent_open',array()),
+            array('cdata',array(' B')),
+            array('listcontent_close',array()),
+            array('listitem_close',array()),
+            array('listu_close',array()),
+            array('listitem_close',array()),
+            array('listitem_open',array(1)),
+            array('listcontent_open',array()),
+            array('cdata',array(' C')),
+            array('listcontent_close',array()),
+            array('listitem_close',array()),
+            array('listu_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testOrderedList() {
+        $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock());
+        $this->P->parse('
+  -A
+    - B
+  - C
+');
+        $calls = array (
+            array('document_start',array()),
+            array('listo_open',array()),
+            array('listitem_open',array(1)),
+            array('listcontent_open',array()),
+            array('cdata',array("A")),
+            array('listcontent_close',array()),
+            array('listo_open',array()),
+            array('listitem_open',array(2)),
+            array('listcontent_open',array()),
+            array('cdata',array(' B')),
+            array('listcontent_close',array()),
+            array('listitem_close',array()),
+            array('listo_close',array()),
+            array('listitem_close',array()),
+            array('listitem_open',array(1)),
+            array('listcontent_open',array()),
+            array('cdata',array(' C')),
+            array('listcontent_close',array()),
+            array('listitem_close',array()),
+            array('listo_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+
+    function testMixedList() {
+        $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock());
+        $this->P->parse('
+  -A
+    * B
+  - C
+');
+        $calls = array (
+            array('document_start',array()),
+            array('listo_open',array()),
+            array('listitem_open',array(1)),
+            array('listcontent_open',array()),
+            array('cdata',array("A")),
+            array('listcontent_close',array()),
+            array('listu_open',array()),
+            array('listitem_open',array(2)),
+            array('listcontent_open',array()),
+            array('cdata',array(' B')),
+            array('listcontent_close',array()),
+            array('listitem_close',array()),
+            array('listu_close',array()),
+            array('listitem_close',array()),
+            array('listitem_open',array(1)),
+            array('listcontent_open',array()),
+            array('cdata',array(' C')),
+            array('listcontent_close',array()),
+            array('listitem_close',array()),
+            array('listo_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+    
+    function testUnorderedListWinEOL() {
+        $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock());
+        $this->P->parse("\r\n  *A\r\n    * B\r\n  * C\r\n");
+        $calls = array (
+            array('document_start',array()),
+            array('listu_open',array()),
+            array('listitem_open',array(1)),
+            array('listcontent_open',array()),
+            array('cdata',array("A")),
+            array('listcontent_close',array()),
+            array('listu_open',array()),
+            array('listitem_open',array(2)),
+            array('listcontent_open',array()),
+            array('cdata',array(' B')),
+            array('listcontent_close',array()),
+            array('listitem_close',array()),
+            array('listu_close',array()),
+            array('listitem_close',array()),
+            array('listitem_open',array(1)),
+            array('listcontent_open',array()),
+            array('cdata',array(' C')),
+            array('listcontent_close',array()),
+            array('listitem_close',array()),
+            array('listu_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+    
+    function testOrderedListWinEOL() {
+        $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock());
+        $this->P->parse("\r\n  -A\r\n    - B\r\n  - C\r\n");
+        $calls = array (
+            array('document_start',array()),
+            array('listo_open',array()),
+            array('listitem_open',array(1)),
+            array('listcontent_open',array()),
+            array('cdata',array("A")),
+            array('listcontent_close',array()),
+            array('listo_open',array()),
+            array('listitem_open',array(2)),
+            array('listcontent_open',array()),
+            array('cdata',array(' B')),
+            array('listcontent_close',array()),
+            array('listitem_close',array()),
+            array('listo_close',array()),
+            array('listitem_close',array()),
+            array('listitem_open',array(1)),
+            array('listcontent_open',array()),
+            array('cdata',array(' C')),
+            array('listcontent_close',array()),
+            array('listitem_close',array()),
+            array('listo_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+    
+    function testNotAList() {
+        $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock());
+        $this->P->parse("Foo  -bar  *foo Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nFoo  -bar  *foo Bar")),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+    
+    function testUnorderedListParagraph() {
+        $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock());
+        $this->P->addMode('eol',new Doku_Parser_Mode_Eol());
+        $this->P->parse('Foo
+  *A
+    * B
+  * C
+Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("Foo")),
+            array('p_close',array()),
+            array('listu_open',array()),
+            array('listitem_open',array(1)),
+            array('listcontent_open',array()),
+            array('cdata',array("A")),
+            array('listcontent_close',array()),
+            array('listu_open',array()),
+            array('listitem_open',array(2)),
+            array('listcontent_open',array()),
+            array('cdata',array(' B')),
+            array('listcontent_close',array()),
+            array('listitem_close',array()),
+            array('listu_close',array()),
+            array('listitem_close',array()),
+            array('listitem_open',array(1)),
+            array('listcontent_open',array()),
+            array('cdata',array(' C')),
+            array('listcontent_close',array()),
+            array('listitem_close',array()),
+            array('listu_close',array()),
+            array('p_open',array()),
+            array('cdata',array("Bar")),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+    
+    // This is really a failing test - formatting able to spread across list items
+    // Problem is fixing it would mean a major rewrite of lists
+    function testUnorderedListStrong() {
+        $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock());
+        $this->P->addMode('strong',new Doku_Parser_Mode_Formatting('strong'));
+        $this->P->parse('
+  ***A**
+    *** B
+  * C**
+');
+        $calls = array (
+            array('document_start',array()),
+            array('listu_open',array()),
+            array('listitem_open',array(1)),
+            array('listcontent_open',array()),
+            array('strong_open',array()),
+            array('cdata',array("A")),
+            array('strong_close',array()),
+            array('listcontent_close',array()),
+            array('listu_open',array()),
+            array('listitem_open',array(2)),
+            array('listcontent_open',array()),
+            array('strong_open',array()),
+            array('cdata',array(" B\n  * C")),
+            array('strong_close',array()),
+            array('listcontent_close',array()),
+            array('listitem_close',array()),
+            array('listu_close',array()),
+            array('listitem_close',array()),
+            array('listu_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+    
+    // This is really a failing test - unformatted able to spread across list items
+    // Problem is fixing it would mean a major rewrite of lists
+    function testUnorderedListUnformatted() {
+        $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock());
+        $this->P->addMode('unformatted',new Doku_Parser_Mode_Unformatted());
+        $this->P->parse('
+  *%%A%%
+    *%% B
+  * C%%
+');
+        $calls = array (
+            array('document_start',array()),
+            array('listu_open',array()),
+            array('listitem_open',array(1)),
+            array('listcontent_open',array()),
+            array('unformatted',array("A")),
+            array('listcontent_close',array()),
+            array('listu_open',array()),
+            array('listitem_open',array(2)),
+            array('listcontent_open',array()),
+            array('unformatted',array(" B\n  * C")),
+            array('listcontent_close',array()),
+            array('listitem_close',array()),
+            array('listu_close',array()),
+            array('listitem_close',array()),
+            array('listu_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+    
+    function testUnorderedListLinebreak() {
+        $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock());
+        $this->P->addMode('linebreak',new Doku_Parser_Mode_Linebreak());
+        $this->P->parse('
+  *A\\\\ D
+    * B
+  * C
+');
+        $calls = array (
+            array('document_start',array()),
+            array('listu_open',array()),
+            array('listitem_open',array(1)),
+            array('listcontent_open',array()),
+            array('cdata',array("A")),
+            array('linebreak',array()),
+            array('cdata',array("D")),
+            array('listcontent_close',array()),
+            array('listu_open',array()),
+            array('listitem_open',array(2)),
+            array('listcontent_open',array()),
+            array('cdata',array(' B')),
+            array('listcontent_close',array()),
+            array('listitem_close',array()),
+            array('listu_close',array()),
+            array('listitem_close',array()),
+            array('listitem_open',array(1)),
+            array('listcontent_open',array()),
+            array('cdata',array(' C')),
+            array('listcontent_close',array()),
+            array('listitem_close',array()),
+            array('listu_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+    
+    function testUnorderedListLinebreak2() {
+        $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock());
+        $this->P->addMode('linebreak',new Doku_Parser_Mode_Linebreak());
+        $this->P->parse('
+  *A\\\\
+  * B
+');
+        $calls = array (
+            array('document_start',array()),
+            array('listu_open',array()),
+            array('listitem_open',array(1)),
+            array('listcontent_open',array()),
+            array('cdata',array("A")),
+            array('linebreak',array()),
+            array('listcontent_close',array()),
+            array('listitem_close',array()),
+            array('listitem_open',array(1)),
+            array('listcontent_open',array()),
+            array('cdata',array(' B')),
+            array('listcontent_close',array()),
+            array('listitem_close',array()),
+            array('listu_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+    
+    function testUnorderedListFootnote() {
+        $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock());
+        $this->P->addMode('footnote',new Doku_Parser_Mode_Footnote());
+        $this->P->parse('
+  *((A))
+    *(( B
+  * C )) 
+
+');
+        $calls = array (
+            array('document_start',array()),
+            array('listu_open',array()),
+            array('listitem_open',array(1)),
+            array('listcontent_open',array()),
+            array('nest', array( array(
+                array('footnote_open',array()),
+                array('cdata',array("A")),
+                array('footnote_close',array())
+            ))),
+            array('listcontent_close',array()),
+            array('listu_open',array()),
+            array('listitem_open',array(2)),
+            array('listcontent_open',array()),
+            array('nest', array( array(
+                array('footnote_open',array()),
+                array('cdata',array(" B")),
+                array('listu_open',array()),
+                array('listitem_open',array(1)),
+                array('listcontent_open',array()),
+                array('cdata',array(" C )) ")),
+                array('listcontent_close',array()),
+                array('listitem_close',array()),
+                array('listu_close',array()),
+                array('cdata',array("\n\n")),
+                array('footnote_close',array())
+            ))),
+            array('listcontent_close',array()),
+            array('listitem_close',array()),
+            array('listu_close',array()),
+            array('listitem_close',array()),
+            array('listu_close',array()),
+            array('document_end',array())
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+}
+
diff --git a/_testing/unittests/inc/parser/parser_preformatted.test.php b/_testing/unittests/inc/parser/parser_preformatted.test.php
new file mode 100644
index 0000000000000000000000000000000000000000..f7a01a7e580263f635f8b19aee36eb8cbebf844f
--- /dev/null
+++ b/_testing/unittests/inc/parser/parser_preformatted.test.php
@@ -0,0 +1,231 @@
+<?php
+require_once 'parser.inc.php';
+
+class TestOfDoku_Parser_Preformatted extends TestOfDoku_Parser {
+
+    function testFile() {
+        $this->P->addMode('file',new Doku_Parser_Mode_File());
+        $this->P->parse('Foo <file>testing</file> Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('p_close',array()),
+            array('file',array('testing',null,null)),
+            array('p_open',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testCode() {
+        $this->P->addMode('code',new Doku_Parser_Mode_Code());
+        $this->P->parse('Foo <code>testing</code> Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('p_close',array()),
+            array('code',array('testing', null, null)),
+            array('p_open',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testCodeWhitespace() {
+        $this->P->addMode('code',new Doku_Parser_Mode_Code());
+        $this->P->parse("Foo <code \n>testing</code> Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('p_close',array()),
+            array('code',array('testing', null, null)),
+            array('p_open',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testCodeLang() {
+        $this->P->addMode('code',new Doku_Parser_Mode_Code());
+        $this->P->parse("Foo <code php>testing</code> Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('p_close',array()),
+            array('code',array('testing', 'php', null)),
+            array('p_open',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testPreformatted() {
+        $this->P->addMode('preformatted',new Doku_Parser_Mode_Preformatted());
+        $this->P->parse("F  oo\n  x  \n    y  \nBar\n");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nF  oo")),
+            array('p_close',array()),
+            array('preformatted',array("x  \n  y  ")),
+            array('p_open',array()),
+            array('cdata',array('Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testPreformattedWinEOL() {
+        $this->P->addMode('preformatted',new Doku_Parser_Mode_Preformatted());
+        $this->P->parse("F  oo\r\n  x  \r\n    y  \r\nBar\r\n");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nF  oo")),
+            array('p_close',array()),
+            array('preformatted',array("x  \n  y  ")),
+            array('p_open',array()),
+            array('cdata',array('Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testPreformattedTab() {
+        $this->P->addMode('preformatted',new Doku_Parser_Mode_Preformatted());
+        $this->P->parse("F  oo\n\tx\t\n\t\ty\t\nBar\n");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nF  oo")),
+            array('p_close',array()),
+            array('preformatted',array("x\t\n\ty\t")),
+            array('p_open',array()),
+            array('cdata',array("Bar")),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testPreformattedTabWinEOL() {
+        $this->P->addMode('preformatted',new Doku_Parser_Mode_Preformatted());
+        $this->P->parse("F  oo\r\n\tx\t\r\n\t\ty\t\r\nBar\r\n");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nF  oo")),
+            array('p_close',array()),
+            array('preformatted',array("x\t\n\ty\t")),
+            array('p_open',array()),
+            array('cdata',array("Bar")),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testPreformattedList() {
+        $this->P->addMode('preformatted',new Doku_Parser_Mode_Preformatted());
+        $this->P->addMode('listblock',new Doku_Parser_Mode_ListBlock());
+        $this->P->parse("  - x \n  * y \nF  oo\n  x  \n    y  \n  -X\n  *Y\nBar\n");
+        $calls = array (
+            array('document_start',array()),
+            array('listo_open',array()),
+            array('listitem_open',array(1)),
+            array('listcontent_open',array()),
+            array('cdata',array(" x ")),
+            array('listcontent_close',array()),
+            array('listitem_close',array()),
+            array('listo_close',array()),
+            array('listu_open',array()),
+            array('listitem_open',array(1)),
+            array('listcontent_open',array()),
+            array('cdata',array(" y ")),
+            array('listcontent_close',array()),
+            array('listitem_close',array()),
+            array('listu_close',array()),
+            array('p_open',array()),
+            array('cdata',array("F  oo")),
+            array('p_close',array()),
+            array('preformatted',array("x  \n  y  \n-X\n*Y")),
+            array('p_open',array()),
+            array('cdata',array("Bar")),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    // test for php
+    function testPHP() {
+
+        $this->P->addMode('php',new Doku_Parser_Mode_PHP());
+        $this->P->parse('Foo <php>testing</php> Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('php',array('testing')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    // test with for HTML
+    function testHTML() {
+
+        $this->P->addMode('html',new Doku_Parser_Mode_HTML());
+        $this->P->parse('Foo <html>testing</html> Bar');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('html',array('testing')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+
+
+    function testPreformattedPlusHeaderAndEol() {
+        // Note that EOL must come after preformatted!
+        $this->P->addMode('preformatted',new Doku_Parser_Mode_Preformatted());
+        $this->P->addMode('header',new Doku_Parser_Mode_Header());
+        $this->P->addMode('eol',new Doku_Parser_Mode_Eol());
+        $this->P->parse("F  oo\n  ==Test==\n    y  \nBar\n");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("F  oo")),
+            array('p_close',array()),
+            array('preformatted',array("==Test==\n  y  ")),
+            array('p_open',array()),
+            array('cdata',array('Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+}
+
diff --git a/_testing/unittests/inc/parser/parser_quote.test.php b/_testing/unittests/inc/parser/parser_quote.test.php
new file mode 100644
index 0000000000000000000000000000000000000000..4d77f2a39e8c6a2c1548d920fe00ef6f60b7a42d
--- /dev/null
+++ b/_testing/unittests/inc/parser/parser_quote.test.php
@@ -0,0 +1,94 @@
+<?php
+require_once 'parser.inc.php';
+
+class TestOfDoku_Parser_Quote extends TestOfDoku_Parser {
+
+    function testQuote() {
+        $this->P->addMode('quote',new Doku_Parser_Mode_Quote());
+        $this->P->parse("abc\n> def\n>>ghi\nklm");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nabc")),
+            array('p_close',array()),
+            array('quote_open',array()),
+            array('cdata',array(" def")),
+            array('quote_open',array()),
+            array('cdata',array("ghi")),
+            array('quote_close',array()),
+            array('quote_close',array()),
+            array('p_open',array()),
+            array('cdata',array("klm")),
+            array('p_close',array()),
+            array('document_end',array()),
+            
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+    
+    function testQuoteWinCr() {
+        $this->P->addMode('quote',new Doku_Parser_Mode_Quote());
+        $this->P->parse("abc\r\n> def\r\n>>ghi\r\nklm");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nabc")),
+            array('p_close',array()),
+            array('quote_open',array()),
+            array('cdata',array(" def")),
+            array('quote_open',array()),
+            array('cdata',array("ghi")),
+            array('quote_close',array()),
+            array('quote_close',array()),
+            array('p_open',array()),
+            array('cdata',array("klm")),
+            array('p_close',array()),
+            array('document_end',array()),
+            
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+    
+    function testQuoteMinumumContext() {
+        $this->P->addMode('quote',new Doku_Parser_Mode_Quote());
+        $this->P->parse("\n> def\n>>ghi\n ");
+        $calls = array (
+            array('document_start',array()),
+            array('quote_open',array()),
+            array('cdata',array(" def")),
+            array('quote_open',array()),
+            array('cdata',array("ghi")),
+            array('quote_close',array()),
+            array('quote_close',array()),
+            array('document_end',array()),
+            
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+    
+    function testQuoteEol() {
+        $this->P->addMode('quote',new Doku_Parser_Mode_Quote());
+        $this->P->addMode('eol',new Doku_Parser_Mode_Eol());
+        $this->P->parse("abc\n> def\n>>ghi\nklm");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("abc")),
+            array('p_close',array()),
+            array('quote_open',array()),
+            array('cdata',array(" def")),
+            array('quote_open',array()),
+            array('cdata',array("ghi")),
+            array('quote_close',array()),
+            array('quote_close',array()),
+            array('p_open',array()),
+            array('cdata',array("klm")),
+            array('p_close',array()),
+            array('document_end',array()),
+            
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+}
+
diff --git a/_testing/unittests/inc/parser/parser_quotes.test.php b/_testing/unittests/inc/parser/parser_quotes.test.php
new file mode 100644
index 0000000000000000000000000000000000000000..b2dae1039a68eb8b4ae771a0b7ac51f1afc36868
--- /dev/null
+++ b/_testing/unittests/inc/parser/parser_quotes.test.php
@@ -0,0 +1,269 @@
+<?php
+require_once 'parser.inc.php';
+
+class TestOfDoku_Parser_Quotes extends TestOfDoku_Parser {
+
+    function setup() {
+        parent::setup();
+        global $conf;
+        $conf['typography'] = 2;
+    }
+
+    function testSingleQuoteOpening() {
+        $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes());
+        $this->P->parse("Foo 'hello Bar");
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('singlequoteopening',array()),
+            array('cdata',array('hello Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testSingleQuoteOpeningSpecial() {
+        $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes());
+        $this->P->parse("Foo said:'hello Bar");
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo said:')),
+            array('singlequoteopening',array()),
+            array('cdata',array('hello Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testSingleQuoteClosing() {
+        $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes());
+        $this->P->parse("Foo hello' Bar");
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo hello')),
+            array('singlequoteclosing',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testSingleQuoteClosingSpecial() {
+        $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes());
+        $this->P->parse("Foo hello') Bar");
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo hello')),
+            array('singlequoteclosing',array()),
+            array('cdata',array(') Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testSingleQuotes() {
+        $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes());
+        $this->P->parse("Foo 'hello' Bar");
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('singlequoteopening',array()),
+            array('cdata',array('hello')),
+            array('singlequoteclosing',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testApostrophe() {
+        $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes());
+        $this->P->parse("hey it's fine weather today");
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'hey it')),
+            array('apostrophe',array()),
+            array('cdata',array('s fine weather today')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+
+    function testSingleQuotesSpecial() {
+        $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes());
+        $this->P->parse("Foo ('hello') Bar");
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo (')),
+            array('singlequoteopening',array()),
+            array('cdata',array('hello')),
+            array('singlequoteclosing',array()),
+            array('cdata',array(') Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testDoubleQuoteOpening() {
+        $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes());
+        $this->P->parse('Foo "hello Bar');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('doublequoteopening',array()),
+            array('cdata',array('hello Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testDoubleQuoteOpeningSpecial() {
+        $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes());
+        $this->P->parse('Foo said:"hello Bar');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo said:')),
+            array('doublequoteopening',array()),
+            array('cdata',array('hello Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testDoubleQuoteClosing() {
+        $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes());
+        $this->P->parse('Foo hello" Bar');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo hello')),
+            array('doublequoteclosing',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testDoubleQuoteClosingSpecial() {
+        $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes());
+        $this->P->parse('Foo hello") Bar');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo hello')),
+            array('doublequoteclosing',array()),
+            array('cdata',array(') Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testDoubleQuotes() {
+        $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes());
+        $this->P->parse('Foo "hello" Bar');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('doublequoteopening',array()),
+            array('cdata',array('hello')),
+            array('doublequoteclosing',array()),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testDoubleQuotesSpecial() {
+        $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes());
+        $this->P->parse('Foo ("hello") Bar');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo (')),
+            array('doublequoteopening',array()),
+            array('cdata',array('hello')),
+            array('doublequoteclosing',array()),
+            array('cdata',array(') Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testAllQuotes() {
+        $this->P->addMode('quotes',new Doku_Parser_Mode_Quotes());
+        $this->P->parse('There was written "He thought \'It\'s a man\'s world\'".');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'There was written ')),
+            array('doublequoteopening',array()),
+            array('cdata',array('He thought ')),
+            array('singlequoteopening',array()),
+            array('cdata',array('It')),
+            array('apostrophe',array()),
+            array('cdata',array('s a man')),
+            array('apostrophe',array()),
+            array('cdata',array('s world')),
+            array('singlequoteclosing',array()),
+            array('doublequoteclosing',array()),
+            array('cdata',array(".")),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+}
+
diff --git a/_testing/unittests/inc/parser/parser_replacements.test.php b/_testing/unittests/inc/parser/parser_replacements.test.php
new file mode 100644
index 0000000000000000000000000000000000000000..f0367dac05737f949af683b93378c7465fc3436e
--- /dev/null
+++ b/_testing/unittests/inc/parser/parser_replacements.test.php
@@ -0,0 +1,379 @@
+<?php
+require_once 'parser.inc.php';
+
+class TestOfDoku_Parser_Replacements extends TestOfDoku_Parser {
+
+    function testSingleAcronym() {
+        $this->P->addMode('acronym',new Doku_Parser_Mode_Acronym(array('FOOBAR')));
+        $this->P->parse('abc FOOBAR xyz');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'abc ')),
+            array('acronym',array('FOOBAR')),
+            array('cdata',array(' xyz')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testAlmostAnAcronym() {
+        $this->P->addMode('acronym',new Doku_Parser_Mode_Acronym(array('FOOBAR')));
+        $this->P->parse('abcFOOBARxyz');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'abcFOOBARxyz')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testPickAcronymCorrectly() {
+        $this->P->addMode('acronym',new Doku_Parser_Mode_Acronym(array('FOO')));
+        $this->P->parse('FOOBAR FOO');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'FOOBAR ')),
+            array('acronym',array('FOO')),
+            array('cdata',array('')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testMultipleAcronyms() {
+        $this->P->addMode('acronym',new Doku_Parser_Mode_Acronym(array('FOO','BAR')));
+        $this->P->parse('abc FOO def BAR xyz');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'abc ')),
+            array('acronym',array('FOO')),
+            array('cdata',array(' def ')),
+            array('acronym',array('BAR')),
+            array('cdata',array(' xyz')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+
+    }
+
+    function testMultipleAcronymsWithSubset1() {
+        $this->P->addMode('acronym',new Doku_Parser_Mode_Acronym(array('FOO','A.FOO','FOO.1','A.FOO.1')));
+        $this->P->parse('FOO A.FOO FOO.1 A.FOO.1');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n")),
+            array('acronym',array('FOO')),
+            array('cdata',array(" ")),
+            array('acronym',array('A.FOO')),
+            array('cdata',array(" ")),
+            array('acronym',array('FOO.1')),
+            array('cdata',array(" ")),
+            array('acronym',array('A.FOO.1')),
+            array('cdata',array('')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testMultipleAcronymsWithSubset2() {
+        $this->P->addMode('acronym',new Doku_Parser_Mode_Acronym(array('A.FOO.1','FOO.1','A.FOO','FOO')));
+        $this->P->parse('FOO A.FOO FOO.1 A.FOO.1');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n")),
+            array('acronym',array('FOO')),
+            array('cdata',array(" ")),
+            array('acronym',array('A.FOO')),
+            array('cdata',array(" ")),
+            array('acronym',array('FOO.1')),
+            array('cdata',array(" ")),
+            array('acronym',array('A.FOO.1')),
+            array('cdata',array('')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testSingleSmileyFail() {
+        $this->P->addMode('smiley',new Doku_Parser_Mode_Smiley(array(':-)')));
+        $this->P->parse('abc:-)xyz');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nabc:-)xyz")),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testSingleSmiley() {
+        $this->P->addMode('smiley',new Doku_Parser_Mode_Smiley(array(':-)')));
+        $this->P->parse('abc :-) xyz');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'abc ')),
+            array('smiley',array(':-)')),
+            array('cdata',array(' xyz')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testMultipleSmileysFail() {
+        $this->P->addMode('smiley',new Doku_Parser_Mode_Smiley(array(':-)','^_^')));
+        $this->P->parse('abc:-)x^_^yz');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nabc:-)x^_^yz")),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testMultipleSmileys() {
+        $this->P->addMode('smiley',new Doku_Parser_Mode_Smiley(array(':-)','^_^')));
+        $this->P->parse('abc :-) x ^_^ yz');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'abc ')),
+            array('smiley',array(':-)')),
+            array('cdata',array(' x ')),
+            array('smiley',array('^_^')),
+            array('cdata',array(' yz')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testBackslashSmileyFail() {
+        // This smiley is really :-\\ but escaping makes like interesting
+        $this->P->addMode('smiley',new Doku_Parser_Mode_Smiley(array(':-\\\\')));
+        $this->P->parse('abc:-\\\xyz');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\nabc".':-\\\\'."xyz")),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testBackslashSmiley() {
+        // This smiley is really :-\\ but escaping makes like interesting
+        $this->P->addMode('smiley',new Doku_Parser_Mode_Smiley(array(':-\\\\')));
+        $this->P->parse('abc :-\\\ xyz');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'abc ')),
+            array('smiley',array(':-\\\\')),
+            array('cdata',array(' xyz')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testSingleWordblock() {
+        $this->P->addMode('wordblock',new Doku_Parser_Mode_Wordblock(array('CAT')));
+        $this->P->parse('abc CAT xyz');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'abc ')),
+            array('wordblock',array('CAT')),
+            array('cdata',array(' xyz')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testWordblockCase() {
+        $this->P->addMode('wordblock',new Doku_Parser_Mode_Wordblock(array('CAT')));
+        $this->P->parse('abc cat xyz');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'abc ')),
+            array('wordblock',array('cat')),
+            array('cdata',array(' xyz')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testMultipleWordblock() {
+        $this->P->addMode('wordblock',new Doku_Parser_Mode_Wordblock(array('CAT','dog')));
+        $this->P->parse('abc cat x DOG yz');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'abc ')),
+            array('wordblock',array('cat')),
+            array('cdata',array(' x ')),
+            array('wordblock',array('DOG')),
+            array('cdata',array(' yz')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testSingleEntity() {
+        $this->P->addMode('entity',new Doku_Parser_Mode_Entity(array('->')));
+        $this->P->parse('x -> y');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'x ')),
+            array('entity',array('->')),
+            array('cdata',array(' y')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testMultipleEntities() {
+        $this->P->addMode('entity',new Doku_Parser_Mode_Entity(array('->','<-')));
+        $this->P->parse('x -> y <- z');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'x ')),
+            array('entity',array('->')),
+            array('cdata',array(' y ')),
+            array('entity',array('<-')),
+            array('cdata',array(' z')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testMultiplyEntity() {
+        $this->P->addMode('multiplyentity',new Doku_Parser_Mode_MultiplyEntity());
+        $this->P->parse('Foo 10x20 Bar');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('multiplyentity',array(10,20)),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testMultiplyEntityHex() {
+    	// the multiply entity pattern should not match hex numbers, eg. 0x123
+        $this->P->addMode('multiplyentity',new Doku_Parser_Mode_MultiplyEntity());
+        $this->P->parse('Foo 0x123 Bar');
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo 0x123 Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testHR() {
+        $this->P->addMode('hr',new Doku_Parser_Mode_HR());
+        $this->P->parse("Foo \n ---- \n Bar");
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('p_close',array()),
+            array('hr',array()),
+            array('p_open',array()),
+            array('cdata',array("\n Bar")),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testHREol() {
+        $this->P->addMode('hr',new Doku_Parser_Mode_HR());
+        $this->P->parse("Foo \n----\n Bar");
+
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('p_close',array()),
+            array('hr',array()),
+            array('p_open',array()),
+            array('cdata',array("\n Bar")),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+}
+
diff --git a/_testing/unittests/inc/parser/parser_table.test.php b/_testing/unittests/inc/parser/parser_table.test.php
new file mode 100644
index 0000000000000000000000000000000000000000..96789c38c9c8b06ce44b9b27ef2061c2dc2a86ff
--- /dev/null
+++ b/_testing/unittests/inc/parser/parser_table.test.php
@@ -0,0 +1,572 @@
+<?php
+require_once 'parser.inc.php';
+
+class TestOfDoku_Parser_Table extends TestOfDoku_Parser {
+
+    function testTable() {
+        $this->P->addMode('table',new Doku_Parser_Mode_Table());
+        $this->P->parse('
+abc
+| Row 0 Col 1    | Row 0 Col 2     | Row 0 Col 3        |
+| Row 1 Col 1    | Row 1 Col 2     | Row 1 Col 3        |
+def');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n\nabc")),
+            array('p_close',array()),
+            array('table_open',array(3, 2, 6)),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 0 Col 1    ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 0 Col 2     ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 0 Col 3        ')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 1    ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 2     ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 3        ')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('table_close',array(121)),
+            array('p_open',array()),
+            array('cdata',array('def')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testTableWinEOL() {
+        $this->P->addMode('table',new Doku_Parser_Mode_Table());
+        $this->P->parse("\r\nabc\r\n| Row 0 Col 1    | Row 0 Col 2     | Row 0 Col 3        |\r\n| Row 1 Col 1    | Row 1 Col 2     | Row 1 Col 3        |\r\ndef");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n\nabc")),
+            array('p_close',array()),
+            array('table_open',array(3, 2, 6)),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 0 Col 1    ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 0 Col 2     ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 0 Col 3        ')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 1    ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 2     ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 3        ')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('table_close',array(121)),
+            array('p_open',array()),
+            array('cdata',array('def')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+
+    function testEmptyTable() {
+        $this->P->addMode('table',new Doku_Parser_Mode_Table());
+        $this->P->parse('
+abc
+|
+def');
+        
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n\nabc")),
+            array('p_close',array()),
+            array('table_open',array(0, 1, 6)),
+            array('tablerow_open',array()),
+            array('tablerow_close',array()),
+            array('table_close',array(7)),
+            array('p_open',array()),
+            array('cdata',array('def')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+    
+    function testTableHeaders() {
+        $this->P->addMode('table',new Doku_Parser_Mode_Table());
+        $this->P->parse('
+abc
+^ X | Y ^ Z |
+def');
+    
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n\nabc")),
+            array('p_close',array()),
+            array('table_open',array(3, 1, 6)),
+            array('tablerow_open',array()),
+            array('tableheader_open',array(1,NULL,1)),
+            array('cdata',array(' X ')),
+            array('tableheader_close',array()),
+            array('tablecell_open',array(1,NULL,1)),
+            array('cdata',array(' Y ')),
+            array('tablecell_close',array()),
+            array('tableheader_open',array(1,NULL,1)),
+            array('cdata',array(' Z ')),
+            array('tableheader_close',array()),
+            array('tablerow_close',array()),
+            array('table_close',array(19)),
+            array('p_open',array()),
+            array('cdata',array('def')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+
+    }
+    
+    function testCellAlignment() {
+        $this->P->addMode('table',new Doku_Parser_Mode_Table());
+        $this->P->parse('
+abc
+|  X | Y  ^  Z  |
+def');
+    
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n\nabc")),
+            array('p_close',array()),
+            array('table_open',array(3, 1, 6)),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,'right',1)),
+            array('cdata',array('  X ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Y  ')),
+            array('tablecell_close',array()),
+            array('tableheader_open',array(1,'center',1)),
+            array('cdata',array('  Z  ')),
+            array('tableheader_close',array()),
+            array('tablerow_close',array()),
+            array('table_close',array(23)),
+            array('p_open',array()),
+            array('cdata',array('def')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+    
+    function testCellSpan() {
+        $this->P->addMode('table',new Doku_Parser_Mode_Table());
+        $this->P->parse('
+abc
+|  d || e |
+| f ^ ^|
+||||
+def');
+        
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n\nabc")),
+            array('p_close',array()),
+            array('table_open',array(3, 3, 6)),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(2,'right',1)),
+            array('cdata',array('  d ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,NULL,1)),
+            array('cdata',array(' e ')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,NULL,1)),
+            array('cdata',array(' f ')),
+            array('tablecell_close',array()),
+            array('tableheader_open',array(2,NULL,1)),
+            array('cdata',array(' ')),
+            array('tableheader_close',array()),
+            array('tablerow_close',array()),
+            array('tablerow_open',array()),
+            array('tablerow_close',array()),
+            array('table_close',array(31)),
+            array('p_open',array()),
+            array('cdata',array('def')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+    
+    function testCellRowSpan() {
+        $this->P->addMode('table',new Doku_Parser_Mode_Table());
+        $this->P->parse('
+abc
+| a |  c:::||
+|:::^ d  | e|
+|b  ^  ::: |:::f|
+def');
+        
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n\nabc")),
+            array('p_close',array()),
+            array('table_open',array(3, 3, 6)),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,NULL,2)),
+            array('cdata',array(' a ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(2,'right',1)),
+            array('cdata',array('  c:::')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('tablerow_open',array()),
+            array('tableheader_open',array(1,'left',2)),
+            array('cdata',array(' d  ')),
+            array('tableheader_close',array()),
+            array('tablecell_open',array(1,NULL,1)),
+            array('cdata',array(' e')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array('b  ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,NULL,1)),
+            array('cdata',array(':::f')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('table_close',array(51)),
+            array('p_open',array()),
+            array('cdata',array('def')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+    
+    function testCellAlignmentFormatting() {
+        $this->P->addMode('table',new Doku_Parser_Mode_Table());
+        $this->P->addMode('strong',new Doku_Parser_Mode_Formatting('strong'));
+        $this->P->parse('
+abc
+|  **X** | Y  ^  Z  |
+def');
+    
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n\nabc")),
+            array('p_close',array()),
+            array('table_open',array(3, 1, 6)),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,'right',1)),
+            array('cdata',array('  ')),
+            array('strong_open',array()),
+            array('cdata',array('X')),
+            array('strong_close',array()),
+            array('cdata',array(' ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Y  ')),
+            array('tablecell_close',array()),
+            array('tableheader_open',array(1,'center',1)),
+            array('cdata',array('  Z  ')),
+            array('tableheader_close',array()),
+            array('tablerow_close',array()),
+            array('table_close',array(27)),
+            array('p_open',array()),
+            array('cdata',array('def')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+ 
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+        
+    }
+    
+    function testTableEol() {
+        $this->P->addMode('table',new Doku_Parser_Mode_Table());
+        $this->P->addMode('eol',new Doku_Parser_Mode_Eol());
+        $this->P->parse('
+abc
+| Row 0 Col 1    | Row 0 Col 2     | Row 0 Col 3        |
+| Row 1 Col 1    | Row 1 Col 2     | Row 1 Col 3        |
+def');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("abc")),
+            array('p_close',array()),
+            array('table_open',array(3, 2, 6)),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 0 Col 1    ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 0 Col 2     ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 0 Col 3        ')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 1    ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 2     ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 3        ')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('table_close',array(121)),
+            array('p_open',array()),
+            array('cdata',array('def')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+    
+    // This is really a failing test - formatting able to spread across cols
+    // Problem is fixing it would mean a major rewrite of table handling
+    function testTableStrong() {
+        $this->P->addMode('table',new Doku_Parser_Mode_Table());
+        $this->P->addMode('strong',new Doku_Parser_Mode_Formatting('strong'));
+        $this->P->parse('
+abc
+| **Row 0 Col 1**    | **Row 0 Col 2     | Row 0 Col 3**        |
+| Row 1 Col 1    | Row 1 Col 2     | Row 1 Col 3        |
+def');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n\nabc")),
+            array('p_close',array()),
+            array('table_open',array(3, 2, 6)),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' ')),
+            array('strong_open',array()),
+            array('cdata',array('Row 0 Col 1')),
+            array('strong_close',array()),
+            array('cdata',array('    ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' ')),
+            array('strong_open',array()),
+            array('cdata',array('Row 0 Col 2     | Row 0 Col 3')),
+            array('strong_close',array()),
+            array('cdata',array('        ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,null,1)),
+            array('cdata',array('')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 1    ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 2     ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 3        ')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('table_close',array(129)),
+            array('p_open',array()),
+            array('cdata',array('def')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+    
+    // This is really a failing test - unformatted able to spread across cols
+    // Problem is fixing it would mean a major rewrite of table handling
+    function testTableUnformatted() {
+        $this->P->addMode('table',new Doku_Parser_Mode_Table());
+        $this->P->addMode('unformatted',new Doku_Parser_Mode_Unformatted());
+        $this->P->parse('
+abc
+| <nowiki>Row 0 Col 1</nowiki>    | <nowiki>Row 0 Col 2     | Row 0 Col 3</nowiki>        |
+| Row 1 Col 1    | Row 1 Col 2     | Row 1 Col 3        |
+def');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n\nabc")),
+            array('p_close',array()),
+            array('table_open',array(3, 2, 6)),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' ')),
+            array('unformatted',array('Row 0 Col 1')),
+            array('cdata',array('    ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' ')),
+            array('unformatted',array('Row 0 Col 2     | Row 0 Col 3')),
+            array('cdata',array('        ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,null,1)),
+            array('cdata',array('')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 1    ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 2     ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 3        ')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('table_close',array(155)),
+            array('p_open',array()),
+            array('cdata',array('def')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+    
+    function testTableLinebreak() {
+        $this->P->addMode('table',new Doku_Parser_Mode_Table());
+        $this->P->addMode('linebreak',new Doku_Parser_Mode_Linebreak());
+        $this->P->parse('
+abc
+| Row 0\\\\ Col 1    | Row 0 Col 2     | Row 0 Col 3        |
+| Row 1 Col 1    | Row 1 Col 2     | Row 1 Col 3        |
+def');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n\nabc")),
+            array('p_close',array()),
+            array('table_open',array(3, 2, 6)),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 0')),
+            array('linebreak',array()),
+            array('cdata',array('Col 1    ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 0 Col 2     ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 0 Col 3        ')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 1    ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 2     ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 3        ')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('table_close',array(123)),
+            array('p_open',array()),
+            array('cdata',array('def')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+    
+    // This is really a failing test - footnote able to spread across cols
+    // Problem is fixing it would mean a major rewrite of table handling
+    function testTableFootnote() {
+        $this->P->addMode('table',new Doku_Parser_Mode_Table());
+        $this->P->addMode('footnote',new Doku_Parser_Mode_Footnote());
+        $this->P->parse('
+abc
+| ((Row 0 Col 1))    | ((Row 0 Col 2     | Row 0 Col 3))        |
+| Row 1 Col 1    | Row 1 Col 2     | Row 1 Col 3        |
+def');
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n\nabc")),
+            array('p_close',array()),
+            array('table_open',array(3, 2, 6)),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' ')),
+            array('nest', array ( array (
+              array('footnote_open',array()),
+              array('cdata',array('Row 0 Col 1')),
+              array('footnote_close',array()),
+            ))),
+            array('cdata',array('    ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' ')),
+            array('nest', array ( array (
+              array('footnote_open',array()),
+              array('cdata',array('Row 0 Col 2     | Row 0 Col 3')),
+              array('footnote_close',array()),
+            ))),
+            array('cdata',array('        ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,null,1)),
+            array('cdata',array('')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 1    ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 2     ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,'left',1)),
+            array('cdata',array(' Row 1 Col 3        ')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('table_close',array(129)),
+            array('p_open',array()),
+            array('cdata',array('def')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+    }
+}
+
diff --git a/_testing/unittests/inc/parser/parser_unformatted.test.php b/_testing/unittests/inc/parser/parser_unformatted.test.php
new file mode 100644
index 0000000000000000000000000000000000000000..7608cfd1cb6d248e0c1b622716d5d450bcfcf164
--- /dev/null
+++ b/_testing/unittests/inc/parser/parser_unformatted.test.php
@@ -0,0 +1,38 @@
+<?php
+require_once 'parser.inc.php';
+
+class TestOfDoku_Parser_Unformatted extends TestOfDoku_Parser {
+    
+    function testNowiki() {
+        $this->P->addMode('unformatted',new Doku_Parser_Mode_Unformatted());
+        $this->P->parse("Foo <nowiki>testing</nowiki> Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('unformatted',array('testing')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+        
+    }
+    
+    function testDoublePercent() {
+        $this->P->addMode('unformatted',new Doku_Parser_Mode_Unformatted());
+        $this->P->parse("Foo %%testing%% Bar");
+        $calls = array (
+            array('document_start',array()),
+            array('p_open',array()),
+            array('cdata',array("\n".'Foo ')),
+            array('unformatted',array('testing')),
+            array('cdata',array(' Bar')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+        $this->assertEquals(array_map('stripByteIndex',$this->H->calls),$calls);
+    }
+}
+