Version 1
[yaffs-website] / vendor / ezyang / htmlpurifier / library / HTMLPurifier / Strategy / FixNesting.php
diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php
new file mode 100644 (file)
index 0000000..6fa673d
--- /dev/null
@@ -0,0 +1,181 @@
+<?php
+
+/**
+ * Takes a well formed list of tokens and fixes their nesting.
+ *
+ * HTML elements dictate which elements are allowed to be their children,
+ * for example, you can't have a p tag in a span tag.  Other elements have
+ * much more rigorous definitions: tables, for instance, require a specific
+ * order for their elements.  There are also constraints not expressible by
+ * document type definitions, such as the chameleon nature of ins/del
+ * tags and global child exclusions.
+ *
+ * The first major objective of this strategy is to iterate through all
+ * the nodes and determine whether or not their children conform to the
+ * element's definition.  If they do not, the child definition may
+ * optionally supply an amended list of elements that is valid or
+ * require that the entire node be deleted (and the previous node
+ * rescanned).
+ *
+ * The second objective is to ensure that explicitly excluded elements of
+ * an element do not appear in its children.  Code that accomplishes this
+ * task is pervasive through the strategy, though the two are distinct tasks
+ * and could, theoretically, be seperated (although it's not recommended).
+ *
+ * @note Whether or not unrecognized children are silently dropped or
+ *       translated into text depends on the child definitions.
+ *
+ * @todo Enable nodes to be bubbled out of the structure.  This is
+ *       easier with our new algorithm.
+ */
+
+class HTMLPurifier_Strategy_FixNesting extends HTMLPurifier_Strategy
+{
+
+    /**
+     * @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, // exclusions
+                  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,
+                        // ToDo: I don't think it matters if it's def or
+                        // child_def, but double check this...
+                        $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);
+    }
+}
+
+// vim: et sw=4 sts=4