Source for file FixNesting.php

Documentation is available at FixNesting.php

  1. <?php
  2.  
  3. /**
  4.  * Takes a well formed list of tokens and fixes their nesting.
  5.  * 
  6.  * HTML elements dictate which elements are allowed to be their children,
  7.  * for example, you can't have a p tag in a span tag.  Other elements have
  8.  * much more rigorous definitions: tables, for instance, require a specific
  9.  * order for their elements.  There are also constraints not expressible by
  10.  * document type definitions, such as the chameleon nature of ins/del
  11.  * tags and global child exclusions.
  12.  * 
  13.  * The first major objective of this strategy is to iterate through all the
  14.  * nodes (not tokens) of the list of tokens and determine whether or not
  15.  * their children conform to the element's definition.  If they do not, the
  16.  * child definition may optionally supply an amended list of elements that
  17.  * is valid or require that the entire node be deleted (and the previous
  18.  * node rescanned).
  19.  * 
  20.  * The second objective is to ensure that explicitly excluded elements of
  21.  * an element do not appear in its children.  Code that accomplishes this
  22.  * task is pervasive through the strategy, though the two are distinct tasks
  23.  * and could, theoretically, be seperated (although it's not recommended).
  24.  * 
  25.  * @note Whether or not unrecognized children are silently dropped or
  26.  *        translated into text depends on the child definitions.
  27.  * 
  28.  * @todo Enable nodes to be bubbled out of the structure.
  29.  */
  30.  
  31. {
  32.     
  33.     public function execute($tokens$config$context{
  34.         //####################################################################//
  35.         // Pre-processing
  36.         
  37.         // get a copy of the HTML definition
  38.         $definition $config->getHTMLDefinition();
  39.         
  40.         // insert implicit "parent" node, will be removed at end.
  41.         // DEFINITION CALL
  42.         $parent_name $definition->info_parent;
  43.         array_unshift($tokensnew HTMLPurifier_Token_Start($parent_name));
  44.         $tokens[new HTMLPurifier_Token_End($parent_name);
  45.         
  46.         // setup the context variable 'IsInline', for chameleon processing
  47.         // is 'false' when we are not inline, 'true' when it must always
  48.         // be inline, and an integer when it is inline for a certain
  49.         // branch of the document tree
  50.         $is_inline $definition->info_parent_def->descendants_are_inline;
  51.         $context->register('IsInline'$is_inline);
  52.         
  53.         // setup error collector
  54.         $e =$context->get('ErrorCollector'true);
  55.         
  56.         //####################################################################//
  57.         // Loop initialization
  58.         
  59.         // stack that contains the indexes of all parents,
  60.         // $stack[count($stack)-1] being the current parent
  61.         $stack array();
  62.         
  63.         // stack that contains all elements that are excluded
  64.         // it is organized by parent elements, similar to $stack, 
  65.         // but it is only populated when an element with exclusions is
  66.         // processed, i.e. there won't be empty exclusions.
  67.         $exclude_stack array();
  68.         
  69.         // variable that contains the start token while we are processing
  70.         // nodes. This enables error reporting to do its job
  71.         $start_token false;
  72.         $context->register('CurrentToken'$start_token);
  73.         
  74.         //####################################################################//
  75.         // Loop
  76.         
  77.         // iterate through all start nodes. Determining the start node
  78.         // is complicated so it has been omitted from the loop construct
  79.         for ($i 0$size count($tokens$i $size{
  80.             
  81.             //################################################################//
  82.             // Gather information on children
  83.             
  84.             // child token accumulator
  85.             $child_tokens array();
  86.             
  87.             // scroll to the end of this node, report number, and collect
  88.             // all children
  89.             for ($j $i$depth 0; ; $j++{
  90.                 if ($tokens[$jinstanceof HTMLPurifier_Token_Start{
  91.                     $depth++;
  92.                     // skip token assignment on first iteration, this is the
  93.                     // token we currently are on
  94.                     if ($depth == 1continue;
  95.                 elseif ($tokens[$jinstanceof HTMLPurifier_Token_End{
  96.                     $depth--;
  97.                     // skip token assignment on last iteration, this is the
  98.                     // end token of the token we're currently on
  99.                     if ($depth == 0break;
  100.                 }
  101.                 $child_tokens[$tokens[$j];
  102.             }
  103.             
  104.             // $i is index of start token
  105.             // $j is index of end token
  106.             
  107.             $start_token $tokens[$i]// to make token available via CurrentToken
  108.             
  109.             //################################################################//
  110.             // Gather information on parent
  111.             
  112.             // calculate parent information
  113.             if ($count count($stack)) {
  114.                 $parent_index $stack[$count-1];
  115.                 $parent_name  $tokens[$parent_index]->name;
  116.                 if ($parent_index == 0{
  117.                     $parent_def   $definition->info_parent_def;
  118.                 else {
  119.                     $parent_def   $definition->info[$parent_name];
  120.                 }
  121.             else {
  122.                 // processing as if the parent were the "root" node
  123.                 // unknown info, it won't be used anyway, in the future,
  124.                 // we may want to enforce one element only (this is 
  125.                 // necessary for HTML Purifier to clean entire documents
  126.                 $parent_index $parent_name $parent_def null;
  127.             }
  128.             
  129.             // calculate context
  130.             if ($is_inline === false{
  131.                 // check if conditions make it inline
  132.                 if (!empty($parent_def&& $parent_def->descendants_are_inline{
  133.                     $is_inline $count 1;
  134.                 }
  135.             else {
  136.                 // check if we're out of inline
  137.                 if ($count === $is_inline{
  138.                     $is_inline false;
  139.                 }
  140.             }
  141.             
  142.             //################################################################//
  143.             // Determine whether element is explicitly excluded SGML-style
  144.             
  145.             // determine whether or not element is excluded by checking all
  146.             // parent exclusions. The array should not be very large, two
  147.             // elements at most.
  148.             $excluded false;
  149.             if (!empty($exclude_stack)) {
  150.                 foreach ($exclude_stack as $lookup{
  151.                     if (isset($lookup[$tokens[$i]->name])) {
  152.                         $excluded true;
  153.                         // no need to continue processing
  154.                         break;
  155.                     }
  156.                 }
  157.             }
  158.             
  159.             //################################################################//
  160.             // Perform child validation
  161.             
  162.             if ($excluded{
  163.                 // there is an exclusion, remove the entire node
  164.                 $result false;
  165.                 $excludes array()// not used, but good to initialize anyway
  166.             else {
  167.                 // DEFINITION CALL
  168.                 if ($i === 0{
  169.                     // special processing for the first node
  170.                     $def $definition->info_parent_def;
  171.                 else {
  172.                     $def $definition->info[$tokens[$i]->name];
  173.                     
  174.                 }
  175.                 
  176.                 if (!empty($def->child)) {
  177.                     // have DTD child def validate children
  178.                     $result $def->child->validateChildren(
  179.                         $child_tokens$config$context);
  180.                 else {
  181.                     // weird, no child definition, get rid of everything
  182.                     $result false;
  183.                 }
  184.                 
  185.                 // determine whether or not this element has any exclusions
  186.                 $excludes $def->excludes;
  187.             }
  188.             
  189.             // $result is now a bool or array
  190.             
  191.             //################################################################//
  192.             // Process result by interpreting $result
  193.             
  194.             if ($result === true || $child_tokens === $result{
  195.                 // leave the node as is
  196.                 
  197.                 // register start token as a parental node start
  198.                 $stack[$i;
  199.                 
  200.                 // register exclusions if there are any
  201.                 if (!empty($excludes)) $exclude_stack[$excludes;
  202.                 
  203.                 // move cursor to next possible start node
  204.                 $i++;
  205.                 
  206.             elseif($result === false{
  207.                 // remove entire node
  208.                 
  209.                 if ($e{
  210.                     if ($excluded{
  211.                         $e->send(E_ERROR'Strategy_FixNesting: Node excluded');
  212.                     else {
  213.                         $e->send(E_ERROR'Strategy_FixNesting: Node removed');
  214.                     }
  215.                 }
  216.                 
  217.                 // calculate length of inner tokens and current tokens
  218.                 $length $j $i 1;
  219.                 
  220.                 // perform removal
  221.                 array_splice($tokens$i$length);
  222.                 
  223.                 // update size
  224.                 $size -= $length;
  225.                 
  226.                 // there is no start token to register,
  227.                 // current node is now the next possible start node
  228.                 // unless it turns out that we need to do a double-check
  229.                 
  230.                 // this is a rought heuristic that covers 100% of HTML's
  231.                 // cases and 99% of all other cases. A child definition
  232.                 // that would be tricked by this would be something like:
  233.                 // ( | a b c) where it's all or nothing. Fortunately,
  234.                 // our current implementation claims that that case would
  235.                 // not allow empty, even if it did
  236.                 if (!$parent_def->child->allow_empty{
  237.                     // we need to do a double-check
  238.                     $i $parent_index;
  239.                     array_pop($stack);
  240.                 }
  241.                 
  242.                 // PROJECTED OPTIMIZATION: Process all children elements before
  243.                 // reprocessing parent node.
  244.                 
  245.             else {
  246.                 // replace node with $result
  247.                 
  248.                 // calculate length of inner tokens
  249.                 $length $j $i 1;
  250.                 
  251.                 if ($e{
  252.                     if (empty($result&& $length{
  253.                         $e->send(E_ERROR'Strategy_FixNesting: Node contents removed');
  254.                     else {
  255.                         $e->send(E_WARNING'Strategy_FixNesting: Node reorganized');
  256.                     }
  257.                 }
  258.                 
  259.                 // perform replacement
  260.                 array_splice($tokens$i 1$length$result);
  261.                 
  262.                 // update size
  263.                 $size -= $length;
  264.                 $size += count($result);
  265.                 
  266.                 // register start token as a parental node start
  267.                 $stack[$i;
  268.                 
  269.                 // register exclusions if there are any
  270.                 if (!empty($excludes)) $exclude_stack[$excludes;
  271.                 
  272.                 // move cursor to next possible start node
  273.                 $i++;
  274.                 
  275.             }
  276.             
  277.             //################################################################//
  278.             // Scroll to next start node
  279.             
  280.             // We assume, at this point, that $i is the index of the token
  281.             // that is the first possible new start point for a node.
  282.             
  283.             // Test if the token indeed is a start tag, if not, move forward
  284.             // and test again.
  285.             $size count($tokens);
  286.             while ($i $size and !$tokens[$iinstanceof HTMLPurifier_Token_Start{
  287.                 if ($tokens[$iinstanceof HTMLPurifier_Token_End{
  288.                     // pop a token index off the stack if we ended a node
  289.                     array_pop($stack);
  290.                     // pop an exclusion lookup off exclusion stack if
  291.                     // we ended node and that node had exclusions
  292.                     if ($i == || $i == $size 1{
  293.                         // use specialized var if it's the super-parent
  294.                         $s_excludes $definition->info_parent_def->excludes;
  295.                     else {
  296.                         $s_excludes $definition->info[$tokens[$i]->name]->excludes;
  297.                     }
  298.                     if ($s_excludes{
  299.                         array_pop($exclude_stack);
  300.                     }
  301.                 }
  302.                 $i++;
  303.             }
  304.             
  305.         }
  306.         
  307.         //####################################################################//
  308.         // Post-processing
  309.         
  310.         // remove implicit parent tokens at the beginning and end
  311.         array_shift($tokens);
  312.         array_pop($tokens);
  313.         
  314.         // remove context variables
  315.         $context->destroy('IsInline');
  316.         $context->destroy('CurrentToken');
  317.         
  318.         //####################################################################//
  319.         // Return
  320.         
  321.         return $tokens;
  322.         
  323.     }
  324.     
  325. }

Documentation generated on Thu, 19 Jun 2008 18:49:12 -0400 by phpDocumentor 1.4.2