vendor/store.shopware.com/swagb2bplatform/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php line 38

Open in your IDE?
  1. <?php
  2. /**
  3.  * Parser that uses PHP 5's DOM extension (part of the core).
  4.  *
  5.  * In PHP 5, the DOM XML extension was revamped into DOM and added to the core.
  6.  * It gives us a forgiving HTML parser, which we use to transform the HTML
  7.  * into a DOM, and then into the tokens.  It is blazingly fast (for large
  8.  * documents, it performs twenty times faster than
  9.  * HTMLPurifier_Lexer_DirectLex,and is the default choice for PHP 5.
  10.  *
  11.  * @note Any empty elements will have empty tokens associated with them, even if
  12.  * this is prohibited by the spec. This is cannot be fixed until the spec
  13.  * comes into play.
  14.  *
  15.  * @note PHP's DOM extension does not actually parse any entities, we use
  16.  *       our own function to do that.
  17.  *
  18.  * @warning DOM tends to drop whitespace, which may wreak havoc on indenting.
  19.  *          If this is a huge problem, due to the fact that HTML is hand
  20.  *          edited and you are unable to get a parser cache that caches the
  21.  *          the output of HTML Purifier while keeping the original HTML lying
  22.  *          around, you may want to run Tidy on the resulting output or use
  23.  *          HTMLPurifier_DirectLex
  24.  */
  25. class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
  26. {
  27.     /**
  28.      * @type HTMLPurifier_TokenFactory
  29.      */
  30.     private $factory;
  31.     public function __construct()
  32.     {
  33.         // setup the factory
  34.         parent::__construct();
  35.         $this->factory = new HTMLPurifier_TokenFactory();
  36.     }
  37.     /**
  38.      * @param string $html
  39.      * @param HTMLPurifier_Config $config
  40.      * @param HTMLPurifier_Context $context
  41.      * @return HTMLPurifier_Token[]
  42.      */
  43.     public function tokenizeHTML($html$config$context)
  44.     {
  45.         $html $this->normalize($html$config$context);
  46.         // attempt to armor stray angled brackets that cannot possibly
  47.         // form tags and thus are probably being used as emoticons
  48.         if ($config->get('Core.AggressivelyFixLt')) {
  49.             $char '[^a-z!\/]';
  50.             $comment "/<!--(.*?)(-->|\z)/is";
  51.             $html preg_replace_callback($comment, array($this'callbackArmorCommentEntities'), $html);
  52.             do {
  53.                 $old $html;
  54.                 $html preg_replace("/<($char)/i"'&lt;\\1'$html);
  55.             } while ($html !== $old);
  56.             $html preg_replace_callback($comment, array($this'callbackUndoCommentSubst'), $html); // fix comments
  57.         }
  58.         // preprocess html, essential for UTF-8
  59.         $html $this->wrapHTML($html$config$context);
  60.         $doc = new DOMDocument();
  61.         $doc->encoding 'UTF-8'// theoretically, the above has this covered
  62.         $options 0;
  63.         if ($config->get('Core.AllowParseManyTags') && defined('LIBXML_PARSEHUGE')) {
  64.             $options |= LIBXML_PARSEHUGE;
  65.         }
  66.         set_error_handler(array($this'muteErrorHandler'));
  67.         // loadHTML() fails on PHP 5.3 when second parameter is given
  68.         if ($options) {
  69.             $doc->loadHTML($html$options);
  70.         } else {
  71.             $doc->loadHTML($html);
  72.         }
  73.         restore_error_handler();
  74.         $body $doc->getElementsByTagName('html')->item(0)-> // <html>
  75.                       getElementsByTagName('body')->item(0);  // <body>
  76.         $div $body->getElementsByTagName('div')->item(0); // <div>
  77.         $tokens = array();
  78.         $this->tokenizeDOM($div$tokens$config);
  79.         // If the div has a sibling, that means we tripped across
  80.         // a premature </div> tag.  So remove the div we parsed,
  81.         // and then tokenize the rest of body.  We can't tokenize
  82.         // the sibling directly as we'll lose the tags in that case.
  83.         if ($div->nextSibling) {
  84.             $body->removeChild($div);
  85.             $this->tokenizeDOM($body$tokens$config);
  86.         }
  87.         return $tokens;
  88.     }
  89.     /**
  90.      * Iterative function that tokenizes a node, putting it into an accumulator.
  91.      * To iterate is human, to recurse divine - L. Peter Deutsch
  92.      * @param DOMNode $node DOMNode to be tokenized.
  93.      * @param HTMLPurifier_Token[] $tokens   Array-list of already tokenized tokens.
  94.      * @return HTMLPurifier_Token of node appended to previously passed tokens.
  95.      */
  96.     protected function tokenizeDOM($node, &$tokens$config)
  97.     {
  98.         $level 0;
  99.         $nodes = array($level => new HTMLPurifier_Queue(array($node)));
  100.         $closingNodes = array();
  101.         do {
  102.             while (!$nodes[$level]->isEmpty()) {
  103.                 $node $nodes[$level]->shift(); // FIFO
  104.                 $collect $level true false;
  105.                 $needEndingTag $this->createStartNode($node$tokens$collect$config);
  106.                 if ($needEndingTag) {
  107.                     $closingNodes[$level][] = $node;
  108.                 }
  109.                 if ($node->childNodes && $node->childNodes->length) {
  110.                     $level++;
  111.                     $nodes[$level] = new HTMLPurifier_Queue();
  112.                     foreach ($node->childNodes as $childNode) {
  113.                         $nodes[$level]->push($childNode);
  114.                     }
  115.                 }
  116.             }
  117.             $level--;
  118.             if ($level && isset($closingNodes[$level])) {
  119.                 while ($node array_pop($closingNodes[$level])) {
  120.                     $this->createEndNode($node$tokens);
  121.                 }
  122.             }
  123.         } while ($level 0);
  124.     }
  125.     /**
  126.      * Portably retrieve the tag name of a node; deals with older versions
  127.      * of libxml like 2.7.6
  128.      * @param DOMNode $node
  129.      */
  130.     protected function getTagName($node)
  131.     {
  132.         if (isset($node->tagName)) {
  133.             return $node->tagName;
  134.         } else if (isset($node->nodeName)) {
  135.             return $node->nodeName;
  136.         } else if (isset($node->localName)) {
  137.             return $node->localName;
  138.         }
  139.         return null;
  140.     }
  141.     /**
  142.      * Portably retrieve the data of a node; deals with older versions
  143.      * of libxml like 2.7.6
  144.      * @param DOMNode $node
  145.      */
  146.     protected function getData($node)
  147.     {
  148.         if (isset($node->data)) {
  149.             return $node->data;
  150.         } else if (isset($node->nodeValue)) {
  151.             return $node->nodeValue;
  152.         } else if (isset($node->textContent)) {
  153.             return $node->textContent;
  154.         }
  155.         return null;
  156.     }
  157.     /**
  158.      * @param DOMNode $node DOMNode to be tokenized.
  159.      * @param HTMLPurifier_Token[] $tokens   Array-list of already tokenized tokens.
  160.      * @param bool $collect  Says whether or start and close are collected, set to
  161.      *                    false at first recursion because it's the implicit DIV
  162.      *                    tag you're dealing with.
  163.      * @return bool if the token needs an endtoken
  164.      * @todo data and tagName properties don't seem to exist in DOMNode?
  165.      */
  166.     protected function createStartNode($node, &$tokens$collect$config)
  167.     {
  168.         // intercept non element nodes. WE MUST catch all of them,
  169.         // but we're not getting the character reference nodes because
  170.         // those should have been preprocessed
  171.         if ($node->nodeType === XML_TEXT_NODE) {
  172.             $data $this->getData($node); // Handle variable data property
  173.             if ($data !== null) {
  174.               $tokens[] = $this->factory->createText($data);
  175.             }
  176.             return false;
  177.         } elseif ($node->nodeType === XML_CDATA_SECTION_NODE) {
  178.             // undo libxml's special treatment of <script> and <style> tags
  179.             $last end($tokens);
  180.             $data $node->data;
  181.             // (note $node->tagname is already normalized)
  182.             if ($last instanceof HTMLPurifier_Token_Start && ($last->name == 'script' || $last->name == 'style')) {
  183.                 $new_data trim($data);
  184.                 if (substr($new_data04) === '<!--') {
  185.                     $data substr($new_data4);
  186.                     if (substr($data, -3) === '-->') {
  187.                         $data substr($data0, -3);
  188.                     } else {
  189.                         // Highly suspicious! Not sure what to do...
  190.                     }
  191.                 }
  192.             }
  193.             $tokens[] = $this->factory->createText($this->parseText($data$config));
  194.             return false;
  195.         } elseif ($node->nodeType === XML_COMMENT_NODE) {
  196.             // this is code is only invoked for comments in script/style in versions
  197.             // of libxml pre-2.6.28 (regular comments, of course, are still
  198.             // handled regularly)
  199.             $tokens[] = $this->factory->createComment($node->data);
  200.             return false;
  201.         } elseif ($node->nodeType !== XML_ELEMENT_NODE) {
  202.             // not-well tested: there may be other nodes we have to grab
  203.             return false;
  204.         }
  205.         $attr $node->hasAttributes() ? $this->transformAttrToAssoc($node->attributes) : array();
  206.         $tag_name $this->getTagName($node); // Handle variable tagName property
  207.         if (empty($tag_name)) {
  208.             return (bool) $node->childNodes->length;
  209.         }
  210.         // We still have to make sure that the element actually IS empty
  211.         if (!$node->childNodes->length) {
  212.             if ($collect) {
  213.                 $tokens[] = $this->factory->createEmpty($tag_name$attr);
  214.             }
  215.             return false;
  216.         } else {
  217.             if ($collect) {
  218.                 $tokens[] = $this->factory->createStart($tag_name$attr);
  219.             }
  220.             return true;
  221.         }
  222.     }
  223.     /**
  224.      * @param DOMNode $node
  225.      * @param HTMLPurifier_Token[] $tokens
  226.      */
  227.     protected function createEndNode($node, &$tokens)
  228.     {
  229.         $tag_name $this->getTagName($node); // Handle variable tagName property
  230.         $tokens[] = $this->factory->createEnd($tag_name);
  231.     }
  232.     /**
  233.      * Converts a DOMNamedNodeMap of DOMAttr objects into an assoc array.
  234.      *
  235.      * @param DOMNamedNodeMap $node_map DOMNamedNodeMap of DOMAttr objects.
  236.      * @return array Associative array of attributes.
  237.      */
  238.     protected function transformAttrToAssoc($node_map)
  239.     {
  240.         // NamedNodeMap is documented very well, so we're using undocumented
  241.         // features, namely, the fact that it implements Iterator and
  242.         // has a ->length attribute
  243.         if ($node_map->length === 0) {
  244.             return array();
  245.         }
  246.         $array = array();
  247.         foreach ($node_map as $attr) {
  248.             $array[$attr->name] = $attr->value;
  249.         }
  250.         return $array;
  251.     }
  252.     /**
  253.      * An error handler that mutes all errors
  254.      * @param int $errno
  255.      * @param string $errstr
  256.      */
  257.     public function muteErrorHandler($errno$errstr)
  258.     {
  259.     }
  260.     /**
  261.      * Callback function for undoing escaping of stray angled brackets
  262.      * in comments
  263.      * @param array $matches
  264.      * @return string
  265.      */
  266.     public function callbackUndoCommentSubst($matches)
  267.     {
  268.         return '<!--' strtr($matches[1], array('&amp;' => '&''&lt;' => '<')) . $matches[2];
  269.     }
  270.     /**
  271.      * Callback function that entity-izes ampersands in comments so that
  272.      * callbackUndoCommentSubst doesn't clobber them
  273.      * @param array $matches
  274.      * @return string
  275.      */
  276.     public function callbackArmorCommentEntities($matches)
  277.     {
  278.         return '<!--' str_replace('&''&amp;'$matches[1]) . $matches[2];
  279.     }
  280.     /**
  281.      * Wraps an HTML fragment in the necessary HTML
  282.      * @param string $html
  283.      * @param HTMLPurifier_Config $config
  284.      * @param HTMLPurifier_Context $context
  285.      * @return string
  286.      */
  287.     protected function wrapHTML($html$config$context$use_div true)
  288.     {
  289.         $def $config->getDefinition('HTML');
  290.         $ret '';
  291.         if (!empty($def->doctype->dtdPublic) || !empty($def->doctype->dtdSystem)) {
  292.             $ret .= '<!DOCTYPE html ';
  293.             if (!empty($def->doctype->dtdPublic)) {
  294.                 $ret .= 'PUBLIC "' $def->doctype->dtdPublic '" ';
  295.             }
  296.             if (!empty($def->doctype->dtdSystem)) {
  297.                 $ret .= '"' $def->doctype->dtdSystem '" ';
  298.             }
  299.             $ret .= '>';
  300.         }
  301.         $ret .= '<html><head>';
  302.         $ret .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
  303.         // No protection if $html contains a stray </div>!
  304.         $ret .= '</head><body>';
  305.         if ($use_div$ret .= '<div>';
  306.         $ret .= $html;
  307.         if ($use_div$ret .= '</div>';
  308.         $ret .= '</body></html>';
  309.         return $ret;
  310.     }
  311. }
  312. // vim: et sw=4 sts=4