File indexing completed on 2024-12-22 05:36:19
0001 <?php 0002 0003 /** 0004 * Definition for tables. The general idea is to extract out all of the 0005 * essential bits, and then reconstruct it later. 0006 * 0007 * This is a bit confusing, because the DTDs and the W3C 0008 * validators seem to disagree on the appropriate definition. The 0009 * DTD claims: 0010 * 0011 * (CAPTION?, (COL*|COLGROUP*), THEAD?, TFOOT?, TBODY+) 0012 * 0013 * But actually, the HTML4 spec then has this to say: 0014 * 0015 * The TBODY start tag is always required except when the table 0016 * contains only one table body and no table head or foot sections. 0017 * The TBODY end tag may always be safely omitted. 0018 * 0019 * So the DTD is kind of wrong. The validator is, unfortunately, kind 0020 * of on crack. 0021 * 0022 * The definition changed again in XHTML1.1; and in my opinion, this 0023 * formulation makes the most sense. 0024 * 0025 * caption?, ( col* | colgroup* ), (( thead?, tfoot?, tbody+ ) | ( tr+ )) 0026 * 0027 * Essentially, we have two modes: thead/tfoot/tbody mode, and tr mode. 0028 * If we encounter a thead, tfoot or tbody, we are placed in the former 0029 * mode, and we *must* wrap any stray tr segments with a tbody. But if 0030 * we don't run into any of them, just have tr tags is OK. 0031 */ 0032 class HTMLPurifier_ChildDef_Table extends HTMLPurifier_ChildDef 0033 { 0034 /** 0035 * @type bool 0036 */ 0037 public $allow_empty = false; 0038 0039 /** 0040 * @type string 0041 */ 0042 public $type = 'table'; 0043 0044 /** 0045 * @type array 0046 */ 0047 public $elements = array( 0048 'tr' => true, 0049 'tbody' => true, 0050 'thead' => true, 0051 'tfoot' => true, 0052 'caption' => true, 0053 'colgroup' => true, 0054 'col' => true 0055 ); 0056 0057 public function __construct() 0058 { 0059 } 0060 0061 /** 0062 * @param array $children 0063 * @param HTMLPurifier_Config $config 0064 * @param HTMLPurifier_Context $context 0065 * @return array 0066 */ 0067 public function validateChildren($children, $config, $context) 0068 { 0069 if (empty($children)) { 0070 return false; 0071 } 0072 0073 // only one of these elements is allowed in a table 0074 $caption = false; 0075 $thead = false; 0076 $tfoot = false; 0077 0078 // whitespace 0079 $initial_ws = array(); 0080 $after_caption_ws = array(); 0081 $after_thead_ws = array(); 0082 $after_tfoot_ws = array(); 0083 0084 // as many of these as you want 0085 $cols = array(); 0086 $content = array(); 0087 0088 $tbody_mode = false; // if true, then we need to wrap any stray 0089 // <tr>s with a <tbody>. 0090 0091 $ws_accum =& $initial_ws; 0092 0093 foreach ($children as $node) { 0094 if ($node instanceof HTMLPurifier_Node_Comment) { 0095 $ws_accum[] = $node; 0096 continue; 0097 } 0098 switch ($node->name) { 0099 case 'tbody': 0100 $tbody_mode = true; 0101 // fall through 0102 case 'tr': 0103 $content[] = $node; 0104 $ws_accum =& $content; 0105 break; 0106 case 'caption': 0107 // there can only be one caption! 0108 if ($caption !== false) break; 0109 $caption = $node; 0110 $ws_accum =& $after_caption_ws; 0111 break; 0112 case 'thead': 0113 $tbody_mode = true; 0114 // XXX This breaks rendering properties with 0115 // Firefox, which never floats a <thead> to 0116 // the top. Ever. (Our scheme will float the 0117 // first <thead> to the top.) So maybe 0118 // <thead>s that are not first should be 0119 // turned into <tbody>? Very tricky, indeed. 0120 if ($thead === false) { 0121 $thead = $node; 0122 $ws_accum =& $after_thead_ws; 0123 } else { 0124 // Oops, there's a second one! What 0125 // should we do? Current behavior is to 0126 // transmutate the first and last entries into 0127 // tbody tags, and then put into content. 0128 // Maybe a better idea is to *attach 0129 // it* to the existing thead or tfoot? 0130 // We don't do this, because Firefox 0131 // doesn't float an extra tfoot to the 0132 // bottom like it does for the first one. 0133 $node->name = 'tbody'; 0134 $content[] = $node; 0135 $ws_accum =& $content; 0136 } 0137 break; 0138 case 'tfoot': 0139 // see above for some aveats 0140 $tbody_mode = true; 0141 if ($tfoot === false) { 0142 $tfoot = $node; 0143 $ws_accum =& $after_tfoot_ws; 0144 } else { 0145 $node->name = 'tbody'; 0146 $content[] = $node; 0147 $ws_accum =& $content; 0148 } 0149 break; 0150 case 'colgroup': 0151 case 'col': 0152 $cols[] = $node; 0153 $ws_accum =& $cols; 0154 break; 0155 case '#PCDATA': 0156 // How is whitespace handled? We treat is as sticky to 0157 // the *end* of the previous element. So all of the 0158 // nonsense we have worked on is to keep things 0159 // together. 0160 if (!empty($node->is_whitespace)) { 0161 $ws_accum[] = $node; 0162 } 0163 break; 0164 } 0165 } 0166 0167 if (empty($content)) { 0168 return false; 0169 } 0170 0171 $ret = $initial_ws; 0172 if ($caption !== false) { 0173 $ret[] = $caption; 0174 $ret = array_merge($ret, $after_caption_ws); 0175 } 0176 if ($cols !== false) { 0177 $ret = array_merge($ret, $cols); 0178 } 0179 if ($thead !== false) { 0180 $ret[] = $thead; 0181 $ret = array_merge($ret, $after_thead_ws); 0182 } 0183 if ($tfoot !== false) { 0184 $ret[] = $tfoot; 0185 $ret = array_merge($ret, $after_tfoot_ws); 0186 } 0187 0188 if ($tbody_mode) { 0189 // we have to shuffle tr into tbody 0190 $current_tr_tbody = null; 0191 0192 foreach($content as $node) { 0193 switch ($node->name) { 0194 case 'tbody': 0195 $current_tr_tbody = null; 0196 $ret[] = $node; 0197 break; 0198 case 'tr': 0199 if ($current_tr_tbody === null) { 0200 $current_tr_tbody = new HTMLPurifier_Node_Element('tbody'); 0201 $ret[] = $current_tr_tbody; 0202 } 0203 $current_tr_tbody->children[] = $node; 0204 break; 0205 case '#PCDATA': 0206 //assert($node->is_whitespace); 0207 if ($current_tr_tbody === null) { 0208 $ret[] = $node; 0209 } else { 0210 $current_tr_tbody->children[] = $node; 0211 } 0212 break; 0213 } 0214 } 0215 } else { 0216 $ret = array_merge($ret, $content); 0217 } 0218 0219 return $ret; 0220 0221 } 0222 } 0223 0224 // vim: et sw=4 sts=4