Index: skins/common/wikibits.js =================================================================== --- skins/common/wikibits.js (revision 40784) +++ skins/common/wikibits.js (working copy) @@ -534,14 +534,8 @@ } function ts_makeSortable(table) { - var firstRow; - if (table.rows && table.rows.length > 0) { - if (table.tHead && table.tHead.rows.length > 0) { - firstRow = table.tHead.rows[table.tHead.rows.length-1]; - } else { - firstRow = table.rows[0]; - } - } + var firstRow = table.rows[0]; + if (!firstRow) return; // We have a first row: assume it's the header, and make its contents clickable links @@ -595,7 +589,7 @@ if (table.rows.length <= 1) return; // Skip the first row if that's where the headings are - var rowStart = (table.tHead && table.tHead.rows.length > 0 ? 0 : 1); + var rowStart = 1; var itm = ""; for (var i = rowStart; i < table.rows.length; i++) { Index: includes/parser/Parser.php =================================================================== --- includes/parser/Parser.php (revision 40784) +++ includes/parser/Parser.php (working copy) @@ -768,196 +768,252 @@ wfProfileIn( __METHOD__ ); $lines = StringUtils::explode( "\n", $text ); + + $tables = array(); $out = ''; - $td_history = array (); // Is currently a td tag open? - $last_tag_history = array (); // Save history of last lag activated (td, th or caption) - $tr_history = array (); // Is currently a tr tag open? - $tr_attributes = array (); // history of tr attributes - $has_opened_tr = array(); // Did this table open a element? - $indent_level = 0; // indent level of the table + $output =& $out; foreach ( $lines as $outLine ) { $line = trim( $outLine ); - if( $line == '' ) { // empty line, go to next line - $out .= $outLine."\n"; + if($line === '' ) { + $output .= "\n" . $outLine; continue; } - $first_character = $line[0]; + + $first_chars = $line[0]; + if ( strlen($line) > 1) + $first_chars .= in_array($line[1], array('}', '+', '-')) ? $line[1] : ''; + $matches = array(); - if ( preg_match( '/^(:*)\{\|(.*)$/', $line , $matches ) ) { - // First check if we are starting a new table - $indent_level = strlen( $matches[1] ); + $tables[] = array(); + $table =& $this->last($tables); + $table[0] = array(); //first row + $current_row =& $table[0]; + $table['indent'] = strlen( $matches[1] ); + $attributes = $this->mStripState->unstripBoth( $matches[2] ); $attributes = Sanitizer::fixTagAttributes ( $attributes , 'table' ); - $outLine = str_repeat( '
' , $indent_level ) . ""; - array_push ( $td_history , false ); - array_push ( $last_tag_history , '' ); - array_push ( $tr_history , false ); - array_push ( $tr_attributes , '' ); - array_push ( $has_opened_tr , false ); - } else if ( count ( $td_history ) == 0 ) { - // Don't do any of the following - $out .= $outLine."\n"; - continue; - } else if ( substr ( $line , 0 , 2 ) === '|}' ) { - // We are ending a table - $line = '' . substr ( $line , 2 ); - $last_tag = array_pop ( $last_tag_history ); + if ( $attributes !== '' ) $table['attributes'] = $attributes; + } + else if ( !isset($tables[0]) ) { + // we're outside the table + $output .= "\n" . $outLine; + } + else if ( $first_chars === '|}' ) { + // trim the |} code from the line + $line = substr ( $line , 2 ); + + // Shorthand for last row + $last_row =& $this->last($table); - if ( !array_pop ( $has_opened_tr ) ) { - $line = "{$line}"; + // a thead at the end becomes a tfoot, unless there is only one row + // Do this before deleting empty last lines to allow headers at the bottom of tables + if(isset($last_row['type']) && $last_row['type'] == 'thead' && isset($table[1])) { + $last_row['type'] = 'tfoot'; + for($i = 0; isset($last_row[$i]); $i++ ) { + $last_row[$i]['type'] = 'td'; + } } + + // Delete empty last lines + if( empty($last_row) ) $last_row = NULL; - if ( array_pop ( $tr_history ) ) { - $line = "{$line}"; + $o = $this->printTableHtml( array_pop($tables) ) . $line; + + if( count($tables) > 0 ) { + $table =& $this->last($tables); + $current_row =& $this->last($table); + $current_element =& $this->last($current_row); + + $output =& $current_element['content']; + } else { + $output =& $out; } - if ( array_pop ( $td_history ) ) { - $line = "{$line}"; + $output .= $o; + } + else if ( $first_chars === '|-' ) { + // start a new row element + // but only when we haven't started one already + if( count($current_row) != 0 ) { + $table[] = array(); + $current_row =& $this->last($table); } - array_pop ( $tr_attributes ); - $outLine = $line . str_repeat( '
' , $indent_level ); - } else if ( substr ( $line , 0 , 2 ) === '|-' ) { - // Now we have a table row - $line = preg_replace( '#^\|-+#', '', $line ); - // Whats after the tag is now only attributes + // Get the attributes, there's nothing else useful in $line now + $line = substr ( $line , 2 ); $attributes = $this->mStripState->unstripBoth( $line ); $attributes = Sanitizer::fixTagAttributes ( $attributes , 'tr' ); - array_pop ( $tr_attributes ); - array_push ( $tr_attributes , $attributes ); + if( $attributes !== '') $current_row['attributes'] = $attributes; + } + else if ( $first_chars === '|+' ) { + // a table caption + $line = substr ( $line , 2 ); - $line = ''; - $last_tag = array_pop ( $last_tag_history ); - array_pop ( $has_opened_tr ); - array_push ( $has_opened_tr , true ); + $c = $this->getCellAttr($line , 'caption'); + $table['caption'] = array(); + $table['caption']['content'] = $c[0]; + if(isset($c[1])) $table['caption']['attributes'] = $c[1]; + unset($c); - if ( array_pop ( $tr_history ) ) { - $line = ''; - } - - if ( array_pop ( $td_history ) ) { - $line = "{$line}"; - } - - $outLine = $line; - array_push ( $tr_history , false ); - array_push ( $td_history , false ); - array_push ( $last_tag_history , '' ); + $output =& $table['caption']; } - else if ( $first_character === '|' || $first_character === '!' || substr ( $line , 0 , 2 ) === '|+' ) { - // This might be cell elements, td, th or captions - if ( substr ( $line , 0 , 2 ) === '|+' ) { - $first_character = '+'; - $line = substr ( $line , 1 ); - } - + else if ( $first_chars === '|' || $first_chars === '!' ) { + // Which kind of cells are we dealing with + $this_tag = 'td'; $line = substr ( $line , 1 ); - if ( $first_character === '!' ) { + if ( $first_chars === '!' ) { $line = str_replace ( '!!' , '||' , $line ); + $this_tag = 'th'; } // Split up multiple cells on the same line. - // FIXME : This can result in improper nesting of tags processed - // by earlier parser steps, but should avoid splitting up eg - // attribute values containing literal "||". $cells = StringUtils::explodeMarkup( '||' , $line ); + $line = ''; // save memory - $outLine = ''; + // decide whether thead to tbody + if ( !array_key_exists('type', $current_row) ) { + $current_row['type'] = ( $first_chars === '!' ) ? 'thead' : 'tbody' ; + } else if( $first_chars === '|' ) { + $current_row['type'] = 'tbody'; + } // Loop through each table cell - foreach ( $cells as $cell ) - { - $previous = ''; - if ( $first_character !== '+' ) - { - $tr_after = array_pop ( $tr_attributes ); - if ( !array_pop ( $tr_history ) ) { - $previous = "\n"; - } - array_push ( $tr_history , true ); - array_push ( $tr_attributes , '' ); - array_pop ( $has_opened_tr ); - array_push ( $has_opened_tr , true ); - } + foreach ( $cells as $cell ) { + // a new cell + $current_row[] = array(); + $current_element =& $this->last($current_row); - $last_tag = array_pop ( $last_tag_history ); + $current_element['type'] = $this_tag; - if ( array_pop ( $td_history ) ) { - $previous = "{$previous}"; - } + $c = $this->getCellAttr($cell , $this_tag); + $current_element['content'] = $c[0]; + if(isset($c[1])) $current_element['attributes'] = $c[1]; + unset($c); + } + $output =& $current_element['content']; + } + else { + $output .= "\n" . $outLine; + } + } - if ( $first_character === '|' ) { - $last_tag = 'td'; - } else if ( $first_character === '!' ) { - $last_tag = 'th'; - } else if ( $first_character === '+' ) { - $last_tag = 'caption'; - } else { - $last_tag = ''; - } + wfProfileOut( __METHOD__ ); - array_push ( $last_tag_history , $last_tag ); + return $out; + } - // A cell could contain both parameters and data - $cell_data = explode ( '|' , $cell , 2 ); - // Bug 553: Note that a '|' inside an invalid link should not - // be mistaken as delimiting cell parameters - if ( strpos( $cell_data[0], '[[' ) !== false ) { - $cell = "{$previous}<{$last_tag}>{$cell}"; - } else if ( count ( $cell_data ) == 1 ) - $cell = "{$previous}<{$last_tag}>{$cell_data[0]}"; - else { - $attributes = $this->mStripState->unstripBoth( $cell_data[0] ); - $attributes = Sanitizer::fixTagAttributes( $attributes , $last_tag ); - $cell = "{$previous}<{$last_tag}{$attributes}>{$cell_data[1]}"; - } + /** + * Helper function for doTableStuff() separating the contents of cells from + * attributes. Particularly useful as there's a possible bug and this action + * is repeated twice. + * + * @private + */ + function getCellAttr ($cell , $tag_name) { + $content = null; + $attributes = null; - $outLine .= $cell; - array_push ( $td_history , true ); - } - } - $out .= $outLine . "\n"; - } + $cell = trim ( $cell ); - // Closing open td, tr && table - while ( count ( $td_history ) > 0 ) - { - if ( array_pop ( $td_history ) ) { - $out .= "\n"; - } - if ( array_pop ( $tr_history ) ) { - $out .= "\n"; - } - if ( !array_pop ( $has_opened_tr ) ) { - $out .= "\n" ; - } + // A cell could contain both parameters and data + $cell_data = explode ( '|' , $cell , 2 ); - $out .= "\n"; + // Bug 553: Note that a '|' inside an invalid link should not + // be mistaken as delimiting cell parameters + if ( strpos( $cell_data[0], '[[' ) !== false ) { + $content = trim ( $cell ); } + else if ( count ( $cell_data ) == 1 ) { + $content = trim ( $cell_data[0] ); + } + else { + $attributes = $this->mStripState->unstripBoth( $cell_data[0] ); + $attributes = Sanitizer::fixTagAttributes( $attributes , $tag_name ); - // Remove trailing line-ending (b/c) - if ( substr( $out, -1 ) === "\n" ) { - $out = substr( $out, 0, -1 ); + $content = trim ( $cell_data[1] ); } + return array($content, $attributes); + } - // special case: don't return empty table - if( $out === "\n\n
" ) { - $out = ''; + + /** + * Helper function for doTableStuff(). This converts the structured array into html. + * + * @private + */ + function printTableHtml (&$t) { + $r = "\n"; + $r .= str_repeat( '
' , $t['indent'] ); + $r .= ''; + } - return $out; + $r .= "\n'; + unset($t[$i][$j]); + } + $r .= ''; + + if( !isset($t[$i+1]) || ( isset($t[$i+1]) && ($t[$i]['type'] != $t[$i+1]['type'])) ) { + $r .= ''; + } + $last_section = $t[$i]['type']; + unset($t[$i]); + } + + $r .= "\n"; + $r .= str_repeat( '
' , $t['indent'] ); + unset($t); + + return $r; } /** + * like end() but only works on the numeric array index and php's internal pointers + * returns a reference to the last element of an array much like "\$arr[-1]" in perl + * ignores associative elements and will create a 0 key will a NULL value if there were + * no numric elements and an array itself if not previously defined. + * + * @private + */ + function &last (&$arr) { + for($i = count($arr); (!isset($arr[$i]) && $i > 0); $i--) { } + return $arr[$i]; + } + + /** * Helper function for parse() that transforms wiki markup into * HTML. Only called for $mOutputType == self::OT_HTML. *