/** * Asserts a specific result from a one parameter + config/context function * @param string $input Input parameter * @param bool|string $expect Expectation */ protected function assertResult($input, $expect = true) { // $func may cause $input to change, so "clone" another copy // to sacrifice if ($this->to_node_list && is_string($input)) { $input = HTMLPurifier_Arborize::arborize($this->tokenize($temp = $input), $this->config, $this->context)->children; $input_c = HTMLPurifier_Arborize::arborize($this->tokenize($temp), $this->config, $this->context)->children; } elseif ($this->to_tokens && is_string($input)) { $input = $this->tokenize($temp = $input); $input_c = $this->tokenize($temp); } else { $input_c = $input; } // call the function $func = $this->func; $result = $this->obj->{$func}($input_c, $this->config, $this->context); // test a bool result if (is_bool($result)) { $this->assertIdentical($expect, $result); return; } elseif (is_bool($expect)) { $expect = $input; } if ($this->to_html) { if ($this->to_node_list) { $result = $this->generateTokens($result); if (is_array($expect) && !empty($expect) && $expect[0] instanceof HTMLPurifier_Node) { $expect = $this->generateTokens($expect); } } $result = $this->generate($result); if (is_array($expect)) { $expect = $this->generate($expect); } } $this->assertIdentical($expect, $result); if ($expect !== $result) { echo '<pre>' . var_dump($result) . '</pre>'; } }
/** * @param HTMLPurifier_Token[] $tokens * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return array|HTMLPurifier_Token[] */ public function execute($tokens, $config, $context) { //####################################################################// // Pre-processing // O(n) pass to convert to a tree, so that we can efficiently // refer to substrings $top_node = HTMLPurifier_Arborize::arborize($tokens, $config, $context); // get a copy of the HTML definition $definition = $config->getHTMLDefinition(); $excludes_enabled = !$config->get('Core.DisableExcludes'); // setup the context variable 'IsInline', for chameleon processing // is 'false' when we are not inline, 'true' when it must always // be inline, and an integer when it is inline for a certain // branch of the document tree $is_inline = $definition->info_parent_def->descendants_are_inline; $context->register('IsInline', $is_inline); // setup error collector $e =& $context->get('ErrorCollector', true); //####################################################################// // Loop initialization // stack that contains all elements that are excluded // it is organized by parent elements, similar to $stack, // but it is only populated when an element with exclusions is // processed, i.e. there won't be empty exclusions. $exclude_stack = array($definition->info_parent_def->excludes); // variable that contains the start token while we are processing // nodes. This enables error reporting to do its job $node = $top_node; // dummy token list($token, $d) = $node->toTokenPair(); $context->register('CurrentNode', $node); $context->register('CurrentToken', $token); //####################################################################// // Loop // We need to implement a post-order traversal iteratively, to // avoid running into stack space limits. This is pretty tricky // to reason about, so we just manually stack-ify the recursive // variant: // // function f($node) { // foreach ($node->children as $child) { // f($child); // } // validate($node); // } // // Thus, we will represent a stack frame as array($node, // $is_inline, stack of children) // e.g. array_reverse($node->children) - already processed // children. $parent_def = $definition->info_parent_def; $stack = array(array($top_node, $parent_def->descendants_are_inline, $parent_def->excludes, 0)); while (!empty($stack)) { list($node, $is_inline, $excludes, $ix) = array_pop($stack); // recursive call $go = false; $def = empty($stack) ? $definition->info_parent_def : $definition->info[$node->name]; while (isset($node->children[$ix])) { $child = $node->children[$ix++]; if ($child instanceof HTMLPurifier_Node_Element) { $go = true; $stack[] = array($node, $is_inline, $excludes, $ix); $stack[] = array($child, $is_inline || $def->descendants_are_inline, empty($def->excludes) ? $excludes : array_merge($excludes, $def->excludes), 0); break; } } if ($go) { continue; } list($token, $d) = $node->toTokenPair(); // base case if ($excludes_enabled && isset($excludes[$node->name])) { $node->dead = true; if ($e) { $e->send(E_ERROR, 'Strategy_FixNesting: Node excluded'); } } else { // XXX I suppose it would be slightly more efficient to // avoid the allocation here and have children // strategies handle it $children = array(); foreach ($node->children as $child) { if (!$child->dead) { $children[] = $child; } } $result = $def->child->validateChildren($children, $config, $context); if ($result === true) { // nop $node->children = $children; } elseif ($result === false) { $node->dead = true; if ($e) { $e->send(E_ERROR, 'Strategy_FixNesting: Node removed'); } } else { $node->children = $result; if ($e) { // XXX This will miss mutations of internal nodes. Perhaps defer to the child validators if (empty($result) && !empty($children)) { $e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed'); } else { if ($result != $children) { $e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized'); } } } } } } //####################################################################// // Post-processing // remove context variables $context->destroy('IsInline'); $context->destroy('CurrentNode'); $context->destroy('CurrentToken'); //####################################################################// // Return return HTMLPurifier_Arborize::flatten($node, $config, $context); }
public function execute($tokens, $config, $context) { $top_node = HTMLPurifier_Arborize::arborize($tokens, $config, $context); $definition = $config->getHTMLDefinition(); $excludes_enabled = !$config->get('Core.DisableExcludes'); $is_inline = $definition->info_parent_def->descendants_are_inline; $context->register('IsInline', $is_inline); $e =& $context->get('ErrorCollector', true); $exclude_stack = array($definition->info_parent_def->excludes); $node = $top_node; list($token, $d) = $node->toTokenPair(); $context->register('CurrentNode', $node); $context->register('CurrentToken', $token); $parent_def = $definition->info_parent_def; $stack = array(array($top_node, $parent_def->descendants_are_inline, $parent_def->excludes, 0)); while (!empty($stack)) { list($node, $is_inline, $excludes, $ix) = array_pop($stack); $go = false; $def = empty($stack) ? $definition->info_parent_def : $definition->info[$node->name]; while (isset($node->children[$ix])) { $child = $node->children[$ix++]; if ($child instanceof HTMLPurifier_Node_Element) { $go = true; $stack[] = array($node, $is_inline, $excludes, $ix); $stack[] = array($child, $is_inline || $def->descendants_are_inline, empty($def->excludes) ? $excludes : array_merge($excludes, $def->excludes), 0); break; } } if ($go) { continue; } list($token, $d) = $node->toTokenPair(); if ($excludes_enabled && isset($excludes[$node->name])) { $node->dead = true; if ($e) { $e->send(E_ERROR, 'Strategy_FixNesting: Node excluded'); } } else { $children = array(); foreach ($node->children as $child) { if (!$child->dead) { $children[] = $child; } } $result = $def->child->validateChildren($children, $config, $context); if ($result === true) { $node->children = $children; } elseif ($result === false) { $node->dead = true; if ($e) { $e->send(E_ERROR, 'Strategy_FixNesting: Node removed'); } } else { $node->children = $result; if ($e) { if (empty($result) && !empty($children)) { $e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed'); } else { if ($result != $children) { $e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized'); } } } } } } $context->destroy('IsInline'); $context->destroy('CurrentNode'); $context->destroy('CurrentToken'); return HTMLPurifier_Arborize::flatten($node, $config, $context); }