true, 'tbody' => true, 'thead' => true, 'tfoot' => true, 'caption' => true, 'colgroup' => true, 'col' => true ); public function __construct() { } /** * * @param array $children * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return array */ public function validateChildren($children, $config, $context) { if (empty ( $children )) { return false; } // only one of these elements is allowed in a table $caption = false; $thead = false; $tfoot = false; // whitespace $initial_ws = array (); $after_caption_ws = array (); $after_thead_ws = array (); $after_tfoot_ws = array (); // as many of these as you want $cols = array (); $content = array (); $tbody_mode = false; // if true, then we need to wrap any stray // s with a . $ws_accum = & $initial_ws; foreach ( $children as $node ) { if ($node instanceof HTMLPurifier_Node_Comment) { $ws_accum [] = $node; continue; } switch ($node->name) { case 'tbody' : $tbody_mode = true; // fall through case 'tr' : $content [] = $node; $ws_accum = & $content; break; case 'caption' : // there can only be one caption! if ($caption !== false) break; $caption = $node; $ws_accum = & $after_caption_ws; break; case 'thead' : $tbody_mode = true; // XXX This breaks rendering properties with // Firefox, which never floats a to // the top. Ever. (Our scheme will float the // first to the top.) So maybe // s that are not first should be // turned into ? Very tricky, indeed. if ($thead === false) { $thead = $node; $ws_accum = & $after_thead_ws; } else { // Oops, there's a second one! What // should we do? Current behavior is to // transmutate the first and last entries into // tbody tags, and then put into content. // Maybe a better idea is to *attach // it* to the existing thead or tfoot? // We don't do this, because Firefox // doesn't float an extra tfoot to the // bottom like it does for the first one. $node->name = 'tbody'; $content [] = $node; $ws_accum = & $content; } break; case 'tfoot' : // see above for some aveats $tbody_mode = true; if ($tfoot === false) { $tfoot = $node; $ws_accum = & $after_tfoot_ws; } else { $node->name = 'tbody'; $content [] = $node; $ws_accum = & $content; } break; case 'colgroup' : case 'col' : $cols [] = $node; $ws_accum = & $cols; break; case '#PCDATA' : // How is whitespace handled? We treat is as sticky to // the *end* of the previous element. So all of the // nonsense we have worked on is to keep things // together. if (! empty ( $node->is_whitespace )) { $ws_accum [] = $node; } break; } } if (empty ( $content )) { return false; } $ret = $initial_ws; if ($caption !== false) { $ret [] = $caption; $ret = array_merge ( $ret, $after_caption_ws ); } if ($cols !== false) { $ret = array_merge ( $ret, $cols ); } if ($thead !== false) { $ret [] = $thead; $ret = array_merge ( $ret, $after_thead_ws ); } if ($tfoot !== false) { $ret [] = $tfoot; $ret = array_merge ( $ret, $after_tfoot_ws ); } if ($tbody_mode) { // we have to shuffle tr into tbody $current_tr_tbody = null; foreach ( $content as $node ) { switch ($node->name) { case 'tbody' : $current_tr_tbody = null; $ret [] = $node; break; case 'tr' : if ($current_tr_tbody === null) { $current_tr_tbody = new HTMLPurifier_Node_Element ( 'tbody' ); $ret [] = $current_tr_tbody; } $current_tr_tbody->children [] = $node; break; case '#PCDATA' : assert ( $node->is_whitespace ); if ($current_tr_tbody === null) { $ret [] = $node; } else { $current_tr_tbody->children [] = $node; } break; } } } else { $ret = array_merge ( $ret, $content ); } return $ret; } } // vim: et sw=4 sts=4