diff --git a/_test/tests/inc/parser/parser_table.test.php b/_test/tests/inc/parser/parser_table.test.php
index bc19ebff9ae9ced00576596cb30bea8dee6fa77f..a9b4e284c63f79c314bb5796802873a479334dce 100644
--- a/_test/tests/inc/parser/parser_table.test.php
+++ b/_test/tests/inc/parser/parser_table.test.php
@@ -44,7 +44,7 @@ def');
             array('p_close',array()),
             array('document_end',array()),
         );
-        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+        $this->assertEquals($calls,array_map('stripbyteindex',$this->H->calls));
     }
 
     function testTableWinEOL() {
@@ -84,7 +84,7 @@ def');
             array('p_close',array()),
             array('document_end',array()),
         );
-        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+        $this->assertEquals($calls,array_map('stripbyteindex',$this->H->calls));
     }
 
     function testEmptyTable() {
@@ -109,7 +109,7 @@ def');
             array('document_end',array()),
         );
 
-        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+        $this->assertEquals($calls,array_map('stripbyteindex',$this->H->calls));
     }
     
     function testTableHeaders() {
@@ -143,7 +143,152 @@ def');
             array('document_end',array()),
         );
 
-        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+        $this->assertEquals($calls,array_map('stripbyteindex',$this->H->calls));
+
+    }
+
+    function testTableHead() {
+        $this->P->addMode('table',new Doku_Parser_Mode_Table());
+        $this->P->parse('
+abc
+^ X ^ Y ^ Z ^
+| 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, 2, 6)),
+            array('tablethead_open',array()),
+            array('tablerow_open',array()),
+            array('tableheader_open',array(1,NULL,1)),
+            array('cdata',array(' X ')),
+            array('tableheader_close',array()),
+            array('tableheader_open',array(1,NULL,1)),
+            array('cdata',array(' Y ')),
+            array('tableheader_close',array()),
+            array('tableheader_open',array(1,NULL,1)),
+            array('cdata',array(' Z ')),
+            array('tableheader_close',array()),
+            array('tablerow_close',array()),
+            array('tablethead_close',array()),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,NULL,1)),
+            array('cdata',array(' x ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,NULL,1)),
+            array('cdata',array(' y ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,NULL,1)),
+            array('cdata',array(' z ')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('table_close',array(33)),
+            array('p_open',array()),
+            array('cdata',array('def')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals($calls,array_map('stripbyteindex',$this->H->calls));
+
+    }
+
+    function testTableHeadOneRowTable() {
+        $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('tableheader_open',array(1,NULL,1)),
+            array('cdata',array(' Y ')),
+            array('tableheader_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($calls,array_map('stripbyteindex',$this->H->calls));
+
+    }
+
+    function testTableHeadMultiline() {
+        $this->P->addMode('table',new Doku_Parser_Mode_Table());
+        $this->P->parse('
+abc
+^ X1 ^ Y1 ^ Z1 ^
+^ X2 ^ Y2 ^ Z2 ^
+| A | B | C |
+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('tablethead_open',array()),
+            array('tablerow_open',array()),
+            array('tableheader_open',array(1,NULL,1)),
+            array('cdata',array(' X1 ')),
+            array('tableheader_close',array()),
+            array('tableheader_open',array(1,NULL,1)),
+            array('cdata',array(' Y1 ')),
+            array('tableheader_close',array()),
+            array('tableheader_open',array(1,NULL,1)),
+            array('cdata',array(' Z1 ')),
+            array('tableheader_close',array()),
+            array('tablerow_close',array()),
+            array('tablerow_open',array()),
+            array('tableheader_open',array(1,NULL,1)),
+            array('cdata',array(' X2 ')),
+            array('tableheader_close',array()),
+            array('tableheader_open',array(1,NULL,1)),
+            array('cdata',array(' Y2 ')),
+            array('tableheader_close',array()),
+            array('tableheader_open',array(1,NULL,1)),
+            array('cdata',array(' Z2 ')),
+            array('tableheader_close',array()),
+            array('tablerow_close',array()),
+            array('tablethead_close',array()),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,NULL,1)),
+            array('cdata',array(' A ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,NULL,1)),
+            array('cdata',array(' B ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,NULL,1)),
+            array('cdata',array(' C ')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('table_close',array(53)),
+            array('p_open',array()),
+            array('cdata',array('def')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals($calls,array_map('stripbyteindex',$this->H->calls));
 
     }
     
@@ -178,7 +323,7 @@ def');
             array('document_end',array()),
         );
 
-        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+        $this->assertEquals($calls,array_map('stripbyteindex',$this->H->calls));
     }
     
     function testCellSpan() {
@@ -220,7 +365,7 @@ def');
             array('p_close',array()),
             array('document_end',array()),
         );
-        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+        $this->assertEquals($calls,array_map('stripbyteindex',$this->H->calls));
     }
     
     function testCellRowSpan() {
@@ -268,7 +413,7 @@ def');
             array('p_close',array()),
             array('document_end',array()),
         );
-        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+        $this->assertEquals($calls,array_map('stripbyteindex',$this->H->calls));
     }
 
     function testCellRowSpanFirstRow() {
@@ -326,9 +471,134 @@ def');
             array('p_close',array()),
             array('document_end',array()),
         );
-        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+        $this->assertEquals($calls,array_map('stripbyteindex',$this->H->calls));
     }
     
+    function testRowSpanTableHead() {
+        $this->P->addMode('table',new Doku_Parser_Mode_Table());
+        $this->P->parse('
+abc
+^ X1 ^ Y1 ^ Z1 ^
+^ X2 ^ ::: ^ Z2 ^
+| A3 | B3 | C3 |
+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('tablethead_open',array()),
+            array('tablerow_open',array()),
+            array('tableheader_open',array(1,NULL,1)),
+            array('cdata',array(' X1 ')),
+            array('tableheader_close',array()),
+            array('tableheader_open',array(1,NULL,2)),
+            array('cdata',array(' Y1 ')),
+            array('tableheader_close',array()),
+            array('tableheader_open',array(1,NULL,1)),
+            array('cdata',array(' Z1 ')),
+            array('tableheader_close',array()),
+            array('tablerow_close',array()),
+            array('tablerow_open',array()),
+            array('tableheader_open',array(1,NULL,1)),
+            array('cdata',array(' X2 ')),
+            array('tableheader_close',array()),
+            array('tableheader_open',array(1,NULL,1)),
+            array('cdata',array(' Z2 ')),
+            array('tableheader_close',array()),
+            array('tablerow_close',array()),
+            array('tablethead_close',array()),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,NULL,1)),
+            array('cdata',array(' A3 ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,NULL,1)),
+            array('cdata',array(' B3 ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,NULL,1)),
+            array('cdata',array(' C3 ')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('table_close',array(57)),
+            array('p_open',array()),
+            array('cdata',array('def')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals($calls,array_map('stripbyteindex',$this->H->calls));
+
+    }
+
+    function testRowSpanAcrossTableHeadBoundary() {
+        $this->P->addMode('table',new Doku_Parser_Mode_Table());
+        $this->P->parse('
+abc
+^ X1 ^ Y1 ^ Z1 ^
+^ X2 ^ ::: ^ Z2 ^
+| A3 | ::: | C3 |
+| A4 | ::: | C4 |
+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, 4, 6)),
+            array('tablethead_open',array()),
+            array('tablerow_open',array()),
+            array('tableheader_open',array(1,NULL,1)),
+            array('cdata',array(' X1 ')),
+            array('tableheader_close',array()),
+            array('tableheader_open',array(1,NULL,2)),
+            array('cdata',array(' Y1 ')),
+            array('tableheader_close',array()),
+            array('tableheader_open',array(1,NULL,1)),
+            array('cdata',array(' Z1 ')),
+            array('tableheader_close',array()),
+            array('tablerow_close',array()),
+            array('tablerow_open',array()),
+            array('tableheader_open',array(1,NULL,1)),
+            array('cdata',array(' X2 ')),
+            array('tableheader_close',array()),
+            array('tableheader_open',array(1,NULL,1)),
+            array('cdata',array(' Z2 ')),
+            array('tableheader_close',array()),
+            array('tablerow_close',array()),
+            array('tablethead_close',array()),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,NULL,1)),
+            array('cdata',array(' A3 ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,NULL,2)),
+            array('cdata',array('')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,NULL,1)),
+            array('cdata',array(' C3 ')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('tablerow_open',array()),
+            array('tablecell_open',array(1,NULL,1)),
+            array('cdata',array(' A4 ')),
+            array('tablecell_close',array()),
+            array('tablecell_open',array(1,NULL,1)),
+            array('cdata',array(' C4 ')),
+            array('tablecell_close',array()),
+            array('tablerow_close',array()),
+            array('table_close',array(76)),
+            array('p_open',array()),
+            array('cdata',array('def')),
+            array('p_close',array()),
+            array('document_end',array()),
+        );
+
+        $this->assertEquals($calls,array_map('stripbyteindex',$this->H->calls));
+
+    }
+
     function testCellAlignmentFormatting() {
         $this->P->addMode('table',new Doku_Parser_Mode_Table());
         $this->P->addMode('strong',new Doku_Parser_Mode_Formatting('strong'));
@@ -365,7 +635,7 @@ def');
             array('document_end',array()),
         );
  
-        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+        $this->assertEquals($calls,array_map('stripbyteindex',$this->H->calls));
         
     }
     
@@ -411,7 +681,7 @@ def');
             array('p_close',array()),
             array('document_end',array()),
         );
-        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+        $this->assertEquals($calls,array_map('stripbyteindex',$this->H->calls));
     }
     
     // This is really a failing test - formatting able to spread across cols
@@ -466,7 +736,7 @@ def');
             array('p_close',array()),
             array('document_end',array()),
         );
-        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+        $this->assertEquals($calls,array_map('stripbyteindex',$this->H->calls));
     }
     
     // This is really a failing test - unformatted able to spread across cols
@@ -517,7 +787,7 @@ def');
             array('p_close',array()),
             array('document_end',array()),
         );
-        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+        $this->assertEquals($calls,array_map('stripbyteindex',$this->H->calls));
     }
     
     function testTableLinebreak() {
@@ -565,7 +835,7 @@ def');
             array('document_end',array()),
         );
 
-        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+        $this->assertEquals($calls,array_map('stripbyteindex',$this->H->calls));
     }
     
     // This is really a failing test - footnote able to spread across cols
@@ -624,7 +894,7 @@ def');
             array('p_close',array()),
             array('document_end',array()),
         );
-        $this->assertEquals(array_map('stripbyteindex',$this->H->calls),$calls);
+        $this->assertEquals($calls,array_map('stripbyteindex',$this->H->calls));
     }
 
     function testTable_FS1833() {
@@ -646,4 +916,3 @@ def');
     }
 
 }
-
diff --git a/inc/parser/handler.php b/inc/parser/handler.php
index dfaf1adaa0922169efb29ff3bafa02262a64c6c2..a1040d12e78e21bb77c6c9af3faae21f84d5333e 100644
--- a/inc/parser/handler.php
+++ b/inc/parser/handler.php
@@ -1156,6 +1156,9 @@ class Doku_Handler_Table {
     var $currentCols = 0;
     var $firstCell = false;
     var $lastCellType = 'tablecell';
+    var $inTableHead = true;
+    var $currentRow = array('tableheader' => 0, 'tablecell' => 0);
+    var $countTableHeadRows = 0;
 
     function Doku_Handler_Table(& $CallWriter) {
         $this->CallWriter = & $CallWriter;
@@ -1223,15 +1226,24 @@ class Doku_Handler_Table {
         $this->firstCell = true;
         $this->lastCellType = 'tablecell';
         $this->maxRows++;
+        if ($this->inTableHead) {
+            $this->currentRow = array('tablecell' => 0, 'tableheader' => 0);
+        }
     }
 
     function tableRowClose($call) {
+        if ($this->inTableHead && ($this->inTableHead = $this->isTableHeadRow())) {
+            $this->countTableHeadRows++;
+        }
         // Strip off final cell opening and anything after it
         while ( $discard = array_pop($this->tableCalls ) ) {
 
             if ( $discard[0] == 'tablecell_open' || $discard[0] == 'tableheader_open') {
                 break;
             }
+            if (!empty($this->currentRow[$discard[0]])) {
+                $this->currentRow[$discard[0]]--;
+            }
         }
         $this->tableCalls[] = array('tablerow_close', array(), $call[2]);
 
@@ -1240,7 +1252,20 @@ class Doku_Handler_Table {
         }
     }
 
+    function isTableHeadRow() {
+        $td = $this->currentRow['tablecell'];
+        $th = $this->currentRow['tableheader'];
+
+        if (!$th || $td > 2) return false;
+        if (2*$td > $th) return false;
+
+        return true;
+    }
+
     function tableCell($call) {
+        if ($this->inTableHead) {
+            $this->currentRow[$call[0]]++;
+        }
         if ( !$this->firstCell ) {
 
             // Increase the span
@@ -1288,6 +1313,13 @@ class Doku_Handler_Table {
         $cellKey = array();
         $toDelete = array();
 
+        // if still in tableheader, then there can be no table header
+        // as all rows can't be within <THEAD>
+        if ($this->inTableHead) {
+            $this->inTableHead = false;
+            $this->countTableHeadRows = 0;
+        }
+
         // Look for the colspan elements and increment the colspan on the
         // previous non-empty opening cell. Once done, delete all the cells
         // that contain colspans
@@ -1295,6 +1327,14 @@ class Doku_Handler_Table {
             $call = $this->tableCalls[$key];
 
             switch ($call[0]) {
+                case 'table_open' :
+                    if($this->countTableHeadRows) {
+                        array_splice($this->tableCalls, $key+1, 0, array(
+                              array('tablethead_open', array(), $call[2]))
+                        );
+                    }
+                    break;
+
                 case 'tablerow_open':
 
                     $lastRow++;
@@ -1364,15 +1404,19 @@ class Doku_Handler_Table {
                     } else {
 
                         $spanning_cell = null;
-                        for($i = $lastRow-1; $i > 0; $i--) {
 
-                            if ( $this->tableCalls[$cellKey[$i][$lastCell]][0] == 'tablecell_open' || $this->tableCalls[$cellKey[$i][$lastCell]][0] == 'tableheader_open' ) {
+                        // can't cross thead/tbody boundary
+                        if (!$this->countTableHeadRows || ($lastRow-1 != $this->countTableHeadRows)) {
+                            for($i = $lastRow-1; $i > 0; $i--) {
 
-                                if ($this->tableCalls[$cellKey[$i][$lastCell]][1][2] >= $lastRow - $i) {
-                                    $spanning_cell = $i;
-                                    break;
-                                }
+                                if ( $this->tableCalls[$cellKey[$i][$lastCell]][0] == 'tablecell_open' || $this->tableCalls[$cellKey[$i][$lastCell]][0] == 'tableheader_open' ) {
+
+                                    if ($this->tableCalls[$cellKey[$i][$lastCell]][1][2] >= $lastRow - $i) {
+                                        $spanning_cell = $i;
+                                        break;
+                                    }
 
+                                }
                             }
                         }
                         if (is_null($spanning_cell)) {
@@ -1403,6 +1447,10 @@ class Doku_Handler_Table {
                         $key += 3;
                     }
 
+                    if($this->countTableHeadRows == $lastRow) {
+                        array_splice($this->tableCalls, $key+1, 0, array(
+                              array('tablethead_close', array(), $call[2])));
+                    }
                     break;
 
             }
@@ -1445,7 +1493,7 @@ class Doku_Handler_Block {
     var $blockOpen = array(
             'header',
             'listu_open','listo_open','listitem_open','listcontent_open',
-            'table_open','tablerow_open','tablecell_open','tableheader_open',
+            'table_open','tablerow_open','tablecell_open','tableheader_open','tablethead_open',
             'quote_open',
             'code','file','hr','preformatted','rss',
             'htmlblock','phpblock',
@@ -1455,7 +1503,7 @@ class Doku_Handler_Block {
     var $blockClose = array(
             'header',
             'listu_close','listo_close','listitem_close','listcontent_close',
-            'table_close','tablerow_close','tablecell_close','tableheader_close',
+            'table_close','tablerow_close','tablecell_close','tableheader_close','tablethead_close',
             'quote_close',
             'code','file','hr','preformatted','rss',
             'htmlblock','phpblock',
diff --git a/inc/parser/renderer.php b/inc/parser/renderer.php
index 0c3c56c568bf0cdf73f723e1a2c9bc0491b1045a..e92b81bd77566dd3e834a5f0d7aa006958b7d896 100644
--- a/inc/parser/renderer.php
+++ b/inc/parser/renderer.php
@@ -249,6 +249,10 @@ class Doku_Renderer extends DokuWiki_Plugin {
 
     function table_close($pos = null){}
 
+    function tablethead_open(){}
+
+    function tablethead_close(){}
+
     function tablerow_open(){}
 
     function tablerow_close(){}
diff --git a/inc/parser/xhtml.php b/inc/parser/xhtml.php
index e3f9a4187b0036205e65a9854eb2530630119fc0..cf36a81757ddd3de7fde5ce2f34d2fbab60c0686 100644
--- a/inc/parser/xhtml.php
+++ b/inc/parser/xhtml.php
@@ -963,6 +963,14 @@ class Doku_Renderer_xhtml extends Doku_Renderer {
         }
     }
 
+    function tablethead_open(){
+        $this->doc .= DOKU_TAB . '<thead>' . DOKU_LF;
+    }
+
+    function tablethead_close(){
+        $this->doc .= DOKU_TAB . '</thead>' . DOKU_LF;
+    }
+
     function tablerow_open(){
         // initialize the cell counter used for classes
         $this->_counter['cell_counter'] = 0;