vendor/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php line 67

Open in your IDE?
  1. <?php
  2. /**
  3.  * Generates HTML from tokens.
  4.  * @todo Refactor interface so that configuration/context is determined
  5.  *       upon instantiation, no need for messy generateFromTokens() calls
  6.  * @todo Make some of the more internal functions protected, and have
  7.  *       unit tests work around that
  8.  */
  9. class HTMLPurifier_Generator
  10. {
  11.     /**
  12.      * Whether or not generator should produce XML output.
  13.      * @type bool
  14.      */
  15.     private $_xhtml true;
  16.     /**
  17.      * :HACK: Whether or not generator should comment the insides of <script> tags.
  18.      * @type bool
  19.      */
  20.     private $_scriptFix false;
  21.     /**
  22.      * Cache of HTMLDefinition during HTML output to determine whether or
  23.      * not attributes should be minimized.
  24.      * @type HTMLPurifier_HTMLDefinition
  25.      */
  26.     private $_def;
  27.     /**
  28.      * Cache of %Output.SortAttr.
  29.      * @type bool
  30.      */
  31.     private $_sortAttr;
  32.     /**
  33.      * Cache of %Output.FlashCompat.
  34.      * @type bool
  35.      */
  36.     private $_flashCompat;
  37.     /**
  38.      * Cache of %Output.FixInnerHTML.
  39.      * @type bool
  40.      */
  41.     private $_innerHTMLFix;
  42.     /**
  43.      * Stack for keeping track of object information when outputting IE
  44.      * compatibility code.
  45.      * @type array
  46.      */
  47.     private $_flashStack = array();
  48.     /**
  49.      * Configuration for the generator
  50.      * @type HTMLPurifier_Config
  51.      */
  52.     protected $config;
  53.     /**
  54.      * @param HTMLPurifier_Config $config
  55.      * @param HTMLPurifier_Context $context
  56.      */
  57.     public function __construct($config$context)
  58.     {
  59.         $this->config $config;
  60.         $this->_scriptFix $config->get('Output.CommentScriptContents');
  61.         $this->_innerHTMLFix $config->get('Output.FixInnerHTML');
  62.         $this->_sortAttr $config->get('Output.SortAttr');
  63.         $this->_flashCompat $config->get('Output.FlashCompat');
  64.         $this->_def $config->getHTMLDefinition();
  65.         $this->_xhtml $this->_def->doctype->xml;
  66.     }
  67.     /**
  68.      * Generates HTML from an array of tokens.
  69.      * @param HTMLPurifier_Token[] $tokens Array of HTMLPurifier_Token
  70.      * @return string Generated HTML
  71.      */
  72.     public function generateFromTokens($tokens)
  73.     {
  74.         if (!$tokens) {
  75.             return '';
  76.         }
  77.         // Basic algorithm
  78.         $html '';
  79.         for ($i 0$size count($tokens); $i $size$i++) {
  80.             if ($this->_scriptFix && $tokens[$i]->name === 'script'
  81.                 && $i $size && $tokens[$i+2] instanceof HTMLPurifier_Token_End) {
  82.                 // script special case
  83.                 // the contents of the script block must be ONE token
  84.                 // for this to work.
  85.                 $html .= $this->generateFromToken($tokens[$i++]);
  86.                 $html .= $this->generateScriptFromToken($tokens[$i++]);
  87.             }
  88.             $html .= $this->generateFromToken($tokens[$i]);
  89.         }
  90.         // Tidy cleanup
  91.         if (extension_loaded('tidy') && $this->config->get('Output.TidyFormat')) {
  92.             $tidy = new Tidy;
  93.             $tidy->parseString(
  94.                 $html,
  95.                 array(
  96.                    'indent'=> true,
  97.                    'output-xhtml' => $this->_xhtml,
  98.                    'show-body-only' => true,
  99.                    'indent-spaces' => 2,
  100.                    'wrap' => 68,
  101.                 ),
  102.                 'utf8'
  103.             );
  104.             $tidy->cleanRepair();
  105.             $html = (string) $tidy// explicit cast necessary
  106.         }
  107.         // Normalize newlines to system defined value
  108.         if ($this->config->get('Core.NormalizeNewlines')) {
  109.             $nl $this->config->get('Output.Newline');
  110.             if ($nl === null) {
  111.                 $nl PHP_EOL;
  112.             }
  113.             if ($nl !== "\n") {
  114.                 $html str_replace("\n"$nl$html);
  115.             }
  116.         }
  117.         return $html;
  118.     }
  119.     /**
  120.      * Generates HTML from a single token.
  121.      * @param HTMLPurifier_Token $token HTMLPurifier_Token object.
  122.      * @return string Generated HTML
  123.      */
  124.     public function generateFromToken($token)
  125.     {
  126.         if (!$token instanceof HTMLPurifier_Token) {
  127.             trigger_error('Cannot generate HTML from non-HTMLPurifier_Token object'E_USER_WARNING);
  128.             return '';
  129.         } elseif ($token instanceof HTMLPurifier_Token_Start) {
  130.             $attr $this->generateAttributes($token->attr$token->name);
  131.             if ($this->_flashCompat) {
  132.                 if ($token->name == "object") {
  133.                     $flash = new stdClass();
  134.                     $flash->attr $token->attr;
  135.                     $flash->param = array();
  136.                     $this->_flashStack[] = $flash;
  137.                 }
  138.             }
  139.             return '<' $token->name . ($attr ' ' '') . $attr '>';
  140.         } elseif ($token instanceof HTMLPurifier_Token_End) {
  141.             $_extra '';
  142.             if ($this->_flashCompat) {
  143.                 if ($token->name == "object" && !empty($this->_flashStack)) {
  144.                     // doesn't do anything for now
  145.                 }
  146.             }
  147.             return $_extra '</' $token->name '>';
  148.         } elseif ($token instanceof HTMLPurifier_Token_Empty) {
  149.             if ($this->_flashCompat && $token->name == "param" && !empty($this->_flashStack)) {
  150.                 $this->_flashStack[count($this->_flashStack)-1]->param[$token->attr['name']] = $token->attr['value'];
  151.             }
  152.             $attr $this->generateAttributes($token->attr$token->name);
  153.              return '<' $token->name . ($attr ' ' '') . $attr .
  154.                 ( $this->_xhtml ' /''' // <br /> v. <br>
  155.                 '>';
  156.         } elseif ($token instanceof HTMLPurifier_Token_Text) {
  157.             return $this->escape($token->dataENT_NOQUOTES);
  158.         } elseif ($token instanceof HTMLPurifier_Token_Comment) {
  159.             return '<!--' $token->data '-->';
  160.         } else {
  161.             return '';
  162.         }
  163.     }
  164.     /**
  165.      * Special case processor for the contents of script tags
  166.      * @param HTMLPurifier_Token $token HTMLPurifier_Token object.
  167.      * @return string
  168.      * @warning This runs into problems if there's already a literal
  169.      *          --> somewhere inside the script contents.
  170.      */
  171.     public function generateScriptFromToken($token)
  172.     {
  173.         if (!$token instanceof HTMLPurifier_Token_Text) {
  174.             return $this->generateFromToken($token);
  175.         }
  176.         // Thanks <http://lachy.id.au/log/2005/05/script-comments>
  177.         $data preg_replace('#//\s*$#'''$token->data);
  178.         return '<!--//--><![CDATA[//><!--' "\n" trim($data) . "\n" '//--><!]]>';
  179.     }
  180.     /**
  181.      * Generates attribute declarations from attribute array.
  182.      * @note This does not include the leading or trailing space.
  183.      * @param array $assoc_array_of_attributes Attribute array
  184.      * @param string $element Name of element attributes are for, used to check
  185.      *        attribute minimization.
  186.      * @return string Generated HTML fragment for insertion.
  187.      */
  188.     public function generateAttributes($assoc_array_of_attributes$element '')
  189.     {
  190.         $html '';
  191.         if ($this->_sortAttr) {
  192.             ksort($assoc_array_of_attributes);
  193.         }
  194.         foreach ($assoc_array_of_attributes as $key => $value) {
  195.             if (!$this->_xhtml) {
  196.                 // Remove namespaced attributes
  197.                 if (strpos($key':') !== false) {
  198.                     continue;
  199.                 }
  200.                 // Check if we should minimize the attribute: val="val" -> val
  201.                 if ($element && !empty($this->_def->info[$element]->attr[$key]->minimized)) {
  202.                     $html .= $key ' ';
  203.                     continue;
  204.                 }
  205.             }
  206.             // Workaround for Internet Explorer innerHTML bug.
  207.             // Essentially, Internet Explorer, when calculating
  208.             // innerHTML, omits quotes if there are no instances of
  209.             // angled brackets, quotes or spaces.  However, when parsing
  210.             // HTML (for example, when you assign to innerHTML), it
  211.             // treats backticks as quotes.  Thus,
  212.             //      <img alt="``" />
  213.             // becomes
  214.             //      <img alt=`` />
  215.             // becomes
  216.             //      <img alt='' />
  217.             // Fortunately, all we need to do is trigger an appropriate
  218.             // quoting style, which we do by adding an extra space.
  219.             // This also is consistent with the W3C spec, which states
  220.             // that user agents may ignore leading or trailing
  221.             // whitespace (in fact, most don't, at least for attributes
  222.             // like alt, but an extra space at the end is barely
  223.             // noticeable).  Still, we have a configuration knob for
  224.             // this, since this transformation is not necesary if you
  225.             // don't process user input with innerHTML or you don't plan
  226.             // on supporting Internet Explorer.
  227.             if ($this->_innerHTMLFix) {
  228.                 if (strpos($value'`') !== false) {
  229.                     // check if correct quoting style would not already be
  230.                     // triggered
  231.                     if (strcspn($value'"\' <>') === strlen($value)) {
  232.                         // protect!
  233.                         $value .= ' ';
  234.                     }
  235.                 }
  236.             }
  237.             $html .= $key.'="'.$this->escape($value).'" ';
  238.         }
  239.         return rtrim($html);
  240.     }
  241.     /**
  242.      * Escapes raw text data.
  243.      * @todo This really ought to be protected, but until we have a facility
  244.      *       for properly generating HTML here w/o using tokens, it stays
  245.      *       public.
  246.      * @param string $string String data to escape for HTML.
  247.      * @param int $quote Quoting style, like htmlspecialchars. ENT_NOQUOTES is
  248.      *               permissible for non-attribute output.
  249.      * @return string escaped data.
  250.      */
  251.     public function escape($string$quote null)
  252.     {
  253.         // Workaround for APC bug on Mac Leopard reported by sidepodcast
  254.         // http://htmlpurifier.org/phorum/read.php?3,4823,4846
  255.         if ($quote === null) {
  256.             $quote ENT_COMPAT;
  257.         }
  258.         return htmlspecialchars($string$quote'UTF-8');
  259.     }
  260. }
  261. // vim: et sw=4 sts=4