225 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			PHP
		
	
	
		
		
			
		
	
	
			225 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			PHP
		
	
	
| 
								 | 
							
								<?php
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Definition for tables.  The general idea is to extract out all of the
							 | 
						||
| 
								 | 
							
								 * essential bits, and then reconstruct it later.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * This is a bit confusing, because the DTDs and the W3C
							 | 
						||
| 
								 | 
							
								 * validators seem to disagree on the appropriate definition. The
							 | 
						||
| 
								 | 
							
								 * DTD claims:
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 *      (CAPTION?, (COL*|COLGROUP*), THEAD?, TFOOT?, TBODY+)
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * But actually, the HTML4 spec then has this to say:
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 *      The TBODY start tag is always required except when the table
							 | 
						||
| 
								 | 
							
								 *      contains only one table body and no table head or foot sections.
							 | 
						||
| 
								 | 
							
								 *      The TBODY end tag may always be safely omitted.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * So the DTD is kind of wrong.  The validator is, unfortunately, kind
							 | 
						||
| 
								 | 
							
								 * of on crack.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * The definition changed again in XHTML1.1; and in my opinion, this
							 | 
						||
| 
								 | 
							
								 * formulation makes the most sense.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 *      caption?, ( col* | colgroup* ), (( thead?, tfoot?, tbody+ ) | ( tr+ ))
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * Essentially, we have two modes: thead/tfoot/tbody mode, and tr mode.
							 | 
						||
| 
								 | 
							
								 * If we encounter a thead, tfoot or tbody, we are placed in the former
							 | 
						||
| 
								 | 
							
								 * mode, and we *must* wrap any stray tr segments with a tbody. But if
							 | 
						||
| 
								 | 
							
								 * we don't run into any of them, just have tr tags is OK.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								class HTMLPurifier_ChildDef_Table extends HTMLPurifier_ChildDef
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * @type bool
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    public $allow_empty = false;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * @type string
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    public $type = 'table';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /**
							 | 
						||
| 
								 | 
							
								     * @type array
							 | 
						||
| 
								 | 
							
								     */
							 | 
						||
| 
								 | 
							
								    public $elements = array(
							 | 
						||
| 
								 | 
							
								        'tr' => 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
							 | 
						||
| 
								 | 
							
								                             // <tr>s with a <tbody>.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        $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 <thead> to
							 | 
						||
| 
								 | 
							
								                // the top. Ever. (Our scheme will float the
							 | 
						||
| 
								 | 
							
								                // first <thead> to the top.)  So maybe
							 | 
						||
| 
								 | 
							
								                // <thead>s that are not first should be
							 | 
						||
| 
								 | 
							
								                // turned into <tbody>? 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) && $thead === false && $tfoot === false) {
							 | 
						||
| 
								 | 
							
								            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
							 |