Seditio Source
Root |
./othercms/phpBB3/vendor/s9e/text-formatter/src/Configurator/JavaScript/StylesheetCompressor.php
<?php

/**
* @package   s9e\TextFormatter
* @copyright Copyright (c) 2010-2021 The s9e authors
* @license   http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\JavaScript;

use
s9e\TextFormatter\Configurator\Helpers\RegexpBuilder;
use
s9e\TextFormatter\Configurator\JavaScript\Code;

class
StylesheetCompressor
{
   
/**
    * @var string[] List of regular expressions that match strings to deduplicate
    */
   
protected $deduplicateTargets = [
       
'<xsl:template match="',
       
'</xsl:template>',
       
'<xsl:apply-templates/>',
       
'<param name="allowfullscreen" value="true"/>',
       
'<xsl:value-of select="',
       
'<xsl:copy-of select="@',
       
'<iframe allowfullscreen="" scrolling="no"',
       
'display:block;overflow:hidden;position:relative;padding-bottom:',
       
'display:inline-block;width:100%;max-width:',
       
' [-:\\w]++="',
       
'\\{[^}]++\\}',
       
'@[-\\w]{4,}+',
       
'(?<=<)[-:\\w]{4,}+',
       
'(?<==")[^"]{4,}+"'
   
];

   
/**
    * @var array Associative array of string replacements as [match => replace]
    */
   
protected $dictionary;

   
/**
    * @var string Prefix used for dictionary keys
    */
   
protected $keyPrefix = '$';

   
/**
    * @var integer Number of bytes each global substitution must save to be considered
    */
   
public $minSaving = 10;

   
/**
    * @var array Associative array of [string => saving]
    */
   
protected $savings;

   
/**
    * @var string
    */
   
protected $xsl;

   
/**
    * Encode given stylesheet into a compact JavaScript representation
    *
    * @param  string $xsl Original stylesheet
    * @return string      JavaScript representation of the compressed stylesheet
    */
   
public function encode($xsl)
    {
       
$this->xsl = $xsl;

       
$this->estimateSavings();
       
$this->filterSavings();
       
$this->buildDictionary();

       
$str = $this->getCompressedStylesheet();

       
// Split the stylesheet's string into 2000 chars chunks to appease Google Closure Compiler
       
preg_match_all('(.{1,2000})su', $str, $matches);

       
$js = implode("+\n", array_map('json_encode', $matches[0]));
        if (!empty(
$this->dictionary))
        {
           
$js = '(' . $js . ').replace(' . $this->getReplacementRegexp() . ',function(k){return' . json_encode($this->dictionary) . '[k];})';
        }

        return
$js;
    }

   
/**
    * Build a dictionary of all cost-effective string replacements
    *
    * @return void
    */
   
protected function buildDictionary()
    {
       
$keys = $this->getAvailableKeys();
       
rsort($keys);

       
$this->dictionary = [];
       
arsort($this->savings);
        foreach (
array_keys($this->savings) as $str)
        {
           
$key = array_pop($keys);
            if (!
$key)
            {
                break;
            }

           
$this->dictionary[$key] = $str;
        }
    }

   
/**
    * Estimate the savings of every possible string replacement
    *
    * @return void
    */
   
protected function estimateSavings()
    {
       
$this->savings = [];
        foreach (
$this->getStringsFrequency() as $str => $cnt)
        {
           
$len             = strlen($str);
           
$originalCost    = $cnt * $len;
           
$replacementCost = $cnt * 2;
           
$overhead        = $len + 6;

           
$this->savings[$str] = $originalCost - ($replacementCost + $overhead);
        }
    }

   
/**
    * Filter the savings according to the minSaving property
    *
    * @return void
    */
   
protected function filterSavings()
    {
       
$this->savings = array_filter(
           
$this->savings,
            function (
$saving)
            {
                return (
$saving >= $this->minSaving);
            }
        );
    }

   
/**
    * Return all the possible dictionary keys that are not present in the original stylesheet
    *
    * @return string[]
    */
   
protected function getAvailableKeys()
    {
        return
array_diff($this->getPossibleKeys(), $this->getUnavailableKeys());
    }

   
/**
    * Return the stylesheet after dictionary replacements
    *
    * @return string
    */
   
protected function getCompressedStylesheet()
    {
        return
strtr($this->xsl, array_flip($this->dictionary));
    }

   
/**
    * Return a list of possible dictionary keys
    *
    * @return string[]
    */
   
protected function getPossibleKeys()
    {
       
$keys = [];
        foreach (
range('a', 'z') as $char)
        {
           
$keys[] = $this->keyPrefix . $char;
        }

        return
$keys;
    }

   
/**
    * Return a regexp that matches all used dictionary keys
    *
    * @return string
    */
   
protected function getReplacementRegexp()
    {
        return
'/' . RegexpBuilder::fromList(array_keys($this->dictionary)) . '/g';
    }

   
/**
    * Return the frequency of all deduplicatable strings
    *
    * @return array Array of [string => frequency]
    */
   
protected function getStringsFrequency()
    {
       
$regexp = '(' . implode('|', $this->deduplicateTargets) . ')S';
       
preg_match_all($regexp, $this->xsl, $matches);

        return
array_count_values($matches[0]);
    }

   
/**
    * Return the list of possible dictionary keys that appear in the original stylesheet
    *
    * @return string[]
    */
   
protected function getUnavailableKeys()
    {
       
preg_match_all('(' . preg_quote($this->keyPrefix) . '.)', $this->xsl, $matches);

        return
array_unique($matches[0]);
    }
}