Ajout du FR

Ajout du FR + correction du "functions.php"
This commit is contained in:
Gauvain Boiché
2020-03-30 14:52:34 +02:00
commit 5e4c5f9418
4541 changed files with 608941 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator;
use s9e\TextFormatter\Configurator;
abstract class Bundle
{
abstract public function configure(Configurator $configurator);
public static function getConfigurator()
{
$configurator = new Configurator;
$bundle = new static;
$bundle->configure($configurator);
return $configurator;
}
public static function getOptions()
{
return [];
}
}

View File

@@ -0,0 +1,113 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Bundles;
use s9e\TextFormatter\Configurator;
use s9e\TextFormatter\Configurator\Bundle;
class Fatdown extends Bundle
{
public function configure(Configurator $configurator)
{
$configurator->urlConfig->allowScheme('ftp');
$configurator->Litedown->decodeHtmlEntities = \true;
$configurator->Autoemail;
$configurator->Autolink;
$configurator->Escaper;
$configurator->FancyPants;
$configurator->HTMLComments;
$configurator->HTMLEntities;
$configurator->PipeTables;
$htmlAliases = [
'a' => ['URL', 'href' => 'url'],
'hr' => 'HR',
'em' => 'EM',
's' => 'S',
'strong' => 'STRONG',
'sup' => 'SUP'
];
foreach ($htmlAliases as $elName => $alias)
if (\is_array($alias))
{
$configurator->HTMLElements->aliasElement($elName, $alias[0]);
unset($alias[0]);
foreach ($alias as $attrName => $alias)
$configurator->HTMLElements->aliasAttribute($elName, $attrName, $alias);
}
else
$configurator->HTMLElements->aliasElement($elName, $alias);
$htmlElements = [
'abbr' => ['title'],
'b',
'br',
'code',
'dd',
'del',
'div' => ['class'],
'dl',
'dt',
'i',
'img' => ['alt', 'height', 'src', 'title', 'width'],
'ins',
'li',
'ol',
'pre',
'rb',
'rp',
'rt',
'rtc',
'ruby',
'span' => ['class'],
'strong',
'sub',
'sup',
'table',
'tbody',
'td' => ['colspan', 'rowspan'],
'tfoot',
'th' => ['colspan', 'rowspan', 'scope'],
'thead',
'tr',
'u',
'ul'
];
foreach ($htmlElements as $k => $v)
{
if (\is_numeric($k))
{
$elName = $v;
$attrNames = [];
}
else
{
$elName = $k;
$attrNames = $v;
}
$configurator->HTMLElements->allowElement($elName);
foreach ($attrNames as $attrName)
$configurator->HTMLElements->allowAttribute($elName, $attrName);
}
$configurator->tags['html:dd']->rules->createParagraphs(\false);
$configurator->tags['html:dt']->rules->createParagraphs(\false);
$configurator->tags['html:td']->rules->createParagraphs(\false);
$configurator->tags['html:th']->rules->createParagraphs(\false);
$configurator->plugins->load('MediaEmbed', ['createMediaBBCode' => \false]);
$sites = [
'bandcamp',
'dailymotion',
'facebook',
'liveleak',
'soundcloud',
'spotify',
'twitch',
'vimeo',
'vine',
'youtube'
];
foreach ($sites as $site)
$configurator->MediaEmbed->add($site);
}
}

View File

@@ -0,0 +1,87 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Bundles;
use s9e\TextFormatter\Configurator;
use s9e\TextFormatter\Configurator\Bundle;
class Forum extends Bundle
{
public function configure(Configurator $configurator)
{
$configurator->rootRules->enableAutoLineBreaks();
$configurator->BBCodes->addFromRepository('B');
$configurator->BBCodes->addFromRepository('CENTER');
$configurator->BBCodes->addFromRepository('CODE');
$configurator->BBCodes->addFromRepository('COLOR');
$configurator->BBCodes->addFromRepository('EMAIL');
$configurator->BBCodes->addFromRepository('FONT');
$configurator->BBCodes->addFromRepository('I');
$configurator->BBCodes->addFromRepository('IMG');
$configurator->BBCodes->addFromRepository('LIST');
$configurator->BBCodes->addFromRepository('*');
$configurator->BBCodes->add('LI');
$configurator->BBCodes->addFromRepository('OL');
$configurator->BBCodes->addFromRepository('QUOTE', 'default', [
'authorStr' => '<xsl:value-of select="@author"/> <xsl:value-of select="$L_WROTE"/>'
]);
$configurator->BBCodes->addFromRepository('S');
$configurator->BBCodes->addFromRepository('SIZE');
$configurator->BBCodes->addFromRepository('SPOILER', 'default', [
'hideStr' => '{L_HIDE}',
'showStr' => '{L_SHOW}',
'spoilerStr' => '{L_SPOILER}',
]);
$configurator->BBCodes->addFromRepository('TABLE');
$configurator->BBCodes->addFromRepository('TD');
$configurator->BBCodes->addFromRepository('TH');
$configurator->BBCodes->addFromRepository('TR');
$configurator->BBCodes->addFromRepository('U');
$configurator->BBCodes->addFromRepository('UL');
$configurator->BBCodes->addFromRepository('URL');
$configurator->rendering->parameters = [
'L_WROTE' => 'wrote:',
'L_HIDE' => 'Hide',
'L_SHOW' => 'Show',
'L_SPOILER' => 'Spoiler'
];
$emoticons = [
':)' => '1F642',
':-)' => '1F642',
';)' => '1F609',
';-)' => '1F609',
':D' => '1F600',
':-D' => '1F600',
':(' => '2639',
':-(' => '2639',
':-*' => '1F618',
':P' => '1F61B',
':-P' => '1F61B',
':p' => '1F61B',
':-p' => '1F61B',
';P' => '1F61C',
';-P' => '1F61C',
';p' => '1F61C',
';-p' => '1F61C',
':?' => '1F615',
':-?' => '1F615',
':|' => '1F610',
':-|' => '1F610',
':o' => '1F62E',
':lol:' => '1F602'
];
foreach ($emoticons as $code => $hex)
$configurator->Emoji->addAlias($code, \html_entity_decode('&#x' . $hex . ';'));
$sites = ['bandcamp', 'dailymotion', 'facebook', 'indiegogo', 'instagram', 'kickstarter', 'liveleak', 'soundcloud', 'twitch', 'twitter', 'vimeo', 'vine', 'wshh', 'youtube'];
foreach ($sites as $siteId)
{
$configurator->MediaEmbed->add($siteId);
$configurator->BBCodes->add($siteId, ['contentAttributes' => ['id', 'url']]);
}
$configurator->Autoemail;
$configurator->Autolink;
}
}

View File

@@ -0,0 +1,24 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Bundles;
use DOMDocument;
use s9e\TextFormatter\Configurator;
use s9e\TextFormatter\Configurator\Bundle;
class MediaPack extends Bundle
{
public function configure(Configurator $configurator)
{
if (!isset($configurator->MediaEmbed))
{
$pluginOptions = ['createMediaBBCode' => isset($configurator->BBCodes)];
$configurator->plugins->load('MediaEmbed', $pluginOptions);
}
foreach ($configurator->MediaEmbed->defaultSites as $siteId => $siteConfig)
$configurator->MediaEmbed->add($siteId);
}
}

View File

@@ -0,0 +1,22 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Collections;
use s9e\TextFormatter\Configurator\Validators\AttributeName;
class AttributeList extends NormalizedList
{
public function normalizeValue($attrName)
{
return AttributeName::normalize($attrName);
}
public function asConfig()
{
$list = \array_unique($this->items);
\sort($list);
return $list;
}
}

View File

@@ -0,0 +1,33 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Collections;
use InvalidArgumentException;
use ReflectionClass;
use s9e\TextFormatter\Configurator\JavaScript\Minifier;
class MinifierList extends NormalizedList
{
public function normalizeValue($minifier)
{
if (\is_string($minifier))
$minifier = $this->getMinifierInstance($minifier);
elseif (\is_array($minifier) && !empty($minifier[0]))
$minifier = $this->getMinifierInstance($minifier[0], \array_slice($minifier, 1));
if (!($minifier instanceof Minifier))
throw new InvalidArgumentException('Invalid minifier ' . \var_export($minifier, \true));
return $minifier;
}
protected function getMinifierInstance($name, array $args = [])
{
$className = 's9e\\TextFormatter\\Configurator\\JavaScript\\Minifiers\\' . $name;
if (!\class_exists($className))
throw new InvalidArgumentException('Invalid minifier ' . \var_export($name, \true));
$reflection = new ReflectionClass($className);
$minifier = (empty($args)) ? $reflection->newInstance() : $reflection->newInstanceArgs($args);
return $minifier;
}
}

View File

@@ -0,0 +1,22 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Collections;
use s9e\TextFormatter\Configurator\Validators\TagName;
class TagList extends NormalizedList
{
public function normalizeValue($attrName)
{
return TagName::normalize($attrName);
}
public function asConfig()
{
$list = \array_unique($this->items);
\sort($list);
return $list;
}
}

View File

@@ -0,0 +1,32 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Exceptions;
use DOMNode;
use RuntimeException;
use s9e\TextFormatter\Configurator\Helpers\TemplateHelper;
class UnsafeTemplateException extends RuntimeException
{
protected $node;
public function __construct($msg, DOMNode $node)
{
parent::__construct($msg);
$this->node = $node;
}
public function getNode()
{
return $this->node;
}
public function highlightNode($prepend = '<span style="background-color:#ff0">', $append = '</span>')
{
return TemplateHelper::highlightNode($this->node, $prepend, $append);
}
public function setNode(DOMNode $node)
{
$this->node = $node;
}
}

View File

@@ -0,0 +1,23 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Helpers;
abstract class ContextSafeness
{
public static function getDisallowedCharactersAsURL()
{
return [':'];
}
public static function getDisallowedCharactersInCSS()
{
return ['(', ')', ':', '\\', '"', "'", ';', '{', '}'];
}
public static function getDisallowedCharactersInJS()
{
return ['(', ')', '"', "'", '\\', "\r", "\n", "\xE2\x80\xA8", "\xE2\x80\xA9", ':', '%', '='];
}
}

View File

@@ -0,0 +1,232 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Helpers;
use RuntimeException;
abstract class RegexpParser
{
public static function getAllowedCharacterRegexp($regexp)
{
$def = self::parse($regexp);
if (\strpos($def['modifiers'], 'm') !== \false)
return '//';
if (\substr($def['regexp'], 0, 1) !== '^'
|| \substr($def['regexp'], -1) !== '$')
return '//';
$def['tokens'][] = [
'pos' => \strlen($def['regexp']),
'len' => 0,
'type' => 'end'
];
$patterns = [];
$literal = '';
$pos = 0;
$skipPos = 0;
$depth = 0;
foreach ($def['tokens'] as $token)
{
if ($token['type'] === 'option')
$skipPos = \max($skipPos, $token['pos'] + $token['len']);
if (\strpos($token['type'], 'AssertionStart') !== \false)
{
$endToken = $def['tokens'][$token['endToken']];
$skipPos = \max($skipPos, $endToken['pos'] + $endToken['len']);
}
if ($token['pos'] >= $skipPos)
{
if ($token['type'] === 'characterClass')
$patterns[] = '[' . $token['content'] . ']';
if ($token['pos'] > $pos)
{
$tmp = \substr($def['regexp'], $pos, $token['pos'] - $pos);
$literal .= $tmp;
if (!$depth)
{
$tmp = \str_replace('\\\\', '', $tmp);
if (\preg_match('/(?<!\\\\)\\|(?!\\^)/', $tmp))
return '//';
if (\preg_match('/(?<![$\\\\])\\|/', $tmp))
return '//';
}
}
}
if (\substr($token['type'], -5) === 'Start')
++$depth;
elseif (\substr($token['type'], -3) === 'End')
--$depth;
$pos = \max($skipPos, $token['pos'] + $token['len']);
}
if (\preg_match('#(?<!\\\\)(?:\\\\\\\\)*\\.#', $literal))
{
if (\strpos($def['modifiers'], 's') !== \false
|| \strpos($literal, "\n") !== \false)
return '//';
$patterns[] = '.';
$literal = \preg_replace('#(?<!\\\\)((?:\\\\\\\\)*)\\.#', '$1', $literal);
}
$literal = \preg_replace('#(?<!\\\\)((?:\\\\\\\\)*)[*+?]#', '$1', $literal);
$literal = \preg_replace('#(?<!\\\\)((?:\\\\\\\\)*)\\{[^}]+\\}#', '$1', $literal);
$literal = \preg_replace('#(?<!\\\\)((?:\\\\\\\\)*)\\\\[bBAZzG1-9]#', '$1', $literal);
$literal = \preg_replace('#(?<!\\\\)((?:\\\\\\\\)*)[$^|]#', '$1', $literal);
$literal = \preg_replace('#(?<!\\\\)((?:\\\\\\\\)*)([-^\\]])#', '$1\\\\$2', $literal);
if (\strpos($def['modifiers'], 'D') === \false)
$literal .= "\n";
if ($literal !== '')
$patterns[] = '[' . $literal . ']';
if (empty($patterns))
return '/^$/D';
$regexp = $def['delimiter'] . \implode('|', $patterns) . $def['delimiter'];
if (\strpos($def['modifiers'], 'i') !== \false)
$regexp .= 'i';
if (\strpos($def['modifiers'], 'u') !== \false)
$regexp .= 'u';
return $regexp;
}
public static function getCaptureNames($regexp)
{
$map = [''];
$regexpInfo = self::parse($regexp);
foreach ($regexpInfo['tokens'] as $tok)
if ($tok['type'] === 'capturingSubpatternStart')
$map[] = (isset($tok['name'])) ? $tok['name'] : '';
return $map;
}
public static function parse($regexp)
{
if (!\preg_match('#^(.)(.*?)\\1([a-zA-Z]*)$#Ds', $regexp, $m))
throw new RuntimeException('Could not parse regexp delimiters');
$ret = [
'delimiter' => $m[1],
'modifiers' => $m[3],
'regexp' => $m[2],
'tokens' => []
];
$regexp = $m[2];
$openSubpatterns = [];
$pos = 0;
$regexpLen = \strlen($regexp);
while ($pos < $regexpLen)
{
switch ($regexp[$pos])
{
case '\\':
$pos += 2;
break;
case '[':
if (!\preg_match('#\\[(.*?(?<!\\\\)(?:\\\\\\\\)*+)\\]((?:[+*][+?]?|\\?)?)#A', $regexp, $m, 0, $pos))
throw new RuntimeException('Could not find matching bracket from pos ' . $pos);
$ret['tokens'][] = [
'pos' => $pos,
'len' => \strlen($m[0]),
'type' => 'characterClass',
'content' => $m[1],
'quantifiers' => $m[2]
];
$pos += \strlen($m[0]);
break;
case '(':
if (\preg_match('#\\(\\?([a-z]*)\\)#iA', $regexp, $m, 0, $pos))
{
$ret['tokens'][] = [
'pos' => $pos,
'len' => \strlen($m[0]),
'type' => 'option',
'options' => $m[1]
];
$pos += \strlen($m[0]);
break;
}
if (\preg_match("#(?J)\\(\\?(?:P?<(?<name>[a-z_0-9]+)>|'(?<name>[a-z_0-9]+)')#A", $regexp, $m, \PREG_OFFSET_CAPTURE, $pos))
{
$tok = [
'pos' => $pos,
'len' => \strlen($m[0][0]),
'type' => 'capturingSubpatternStart',
'name' => $m['name'][0]
];
$pos += \strlen($m[0][0]);
}
elseif (\preg_match('#\\(\\?([a-z]*):#iA', $regexp, $m, 0, $pos))
{
$tok = [
'pos' => $pos,
'len' => \strlen($m[0]),
'type' => 'nonCapturingSubpatternStart',
'options' => $m[1]
];
$pos += \strlen($m[0]);
}
elseif (\preg_match('#\\(\\?>#iA', $regexp, $m, 0, $pos))
{
$tok = [
'pos' => $pos,
'len' => \strlen($m[0]),
'type' => 'nonCapturingSubpatternStart',
'subtype' => 'atomic'
];
$pos += \strlen($m[0]);
}
elseif (\preg_match('#\\(\\?(<?[!=])#A', $regexp, $m, 0, $pos))
{
$assertions = [
'=' => 'lookahead',
'<=' => 'lookbehind',
'!' => 'negativeLookahead',
'<!' => 'negativeLookbehind'
];
$tok = [
'pos' => $pos,
'len' => \strlen($m[0]),
'type' => $assertions[$m[1]] . 'AssertionStart'
];
$pos += \strlen($m[0]);
}
elseif (\preg_match('#\\(\\?#A', $regexp, $m, 0, $pos))
throw new RuntimeException('Unsupported subpattern type at pos ' . $pos);
else
{
$tok = [
'pos' => $pos,
'len' => 1,
'type' => 'capturingSubpatternStart'
];
++$pos;
}
$openSubpatterns[] = \count($ret['tokens']);
$ret['tokens'][] = $tok;
break;
case ')':
if (empty($openSubpatterns))
throw new RuntimeException('Could not find matching pattern start for right parenthesis at pos ' . $pos);
$k = \array_pop($openSubpatterns);
$startToken =& $ret['tokens'][$k];
$startToken['endToken'] = \count($ret['tokens']);
$startToken['content'] = \substr(
$regexp,
$startToken['pos'] + $startToken['len'],
$pos - ($startToken['pos'] + $startToken['len'])
);
$spn = \strspn($regexp, '+*?', 1 + $pos);
$quantifiers = \substr($regexp, 1 + $pos, $spn);
$ret['tokens'][] = [
'pos' => $pos,
'len' => 1 + $spn,
'type' => \substr($startToken['type'], 0, -5) . 'End',
'quantifiers' => $quantifiers
];
unset($startToken);
$pos += 1 + $spn;
break;
default:
++$pos;
}
}
if (!empty($openSubpatterns))
throw new RuntimeException('Could not find matching pattern end for left parenthesis at pos ' . $ret['tokens'][$openSubpatterns[0]]['pos']);
return $ret;
}
}

View File

@@ -0,0 +1,82 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Helpers;
use DOMAttr;
use DOMDocument;
use DOMText;
use DOMXPath;
abstract class TemplateModifier
{
const XMLNS_XSL = 'http://www.w3.org/1999/XSL/Transform';
public static function replaceTokens($template, $regexp, $fn)
{
$dom = TemplateLoader::load($template);
$xpath = new DOMXPath($dom);
foreach ($xpath->query('//@*') as $attribute)
self::replaceTokensInAttribute($attribute, $regexp, $fn);
foreach ($xpath->query('//text()') as $node)
self::replaceTokensInText($node, $regexp, $fn);
return TemplateLoader::save($dom);
}
protected static function createReplacementNode(DOMDocument $dom, array $replacement)
{
if ($replacement[0] === 'expression')
{
$newNode = $dom->createElementNS(self::XMLNS_XSL, 'xsl:value-of');
$newNode->setAttribute('select', $replacement[1]);
}
elseif ($replacement[0] === 'passthrough')
{
$newNode = $dom->createElementNS(self::XMLNS_XSL, 'xsl:apply-templates');
if (isset($replacement[1]))
$newNode->setAttribute('select', $replacement[1]);
}
else
$newNode = $dom->createTextNode($replacement[1]);
return $newNode;
}
protected static function replaceTokensInAttribute(DOMAttr $attribute, $regexp, $fn)
{
$attrValue = \preg_replace_callback(
$regexp,
function ($m) use ($fn, $attribute)
{
$replacement = $fn($m, $attribute);
if ($replacement[0] === 'expression' || $replacement[0] === 'passthrough')
{
$replacement[] = '.';
return '{' . $replacement[1] . '}';
}
else
return $replacement[1];
},
$attribute->value
);
$attribute->value = \htmlspecialchars($attrValue, \ENT_COMPAT, 'UTF-8');
}
protected static function replaceTokensInText(DOMText $node, $regexp, $fn)
{
$parentNode = $node->parentNode;
$dom = $node->ownerDocument;
\preg_match_all($regexp, $node->textContent, $matches, \PREG_SET_ORDER | \PREG_OFFSET_CAPTURE);
$lastPos = 0;
foreach ($matches as $m)
{
$pos = $m[0][1];
$text = \substr($node->textContent, $lastPos, $pos - $lastPos);
$parentNode->insertBefore($dom->createTextNode($text), $node);
$lastPos = $pos + \strlen($m[0][0]);
$_m=[];foreach($m as $v)$_m[]=$v[0];$replacement = $fn($_m, $node);
$newNode = self::createReplacementNode($dom, $replacement);
$parentNode->insertBefore($newNode, $node);
}
$text = \substr($node->textContent, $lastPos);
$parentNode->insertBefore($dom->createTextNode($text), $node);
$parentNode->removeChild($node);
}
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items\AttributeFilters;
class AlnumFilter extends RegexpFilter
{
public function __construct()
{
parent::__construct('/^[0-9A-Za-z]+$/D');
$this->markAsSafeAsURL();
$this->markAsSafeInCSS();
}
}

View File

@@ -0,0 +1,31 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items\AttributeFilters;
use InvalidArgumentException;
use s9e\TextFormatter\Configurator\Helpers\RegexpBuilder;
class ChoiceFilter extends RegexpFilter
{
public function __construct(array $values = \null, $caseSensitive = \false)
{
parent::__construct();
if (isset($values))
$this->setValues($values, $caseSensitive);
}
public function setValues(array $values, $caseSensitive = \false)
{
if (!\is_bool($caseSensitive))
throw new InvalidArgumentException('Argument 2 passed to ' . __METHOD__ . ' must be a boolean');
$regexp = RegexpBuilder::fromList($values, ['delimiter' => '/']);
$regexp = '/^' . $regexp . '$/D';
if (!$caseSensitive)
$regexp .= 'i';
if (!\preg_match('#^[[:ascii:]]*$#D', $regexp))
$regexp .= 'u';
$this->setRegexp($regexp);
}
}

View File

@@ -0,0 +1,16 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items\AttributeFilters;
class ColorFilter extends RegexpFilter
{
public function __construct()
{
parent::__construct('/^(?:#(?:(?:[0-9a-f]{3}){1,2}|(?:[0-9a-f]{4}){1,2})|rgb\\(\\d{1,3}, *\\d{1,3}, *\\d{1,3}\\)|rgba\\(\\d{1,3}, *\\d{1,3}, *\\d{1,3}, *\\d*(?:\\.\\d+)?\\)|[a-z]+)$/Di');
$this->markAsSafeInCSS();
}
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items\AttributeFilters;
use s9e\TextFormatter\Configurator\Items\AttributeFilter;
class EmailFilter extends AttributeFilter
{
public function __construct()
{
parent::__construct('s9e\\TextFormatter\\Parser\\AttributeFilters\\EmailFilter::filter');
$this->setJS('EmailFilter.filter');
}
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items\AttributeFilters;
use s9e\TextFormatter\Configurator\Items\AttributeFilter;
class FalseFilter extends AttributeFilter
{
public function __construct()
{
parent::__construct('s9e\\TextFormatter\\Parser\\AttributeFilters\\FalseFilter::filter');
$this->setJS('FalseFilter.filter');
}
}

View File

@@ -0,0 +1,20 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items\AttributeFilters;
use s9e\TextFormatter\Configurator\Items\AttributeFilter;
class FloatFilter extends AttributeFilter
{
public function __construct()
{
parent::__construct('s9e\\TextFormatter\\Parser\\AttributeFilters\\NumericFilter::filterFloat');
$this->setJS('NumericFilter.filterFloat');
$this->markAsSafeAsURL();
$this->markAsSafeInCSS();
$this->markAsSafeInJS();
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items\AttributeFilters;
class FontfamilyFilter extends RegexpFilter
{
public function __construct()
{
$namechars = '[- \\w]+';
$double = '"' . $namechars . '"';
$single = "'" . $namechars . "'";
$name = '(?:' . $single . '|' . $double . '|' . $namechars . ')';
$regexp = '/^' . $name . '(?:, *' . $name . ')*$/';
parent::__construct($regexp);
$this->markAsSafeInCSS();
}
}

View File

@@ -0,0 +1,74 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items\AttributeFilters;
use InvalidArgumentException;
use RuntimeException;
use s9e\TextFormatter\Configurator\Helpers\ContextSafeness;
use s9e\TextFormatter\Configurator\Items\AttributeFilter;
use s9e\TextFormatter\Configurator\JavaScript\Dictionary;
class HashmapFilter extends AttributeFilter
{
public function __construct(array $map = \null, $strict = \false)
{
parent::__construct('s9e\\TextFormatter\\Parser\\AttributeFilters\\HashmapFilter::filter');
$this->resetParameters();
$this->addParameterByName('attrValue');
$this->addParameterByName('map');
$this->addParameterByName('strict');
$this->setJS('HashmapFilter.filter');
if (isset($map))
$this->setMap($map, $strict);
}
public function asConfig()
{
if (!isset($this->vars['map']))
throw new RuntimeException("Hashmap filter is missing a 'map' value");
return parent::asConfig();
}
public function setMap(array $map, $strict = \false)
{
if (!\is_bool($strict))
throw new InvalidArgumentException('Argument 2 passed to ' . __METHOD__ . ' must be a boolean');
if (!$strict)
$map = $this->optimizeLooseMap($map);
\ksort($map);
$this->vars['map'] = new Dictionary($map);
$this->vars['strict'] = $strict;
$this->resetSafeness();
if (!empty($this->vars['strict']))
{
$this->evaluateSafenessInCSS();
$this->evaluateSafenessInJS();
}
}
protected function evaluateSafenessInCSS()
{
$disallowedChars = ContextSafeness::getDisallowedCharactersInCSS();
foreach ($this->vars['map'] as $value)
foreach ($disallowedChars as $char)
if (\strpos($value, $char) !== \false)
return;
$this->markAsSafeInCSS();
}
protected function evaluateSafenessInJS()
{
$disallowedChars = ContextSafeness::getDisallowedCharactersInJS();
foreach ($this->vars['map'] as $value)
foreach ($disallowedChars as $char)
if (\strpos($value, $char) !== \false)
return;
$this->markAsSafeInJS();
}
protected function optimizeLooseMap(array $map)
{
foreach ($map as $k => $v)
if ($k === $v)
unset($map[$k]);
return $map;
}
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items\AttributeFilters;
class IdentifierFilter extends RegexpFilter
{
public function __construct()
{
parent::__construct('/^[-0-9A-Za-z_]+$/D');
$this->markAsSafeAsURL();
$this->markAsSafeInCSS();
}
}

View File

@@ -0,0 +1,20 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items\AttributeFilters;
use s9e\TextFormatter\Configurator\Items\AttributeFilter;
class IntFilter extends AttributeFilter
{
public function __construct()
{
parent::__construct('s9e\\TextFormatter\\Parser\\AttributeFilters\\NumericFilter::filterInt');
$this->setJS('NumericFilter.filterInt');
$this->markAsSafeAsURL();
$this->markAsSafeInCSS();
$this->markAsSafeInJS();
}
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items\AttributeFilters;
use s9e\TextFormatter\Configurator\Items\AttributeFilter;
class IpFilter extends AttributeFilter
{
public function __construct()
{
parent::__construct('s9e\\TextFormatter\\Parser\\AttributeFilters\\NetworkFilter::filterIp');
$this->setJS('NetworkFilter.filterIp');
}
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items\AttributeFilters;
use s9e\TextFormatter\Configurator\Items\AttributeFilter;
class IpportFilter extends AttributeFilter
{
public function __construct()
{
parent::__construct('s9e\\TextFormatter\\Parser\\AttributeFilters\\NetworkFilter::filterIpport');
$this->setJS('NetworkFilter.filterIpport');
}
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items\AttributeFilters;
use s9e\TextFormatter\Configurator\Items\AttributeFilter;
class Ipv4Filter extends AttributeFilter
{
public function __construct()
{
parent::__construct('s9e\\TextFormatter\\Parser\\AttributeFilters\\NetworkFilter::filterIpv4');
$this->setJS('NetworkFilter.filterIpv4');
}
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items\AttributeFilters;
use s9e\TextFormatter\Configurator\Items\AttributeFilter;
class Ipv6Filter extends AttributeFilter
{
public function __construct()
{
parent::__construct('s9e\\TextFormatter\\Parser\\AttributeFilters\\NetworkFilter::filterIpv6');
$this->setJS('NetworkFilter.filterIpv6');
}
}

View File

@@ -0,0 +1,88 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items\AttributeFilters;
use InvalidArgumentException;
use RuntimeException;
use s9e\TextFormatter\Configurator\Helpers\ContextSafeness;
use s9e\TextFormatter\Configurator\Helpers\RegexpBuilder;
use s9e\TextFormatter\Configurator\Items\AttributeFilter;
use s9e\TextFormatter\Configurator\Items\Regexp;
class MapFilter extends AttributeFilter
{
public function __construct(array $map = \null, $caseSensitive = \false, $strict = \false)
{
parent::__construct('s9e\\TextFormatter\\Parser\\AttributeFilters\\MapFilter::filter');
$this->resetParameters();
$this->addParameterByName('attrValue');
$this->addParameterByName('map');
$this->setJS('MapFilter.filter');
if (isset($map))
$this->setMap($map, $caseSensitive, $strict);
}
public function asConfig()
{
if (!isset($this->vars['map']))
throw new RuntimeException("Map filter is missing a 'map' value");
return parent::asConfig();
}
public function setMap(array $map, $caseSensitive = \false, $strict = \false)
{
if (!\is_bool($caseSensitive))
throw new InvalidArgumentException('Argument 2 passed to ' . __METHOD__ . ' must be a boolean');
if (!\is_bool($strict))
throw new InvalidArgumentException('Argument 3 passed to ' . __METHOD__ . ' must be a boolean');
$this->resetSafeness();
if ($strict)
$this->assessSafeness($map);
$valueKeys = [];
foreach ($map as $key => $value)
$valueKeys[$value][] = $key;
$map = [];
foreach ($valueKeys as $value => $keys)
{
$regexp = RegexpBuilder::fromList(
$keys,
[
'delimiter' => '/',
'caseInsensitive' => !$caseSensitive
]
);
$regexp = '/^' . $regexp . '$/D';
if (!$caseSensitive)
$regexp .= 'i';
if (!\preg_match('#^[[:ascii:]]*$#D', $regexp))
$regexp .= 'u';
$map[] = [new Regexp($regexp), $value];
}
if ($strict)
$map[] = [new Regexp('//'), \false];
$this->vars['map'] = $map;
}
protected function assessSafeness(array $map)
{
$values = \implode('', $map);
$isSafeInCSS = \true;
foreach (ContextSafeness::getDisallowedCharactersInCSS() as $char)
if (\strpos($values, $char) !== \false)
{
$isSafeInCSS = \false;
break;
}
if ($isSafeInCSS)
$this->markAsSafeInCSS();
$isSafeInJS = \true;
foreach (ContextSafeness::getDisallowedCharactersInJS() as $char)
if (\strpos($values, $char) !== \false)
{
$isSafeInJS = \false;
break;
}
if ($isSafeInJS)
$this->markAsSafeInJS();
}
}

View File

@@ -0,0 +1,18 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items\AttributeFilters;
class NumberFilter extends RegexpFilter
{
public function __construct()
{
parent::__construct('/^[0-9]+$/D');
$this->markAsSafeAsURL();
$this->markAsSafeInCSS();
$this->markAsSafeInJS();
}
}

View File

@@ -0,0 +1,50 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items\AttributeFilters;
use InvalidArgumentException;
use RuntimeException;
use s9e\TextFormatter\Configurator\Items\AttributeFilter;
class RangeFilter extends AttributeFilter
{
public function __construct($min = \null, $max = \null)
{
parent::__construct('s9e\\TextFormatter\\Parser\\AttributeFilters\\NumericFilter::filterRange');
$this->resetParameters();
$this->addParameterByName('attrValue');
$this->addParameterByName('min');
$this->addParameterByName('max');
$this->addParameterByName('logger');
$this->setJS('NumericFilter.filterRange');
$this->markAsSafeAsURL();
$this->markAsSafeInCSS();
$this->markAsSafeInJS();
if (isset($min))
$this->setRange($min, $max);
}
public function asConfig()
{
if (!isset($this->vars['min']))
throw new RuntimeException("Range filter is missing a 'min' value");
if (!isset($this->vars['max']))
throw new RuntimeException("Range filter is missing a 'max' value");
return parent::asConfig();
}
public function setRange($min, $max)
{
$min = \filter_var($min, \FILTER_VALIDATE_INT);
$max = \filter_var($max, \FILTER_VALIDATE_INT);
if ($min === \false)
throw new InvalidArgumentException('Argument 1 passed to ' . __METHOD__ . ' must be an integer');
if ($max === \false)
throw new InvalidArgumentException('Argument 2 passed to ' . __METHOD__ . ' must be an integer');
if ($min > $max)
throw new InvalidArgumentException('Invalid range: min (' . $min . ') > max (' . $max . ')');
$this->vars['min'] = $min;
$this->vars['max'] = $max;
}
}

View File

@@ -0,0 +1,92 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items\AttributeFilters;
use Exception;
use RuntimeException;
use s9e\TextFormatter\Configurator\Helpers\ContextSafeness;
use s9e\TextFormatter\Configurator\Helpers\RegexpParser;
use s9e\TextFormatter\Configurator\Items\AttributeFilter;
use s9e\TextFormatter\Configurator\Items\Regexp;
class RegexpFilter extends AttributeFilter
{
public function __construct($regexp = \null)
{
parent::__construct('s9e\\TextFormatter\\Parser\\AttributeFilters\\RegexpFilter::filter');
$this->resetParameters();
$this->addParameterByName('attrValue');
$this->addParameterByName('regexp');
$this->setJS('RegexpFilter.filter');
if (isset($regexp))
$this->setRegexp($regexp);
}
public function asConfig()
{
if (!isset($this->vars['regexp']))
throw new RuntimeException("Regexp filter is missing a 'regexp' value");
return parent::asConfig();
}
public function getRegexp()
{
return (string) $this->vars['regexp'];
}
public function setRegexp($regexp)
{
if (\is_string($regexp))
$regexp = new Regexp($regexp);
$this->vars['regexp'] = $regexp;
$this->resetSafeness();
$this->evaluateSafeness();
}
protected function evaluateSafeness()
{
try
{
$this->evaluateSafenessAsURL();
$this->evaluateSafenessInCSS();
$this->evaluateSafenessInJS();
}
catch (Exception $e)
{
}
}
protected function evaluateSafenessAsURL()
{
$regexpInfo = RegexpParser::parse($this->vars['regexp']);
$captureStart = '(?>\\((?:\\?:)?)*';
$regexp = '#^\\^' . $captureStart . '(?!data|\\w*script)[a-z0-9]+\\??:#i';
if (\preg_match($regexp, $regexpInfo['regexp'])
&& \strpos($regexpInfo['modifiers'], 'm') === \false)
{
$this->markAsSafeAsURL();
return;
}
$regexp = RegexpParser::getAllowedCharacterRegexp($this->vars['regexp']);
foreach (ContextSafeness::getDisallowedCharactersAsURL() as $char)
if (\preg_match($regexp, $char))
return;
$this->markAsSafeAsURL();
}
protected function evaluateSafenessInCSS()
{
$regexp = RegexpParser::getAllowedCharacterRegexp($this->vars['regexp']);
foreach (ContextSafeness::getDisallowedCharactersInCSS() as $char)
if (\preg_match($regexp, $char))
return;
$this->markAsSafeInCSS();
}
protected function evaluateSafenessInJS()
{
$safeExpressions = [
'\\d+',
'[0-9]+'
];
$regexp = '(^(?<delim>.)\\^(?:(?<expr>' . \implode('|', \array_map('preg_quote', $safeExpressions)) . ')|\\((?:\\?[:>])?(?&expr)\\))\\$(?&delim)(?=.*D)[Dis]*$)D';
if (\preg_match($regexp, $this->vars['regexp']))
$this->markAsSafeInJS();
}
}

View File

@@ -0,0 +1,16 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items\AttributeFilters;
class SimpletextFilter extends RegexpFilter
{
public function __construct()
{
parent::__construct('/^[- +,.0-9A-Za-z_]+$/D');
$this->markAsSafeInCSS();
}
}

View File

@@ -0,0 +1,20 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items\AttributeFilters;
use s9e\TextFormatter\Configurator\Items\AttributeFilter;
class TimestampFilter extends AttributeFilter
{
public function __construct()
{
parent::__construct('s9e\\TextFormatter\\Parser\\AttributeFilters\\TimestampFilter::filter');
$this->setJS('TimestampFilter.filter');
$this->markAsSafeAsURL();
$this->markAsSafeInCSS();
$this->markAsSafeInJS();
}
}

View File

@@ -0,0 +1,29 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items\AttributeFilters;
use s9e\TextFormatter\Configurator\Items\AttributeFilter;
class UintFilter extends AttributeFilter
{
public function __construct()
{
parent::__construct('s9e\\TextFormatter\\Parser\\AttributeFilters\\NumericFilter::filterUint');
$this->setJS('NumericFilter.filterUint');
}
public function isSafeInCSS()
{
return \true;
}
public function isSafeInJS()
{
return \true;
}
public function isSafeAsURL()
{
return \true;
}
}

View File

@@ -0,0 +1,22 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items;
use InvalidArgumentException;
use s9e\TextFormatter\Configurator\Items\Regexp;
class AttributePreprocessor extends Regexp
{
public function getAttributes()
{
return $this->getNamedCaptures();
}
public function getRegexp()
{
return $this->regexp;
}
}

View File

@@ -0,0 +1,22 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items;
use DOMDocument;
use s9e\TextFormatter\Configurator\Helpers\TemplateHelper;
class TemplateDocument extends DOMDocument
{
protected $template;
public function __construct(Template $template)
{
$this->template = $template;
}
public function saveChanges()
{
$this->template->setContent(TemplateHelper::saveTemplate($this));
}
}

View File

@@ -0,0 +1,11 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Items;
class UnsafeTemplate extends Template
{
}

View File

@@ -0,0 +1,234 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator;
use ReflectionClass;
use s9e\TextFormatter\Configurator;
use s9e\TextFormatter\Configurator\Helpers\ConfigHelper;
use s9e\TextFormatter\Configurator\JavaScript\CallbackGenerator;
use s9e\TextFormatter\Configurator\JavaScript\Code;
use s9e\TextFormatter\Configurator\JavaScript\ConfigOptimizer;
use s9e\TextFormatter\Configurator\JavaScript\Dictionary;
use s9e\TextFormatter\Configurator\JavaScript\Encoder;
use s9e\TextFormatter\Configurator\JavaScript\HintGenerator;
use s9e\TextFormatter\Configurator\JavaScript\Minifier;
use s9e\TextFormatter\Configurator\JavaScript\Minifiers\Noop;
use s9e\TextFormatter\Configurator\JavaScript\RegexpConvertor;
use s9e\TextFormatter\Configurator\JavaScript\StylesheetCompressor;
use s9e\TextFormatter\Configurator\RendererGenerators\XSLT;
class JavaScript
{
protected $callbackGenerator;
protected $config;
protected $configOptimizer;
protected $configurator;
public $encoder;
public $exportMethods;
public $exports = [
'disablePlugin',
'disableTag',
'enablePlugin',
'enableTag',
'getLogger',
'parse',
'preview',
'registeredVars',
'setNestingLimit',
'setParameter',
'setTagLimit'
];
protected $hintGenerator;
protected $minifier;
protected $stylesheetCompressor;
protected $xsl;
public function __construct(Configurator $configurator)
{
$this->exportMethods =& $this->exports;
$this->encoder = new Encoder;
$this->callbackGenerator = new CallbackGenerator;
$this->configOptimizer = new ConfigOptimizer($this->encoder);
$this->configurator = $configurator;
$this->hintGenerator = new HintGenerator;
$this->stylesheetCompressor = new StylesheetCompressor;
}
public function getMinifier()
{
if (!isset($this->minifier))
$this->minifier = new Noop;
return $this->minifier;
}
public function getParser(array $config = \null)
{
$this->configOptimizer->reset();
$xslt = new XSLT;
$xslt->optimizer->normalizer->remove('RemoveLivePreviewAttributes');
$this->xsl = $xslt->getXSL($this->configurator->rendering);
$this->config = (isset($config)) ? $config : $this->configurator->asConfig();
$this->config = ConfigHelper::filterConfig($this->config, 'JS');
$this->config = $this->callbackGenerator->replaceCallbacks($this->config);
$src = $this->getHints() . $this->injectConfig($this->getSource());
$src .= "if (!window['s9e']) window['s9e'] = {};\n" . $this->getExports();
$src = $this->getMinifier()->get($src);
$src = '(function(){' . $src . '})()';
return $src;
}
public function setMinifier($minifier)
{
if (\is_string($minifier))
{
$className = __NAMESPACE__ . '\\JavaScript\\Minifiers\\' . $minifier;
$args = \array_slice(\func_get_args(), 1);
if (!empty($args))
{
$reflection = new ReflectionClass($className);
$minifier = $reflection->newInstanceArgs($args);
}
else
$minifier = new $className;
}
$this->minifier = $minifier;
return $minifier;
}
protected function encode($value)
{
return $this->encoder->encode($value);
}
protected function getExports()
{
if (empty($this->exports))
return '';
$exports = [];
foreach ($this->exports as $export)
$exports[] = "'" . $export . "':" . $export;
\sort($exports);
return "window['s9e']['TextFormatter'] = {" . \implode(',', $exports) . '}';
}
protected function getHints()
{
$this->hintGenerator->setConfig($this->config);
$this->hintGenerator->setPlugins($this->configurator->plugins);
$this->hintGenerator->setXSL($this->xsl);
return $this->hintGenerator->getHints();
}
protected function getPluginsConfig()
{
$plugins = new Dictionary;
foreach ($this->config['plugins'] as $pluginName => $pluginConfig)
{
if (!isset($pluginConfig['js']))
continue;
$js = $pluginConfig['js'];
unset($pluginConfig['js']);
unset($pluginConfig['className']);
if (isset($pluginConfig['quickMatch']))
{
$valid = [
'[[:ascii:]]',
'[\\xC0-\\xDF][\\x80-\\xBF]',
'[\\xE0-\\xEF][\\x80-\\xBF]{2}',
'[\\xF0-\\xF7][\\x80-\\xBF]{3}'
];
$regexp = '#(?>' . \implode('|', $valid) . ')+#';
if (\preg_match($regexp, $pluginConfig['quickMatch'], $m))
$pluginConfig['quickMatch'] = $m[0];
else
unset($pluginConfig['quickMatch']);
}
$globalKeys = [
'quickMatch' => 1,
'regexp' => 1,
'regexpLimit' => 1
];
$globalConfig = \array_intersect_key($pluginConfig, $globalKeys);
$localConfig = \array_diff_key($pluginConfig, $globalKeys);
if (isset($globalConfig['regexp']) && !($globalConfig['regexp'] instanceof Code))
$globalConfig['regexp'] = new Code(RegexpConvertor::toJS($globalConfig['regexp'], \true));
$globalConfig['parser'] = new Code(
'/**
* @param {!string} text
* @param {!Array.<Array>} matches
*/
function(text, matches)
{
/** @const */
var config=' . $this->encode($localConfig) . ';
' . $js . '
}'
);
$plugins[$pluginName] = $globalConfig;
}
return $plugins;
}
protected function getRegisteredVarsConfig()
{
$registeredVars = $this->config['registeredVars'];
unset($registeredVars['cacheDir']);
return new Dictionary($registeredVars);
}
protected function getRootContext()
{
return $this->config['rootContext'];
}
protected function getSource()
{
$rootDir = __DIR__ . '/..';
$src = '';
$logger = (\in_array('getLogger', $this->exports)) ? 'Logger.js' : 'NullLogger.js';
$files = \glob($rootDir . '/Parser/AttributeFilters/*.js');
$files[] = $rootDir . '/Parser/utils.js';
$files[] = $rootDir . '/Parser/FilterProcessing.js';
$files[] = $rootDir . '/Parser/' . $logger;
$files[] = $rootDir . '/Parser/Tag.js';
$files[] = $rootDir . '/Parser.js';
if (\in_array('preview', $this->exports, \true))
{
$files[] = $rootDir . '/render.js';
$src .= '/** @const */ var xsl=' . $this->getStylesheet() . ";\n";
}
$src .= \implode("\n", \array_map('file_get_contents', $files));
return $src;
}
protected function getStylesheet()
{
return $this->stylesheetCompressor->encode($this->xsl);
}
protected function getTagsConfig()
{
$tags = new Dictionary;
foreach ($this->config['tags'] as $tagName => $tagConfig)
{
if (isset($tagConfig['attributes']))
$tagConfig['attributes'] = new Dictionary($tagConfig['attributes']);
$tags[$tagName] = $tagConfig;
}
return $tags;
}
protected function injectConfig($src)
{
$config = \array_map(
[$this, 'encode'],
$this->configOptimizer->optimize(
[
'plugins' => $this->getPluginsConfig(),
'registeredVars' => $this->getRegisteredVarsConfig(),
'rootContext' => $this->getRootContext(),
'tagsConfig' => $this->getTagsConfig()
]
)
);
$src = \preg_replace_callback(
'/(\\nvar (' . \implode('|', \array_keys($config)) . '))(;)/',
function ($m) use ($config)
{
return $m[1] . '=' . $config[$m[2]] . $m[3];
},
$src
);
$src = $this->configOptimizer->getVarDeclarations() . $src;
return $src;
}
}

View File

@@ -0,0 +1,81 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\JavaScript;
class CallbackGenerator
{
public $callbacks = [
'tags.*.attributes.*.filterChain.*' => [
'attrValue' => '*',
'attrName' => '!string'
],
'tags.*.filterChain.*' => [
'tag' => '!Tag',
'tagConfig' => '!Object'
]
];
protected $encoder;
public function __construct()
{
$this->encoder = new Encoder;
}
public function replaceCallbacks(array $config)
{
foreach ($this->callbacks as $path => $params)
$config = $this->mapArray($config, \explode('.', $path), $params);
return $config;
}
protected function buildCallbackArguments(array $params, array $localVars)
{
unset($params['parser']);
$localVars += ['logger' => 1, 'openTags' => 1, 'registeredVars' => 1, 'text' => 1];
$args = [];
foreach ($params as $k => $v)
if (isset($v))
$args[] = $this->encoder->encode($v);
elseif (isset($localVars[$k]))
$args[] = $k;
else
$args[] = 'registeredVars[' . \json_encode($k) . ']';
return \implode(',', $args);
}
protected function generateFunction(array $config, array $params)
{
if ($config['js'] == 'returnFalse' || $config['js'] == 'returnTrue')
return new Code((string) $config['js']);
$config += ['params' => []];
$src = $this->getHeader($params);
$src .= 'function(' . \implode(',', \array_keys($params)) . '){';
$src .= 'return ' . $this->parenthesizeCallback($config['js']);
$src .= '(' . $this->buildCallbackArguments($config['params'], $params) . ');}';
return new Code($src);
}
protected function getHeader(array $params)
{
$header = "/**\n";
foreach ($params as $paramName => $paramType)
$header .= '* @param {' . $paramType . '} ' . $paramName . "\n";
$header .= "*/\n";
return $header;
}
protected function mapArray(array $array, array $path, array $params)
{
$key = \array_shift($path);
$keys = ($key === '*') ? \array_keys($array) : [$key];
foreach ($keys as $key)
{
if (!isset($array[$key]))
continue;
$array[$key] = (empty($path)) ? $this->generateFunction($array[$key], $params) : $this->mapArray($array[$key], $path, $params);
}
return $array;
}
protected function parenthesizeCallback($callback)
{
return (\preg_match('(^[.\\w]+$)D', $callback)) ? $callback : '(' . $callback . ')';
}
}

View File

@@ -0,0 +1,95 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\JavaScript;
class ConfigOptimizer
{
protected $configValues;
protected $encoder;
protected $jsLengths;
public function __construct(Encoder $encoder)
{
$this->encoder = $encoder;
$this->reset();
}
public function getVarDeclarations()
{
\asort($this->jsLengths);
$src = '';
foreach (\array_keys($this->jsLengths) as $varName)
{
$configValue = $this->configValues[$varName];
if ($configValue->isDeduplicated())
$src .= '/** @const */ var ' . $varName . '=' . $this->encoder->encode($configValue->getValue()) . ";\n";
}
return $src;
}
public function optimize($object)
{
return \current($this->optimizeObjectContent([$object]))->getValue();
}
public function reset()
{
$this->configValues = [];
$this->jsLengths = [];
}
protected function canDeduplicate($value)
{
if (\is_array($value) || $value instanceof Dictionary)
return (bool) \count($value);
return ($value instanceof Code);
}
protected function deduplicateConfigValues()
{
\arsort($this->jsLengths);
foreach (\array_keys($this->jsLengths) as $varName)
{
$configValue = $this->configValues[$varName];
if ($configValue->getUseCount() > 1)
$configValue->deduplicate();
}
}
protected function getVarName($js)
{
return \sprintf('o%08X', \crc32($js));
}
protected function isIterable($value)
{
return (\is_array($value) || $value instanceof Dictionary);
}
protected function optimizeObjectContent($object)
{
$object = $this->recordObject($object);
$this->deduplicateConfigValues();
return $object->getValue();
}
protected function recordObject($object)
{
$js = $this->encoder->encode($object);
$varName = $this->getVarName($js);
if ($this->isIterable($object))
$object = $this->recordObjectContent($object);
if (!isset($this->configValues[$varName]))
{
$this->configValues[$varName] = new ConfigValue($object, $varName);
$this->jsLengths[$varName] = \strlen($js);
}
$this->configValues[$varName]->incrementUseCount();
return $this->configValues[$varName];
}
protected function recordObjectContent($object)
{
foreach ($object as $k => $v)
if ($this->canDeduplicate($v) && !$this->shouldPreserve($v))
$object[$k] = $this->recordObject($v);
return $object;
}
protected function shouldPreserve($value)
{
return ($value instanceof Code && \preg_match('(^\\w+$)', $value));
}
}

View File

@@ -0,0 +1,56 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\JavaScript;
class ConfigValue
{
protected $isDeduplicated = \false;
protected $name;
protected $useCount = 0;
protected $value;
protected $varName;
public function __construct($value, $varName)
{
$this->value = $value;
$this->varName = $varName;
}
public function deduplicate()
{
if ($this->useCount > 1)
{
$this->isDeduplicated = \true;
$this->decrementUseCount($this->useCount - 1);
}
}
public function getUseCount()
{
return $this->useCount;
}
public function getValue()
{
return $this->value;
}
public function getVarName()
{
return $this->varName;
}
public function incrementUseCount()
{
++$this->useCount;
}
public function isDeduplicated()
{
return $this->isDeduplicated;
}
protected function decrementUseCount($step = 1)
{
$this->useCount -= $step;
foreach ($this->value as $value)
if ($value instanceof ConfigValue)
$value->decrementUseCount($step);
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\JavaScript;
use ArrayObject;
use s9e\TextFormatter\Configurator\FilterableConfigValue;
use s9e\TextFormatter\Configurator\Helpers\ConfigHelper;
class Dictionary extends ArrayObject implements FilterableConfigValue
{
public function filterConfig($target)
{
$value = $this->getArrayCopy();
if ($target === 'JS')
$value = new Dictionary(ConfigHelper::filterConfig($value, $target));
return $value;
}
}

View File

@@ -0,0 +1,114 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\JavaScript;
use RuntimeException;
use s9e\TextFormatter\Configurator\Items\Regexp;
use s9e\TextFormatter\Configurator\JavaScript\Code;
use s9e\TextFormatter\Configurator\JavaScript\Dictionary;
use s9e\TextFormatter\Configurator\JavaScript\Encoder;
class Encoder
{
public $objectEncoders;
public $typeEncoders;
public function __construct()
{
$ns = 's9e\\TextFormatter\\Configurator\\';
$this->objectEncoders = [
$ns . 'Items\\Regexp' => [$this, 'encodeRegexp'],
$ns . 'JavaScript\\Code' => [$this, 'encodeCode'],
$ns . 'JavaScript\\ConfigValue' => [$this, 'encodeConfigValue'],
$ns . 'JavaScript\\Dictionary' => [$this, 'encodeDictionary']
];
$this->typeEncoders = [
'array' => [$this, 'encodeArray'],
'boolean' => [$this, 'encodeBoolean'],
'double' => [$this, 'encodeScalar'],
'integer' => [$this, 'encodeScalar'],
'object' => [$this, 'encodeObject'],
'string' => [$this, 'encodeScalar']
];
}
public function encode($value)
{
$type = \gettype($value);
if (!isset($this->typeEncoders[$type]))
throw new RuntimeException('Cannot encode ' . $type . ' value');
return $this->typeEncoders[$type]($value);
}
protected function encodeArray(array $array)
{
return ($this->isIndexedArray($array)) ? $this->encodeIndexedArray($array) : $this->encodeAssociativeArray($array);
}
protected function encodeAssociativeArray(array $array, $preserveNames = \false)
{
\ksort($array);
$src = '{';
$sep = '';
foreach ($array as $k => $v)
{
$src .= $sep . $this->encodePropertyName("$k", $preserveNames) . ':' . $this->encode($v);
$sep = ',';
}
$src .= '}';
return $src;
}
protected function encodeBoolean($value)
{
return ($value) ? '!0' : '!1';
}
protected function encodeCode(Code $code)
{
return (string) $code;
}
protected function encodeConfigValue(ConfigValue $configValue)
{
return ($configValue->isDeduplicated()) ? $configValue->getVarName() : $this->encode($configValue->getValue());
}
protected function encodeDictionary(Dictionary $dict)
{
return $this->encodeAssociativeArray($dict->getArrayCopy(), \true);
}
protected function encodeIndexedArray(array $array)
{
return '[' . \implode(',', \array_map([$this, 'encode'], $array)) . ']';
}
protected function encodeObject($object)
{
foreach ($this->objectEncoders as $className => $callback)
if ($object instanceof $className)
return $callback($object);
throw new RuntimeException('Cannot encode instance of ' . \get_class($object));
}
protected function encodePropertyName($name, $preserveNames)
{
return ($preserveNames || !$this->isLegalProp($name)) ? \json_encode($name) : $name;
}
protected function encodeRegexp(Regexp $regexp)
{
return $regexp->getJS();
}
protected function encodeScalar($value)
{
return \json_encode($value);
}
protected function isIndexedArray(array $array)
{
if (empty($array))
return \true;
if (isset($array[0]) && \array_keys($array) === \range(0, \count($array) - 1))
return \true;
return \false;
}
protected function isLegalProp($name)
{
$reserved = ['abstract', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 'class', 'const', 'continue', 'debugger', 'default', 'delete', 'do', 'double', 'else', 'enum', 'export', 'extends', 'false', 'final', 'finally', 'float', 'for', 'function', 'goto', 'if', 'implements', 'import', 'in', 'instanceof', 'int', 'interface', 'let', 'long', 'native', 'new', 'null', 'package', 'private', 'protected', 'public', 'return', 'short', 'static', 'super', 'switch', 'synchronized', 'this', 'throw', 'throws', 'transient', 'true', 'try', 'typeof', 'var', 'void', 'volatile', 'while', 'with'];
if (\in_array($name, $reserved, \true))
return \false;
return (bool) \preg_match('#^(?![0-9])[$_\\pL][$_\\pL\\pNl]+$#Du', $name);
}
}

View File

@@ -0,0 +1,89 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\JavaScript;
use ReflectionClass;
use s9e\TextFormatter\Configurator\Collections\PluginCollection;
class HintGenerator
{
protected $config;
protected $hints;
protected $plugins;
protected $xsl;
public function getHints()
{
$this->hints = [];
$this->setPluginsHints();
$this->setRenderingHints();
$this->setRulesHints();
$this->setTagsHints();
$js = "/** @const */ var HINT={};\n";
\ksort($this->hints);
foreach ($this->hints as $hintName => $hintValue)
$js .= '/** @const */ HINT.' . $hintName . '=' . \json_encode($hintValue) . ";\n";
return $js;
}
public function setConfig(array $config)
{
$this->config = $config;
}
public function setPlugins(PluginCollection $plugins)
{
$this->plugins = $plugins;
}
public function setXSL($xsl)
{
$this->xsl = $xsl;
}
protected function setPluginsHints()
{
foreach ($this->plugins as $plugins)
$this->hints += $plugins->getJSHints();
}
protected function setRenderingHints()
{
$this->hints['postProcessing'] = (int) (\strpos($this->xsl, 'data-s9e-livepreview-postprocess') !== \false);
$this->hints['ignoreAttrs'] = (int) (\strpos($this->xsl, 'data-s9e-livepreview-ignore-attrs') !== \false);
}
protected function setRulesHints()
{
$this->hints['closeAncestor'] = 0;
$this->hints['closeParent'] = 0;
$this->hints['createChild'] = 0;
$this->hints['fosterParent'] = 0;
$this->hints['requireAncestor'] = 0;
$flags = 0;
foreach ($this->config['tags'] as $tagConfig)
{
foreach (\array_intersect_key($tagConfig['rules'], $this->hints) as $k => $v)
$this->hints[$k] = 1;
$flags |= $tagConfig['rules']['flags'];
}
$flags |= $this->config['rootContext']['flags'];
$parser = new ReflectionClass('s9e\\TextFormatter\\Parser');
foreach ($parser->getConstants() as $constName => $constValue)
if (\substr($constName, 0, 5) === 'RULE_')
$this->hints[$constName] = ($flags & $constValue) ? 1 : 0;
}
protected function setTagAttributesHints(array $tagConfig)
{
if (empty($tagConfig['attributes']))
return;
foreach ($tagConfig['attributes'] as $attrConfig)
$this->hints['attributeDefaultValue'] |= isset($attrConfig['defaultValue']);
}
protected function setTagsHints()
{
$this->hints['attributeDefaultValue'] = 0;
$this->hints['namespaces'] = 0;
foreach ($this->config['tags'] as $tagName => $tagConfig)
{
$this->hints['namespaces'] |= (\strpos($tagName, ':') !== \false);
$this->setTagAttributesHints($tagConfig);
}
}
}

View File

@@ -0,0 +1,41 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\JavaScript;
use Exception;
abstract class Minifier
{
public $cacheDir;
public $keepGoing = \false;
abstract public function minify($src);
public function get($src)
{
try
{
return (isset($this->cacheDir)) ? $this->getFromCache($src) : $this->minify($src);
}
catch (Exception $e)
{
if (!$this->keepGoing)
throw $e;
}
return $src;
}
public function getCacheDifferentiator()
{
return '';
}
protected function getFromCache($src)
{
$differentiator = $this->getCacheDifferentiator();
$key = \sha1(\serialize([\get_class($this), $differentiator, $src]));
$cacheFile = $this->cacheDir . '/minifier.' . $key . '.js';
if (!\file_exists($cacheFile))
\file_put_contents($cacheFile, $this->minify($src));
return \file_get_contents($cacheFile);
}
}

View File

@@ -0,0 +1,85 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\JavaScript\Minifiers;
use RuntimeException;
use s9e\TextFormatter\Configurator\JavaScript\Minifier;
class ClosureCompilerApplication extends Minifier
{
public $closureCompilerBin;
public $command;
public $compilationLevel = 'ADVANCED_OPTIMIZATIONS';
public $excludeDefaultExterns = \true;
public $javaBin = 'java';
public $options = '--use_types_for_optimization';
public function __construct($filepathOrCommand = \null)
{
if (isset($filepathOrCommand))
if (\file_exists($filepathOrCommand) && \substr($filepathOrCommand, -4) === '.jar')
$this->closureCompilerBin = $filepathOrCommand;
else
$this->command = $filepathOrCommand;
}
public function getCacheDifferentiator()
{
$key = [
$this->compilationLevel,
$this->excludeDefaultExterns,
$this->options
];
if (isset($this->closureCompilerBin))
$key[] = $this->getClosureCompilerBinHash();
if ($this->excludeDefaultExterns)
$key[] = \file_get_contents(__DIR__ . '/../externs.application.js');
return $key;
}
public function minify($src)
{
$this->testFilepaths();
$options = ($this->options) ? ' ' . $this->options : '';
if ($this->excludeDefaultExterns && $this->compilationLevel === 'ADVANCED_OPTIMIZATIONS')
$options .= ' --externs ' . __DIR__ . '/../externs.application.js --env=CUSTOM';
$crc = \crc32($src);
$inFile = \sys_get_temp_dir() . '/' . $crc . '.js';
$outFile = \sys_get_temp_dir() . '/' . $crc . '.min.js';
\file_put_contents($inFile, $src);
if (isset($this->command))
$cmd = $this->command;
else
$cmd = \escapeshellcmd($this->javaBin) . ' -jar ' . \escapeshellarg($this->closureCompilerBin);
$cmd .= ' --compilation_level ' . \escapeshellarg($this->compilationLevel)
. $options
. ' --js ' . \escapeshellarg($inFile)
. ' --js_output_file ' . \escapeshellarg($outFile);
\exec($cmd . ' 2>&1', $output, $return);
\unlink($inFile);
if (\file_exists($outFile))
{
$src = \trim(\file_get_contents($outFile));
\unlink($outFile);
}
if (!empty($return))
throw new RuntimeException('An error occured during minification: ' . \implode("\n", $output));
return $src;
}
protected function getClosureCompilerBinHash()
{
static $cache = [];
if (!isset($cache[$this->closureCompilerBin]))
$cache[$this->closureCompilerBin] = \md5_file($this->closureCompilerBin);
return $cache[$this->closureCompilerBin];
}
protected function testFilepaths()
{
if (isset($this->command))
return;
if (!isset($this->closureCompilerBin))
throw new RuntimeException('No path set for Closure Compiler');
if (!\file_exists($this->closureCompilerBin))
throw new RuntimeException('Cannot find Closure Compiler at ' . $this->closureCompilerBin);
}
}

View File

@@ -0,0 +1,88 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\JavaScript\Minifiers;
use RuntimeException;
use s9e\TextFormatter\Configurator\JavaScript\OnlineMinifier;
class ClosureCompilerService extends OnlineMinifier
{
public $compilationLevel = 'ADVANCED_OPTIMIZATIONS';
public $excludeDefaultExterns = \true;
public $externs;
public $url = 'https://closure-compiler.appspot.com/compile';
public function __construct()
{
parent::__construct();
$this->externs = \file_get_contents(__DIR__ . '/../externs.service.js');
}
public function getCacheDifferentiator()
{
$key = [$this->compilationLevel, $this->excludeDefaultExterns];
if ($this->excludeDefaultExterns)
$key[] = $this->externs;
return $key;
}
public function minify($src)
{
$body = $this->generateRequestBody($src);
$response = $this->query($body);
if ($response === \false)
throw new RuntimeException('Could not contact the Closure Compiler service');
return $this->decodeResponse($response);
}
protected function decodeResponse($response)
{
$response = \json_decode($response, \true);
if (\is_null($response))
{
$msgs = array(
\JSON_ERROR_NONE => 'No error',
\JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
\JSON_ERROR_STATE_MISMATCH => 'State mismatch (invalid or malformed JSON)',
\JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded',
\JSON_ERROR_SYNTAX => 'Syntax error',
\JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded'
);
throw new RuntimeException('Closure Compiler service returned invalid JSON: ' . (isset($msgs[\json_last_error()]) ? $msgs[\json_last_error()] : 'Unknown error'));
}
if (isset($response['serverErrors'][0]))
{
$error = $response['serverErrors'][0];
throw new RuntimeException('Server error ' . $error['code'] . ': ' . $error['error']);
}
if (isset($response['errors'][0]))
{
$error = $response['errors'][0];
throw new RuntimeException('Compilation error: ' . $error['error']);
}
return $response['compiledCode'];
}
protected function generateRequestBody($src)
{
$params = [
'compilation_level' => $this->compilationLevel,
'js_code' => $src,
'output_format' => 'json',
'output_info' => 'compiled_code'
];
if ($this->excludeDefaultExterns && $this->compilationLevel === 'ADVANCED_OPTIMIZATIONS')
{
$params['exclude_default_externs'] = 'true';
$params['js_externs'] = $this->externs;
}
$body = \http_build_query($params) . '&output_info=errors';
return $body;
}
protected function query($body)
{
return $this->httpClient->post(
$this->url,
['Content-Type: application/x-www-form-urlencoded'],
$body
);
}
}

View File

@@ -0,0 +1,38 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\JavaScript\Minifiers;
use ArrayAccess;
use Exception;
use RuntimeException;
use s9e\TextFormatter\Configurator\Collections\MinifierList;
use s9e\TextFormatter\Configurator\JavaScript\Minifier;
use s9e\TextFormatter\Configurator\Traits\CollectionProxy;
class FirstAvailable extends Minifier implements ArrayAccess
{
use CollectionProxy;
protected $collection;
public function __construct()
{
$this->collection = new MinifierList;
foreach (\func_get_args() as $minifier)
$this->collection->add($minifier);
}
public function minify($src)
{
foreach ($this->collection as $minifier)
try
{
return $minifier->minify($src);
}
catch (Exception $e)
{
}
throw new RuntimeException('No minifier available');
}
}

View File

@@ -0,0 +1,18 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\JavaScript\Minifiers;
use MatthiasMullie\Minify;
use s9e\TextFormatter\Configurator\JavaScript\Minifier;
class MatthiasMullieMinify extends Minifier
{
public function minify($src)
{
$minifier = new Minify\JS($src);
return $minifier->minify();
}
}

View File

@@ -0,0 +1,16 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\JavaScript\Minifiers;
use s9e\TextFormatter\Configurator\JavaScript\Minifier;
class Noop extends Minifier
{
public function minify($src)
{
return $src;
}
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\JavaScript;
use s9e\TextFormatter\Utils\Http;
abstract class OnlineMinifier extends Minifier
{
public $httpClient;
public function __construct()
{
$this->httpClient = Http::getClient();
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,111 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 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
{
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,}+"'
];
protected $dictionary;
protected $keyPrefix = '$';
public $minSaving = 10;
protected $savings;
protected $xsl;
public function encode($xsl)
{
$this->xsl = $xsl;
$this->estimateSavings();
$this->filterSavings();
$this->buildDictionary();
$js = \json_encode($this->getCompressedStylesheet());
if (!empty($this->dictionary))
$js .= '.replace(' . $this->getReplacementRegexp() . ',function(k){return' . \json_encode($this->dictionary) . '[k]})';
return $js;
}
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;
}
}
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);
}
}
protected function filterSavings()
{
$this->savings = \array_filter(
$this->savings,
function ($saving)
{
return ($saving >= $this->minSaving);
}
);
}
protected function getAvailableKeys()
{
return \array_diff($this->getPossibleKeys(), $this->getUnavailableKeys());
}
protected function getCompressedStylesheet()
{
return \strtr($this->xsl, \array_flip($this->dictionary));
}
protected function getPossibleKeys()
{
$keys = [];
foreach (\range('a', 'z') as $char)
$keys[] = $this->keyPrefix . $char;
return $keys;
}
protected function getReplacementRegexp()
{
return '/' . RegexpBuilder::fromList(\array_keys($this->dictionary)) . '/g';
}
protected function getStringsFrequency()
{
$regexp = '(' . \implode('|', $this->deduplicateTargets) . ')S';
\preg_match_all($regexp, $this->xsl, $matches);
return \array_count_values($matches[0]);
}
protected function getUnavailableKeys()
{
\preg_match_all('(' . \preg_quote($this->keyPrefix) . '.)', $this->xsl, $matches);
return \array_unique($matches[0]);
}
}

View File

@@ -0,0 +1,227 @@
/*
* Copyright 2008 The Closure Compiler Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// This file was auto-generated.
// See https://github.com/google/closure-compiler for the original source.
// See https://github.com/s9e/TextFormatter/blob/master/scripts/generateExterns.php for details.
/**
* @const
*/
var punycode = {};
/**
* @param {string} domain
* @return {string}
*/
punycode.toASCII;
/** @constructor */
function XSLTProcessor() {}
/**
* @type {string}
* @implicitCast
*/
Element.prototype.innerHTML;
/**
* @constructor
*/
function DOMParser() {}
/**
* @param {string} src The UTF16 string to be parsed.
* @param {string} type The content type of the string.
* @return {Document}
*/
DOMParser.prototype.parseFromString = function(src, type) {};
/**
* @type {!Window}
*/
var window;
/**
* @constructor
* @extends {Node}
*/
function Document() {}
/**
* @return {!DocumentFragment}
* @nosideeffects
*/
Document.prototype.createDocumentFragment = function() {};
/**
* @param {string} tagName
* @param {string=} opt_typeExtension
* @return {!Element}
* @nosideeffects
*/
Document.prototype.createElement = function(tagName, opt_typeExtension) {};
/**
* @constructor
* @extends {Node}
*/
function DocumentFragment() {}
/**
* @constructor
* @implements {IObject<(string|number), T>}
* @implements {IArrayLike<T>}
* @implements {Iterable<T>}
* @template T
*/
function NamedNodeMap() {}
/**
* @param {number} index
* @return {Node}
* @nosideeffects
*/
NamedNodeMap.prototype.item = function(index) {};
/**
* @type {number}
*/
NamedNodeMap.prototype.length;
/**
* @constructor
*/
function Node() {}
/**
* @param {Node} newChild
* @return {!Node}
*/
Node.prototype.appendChild = function(newChild) {};
/**
* @type {!NodeList<!Node>}
*/
Node.prototype.childNodes;
/**
* @param {boolean} deep
* @return {!Node}
* @nosideeffects
*/
Node.prototype.cloneNode = function(deep) {};
/**
* @type {Node}
*/
Node.prototype.firstChild;
/**
* @param {Node} newChild
* @param {Node} refChild
* @return {!Node}
*/
Node.prototype.insertBefore = function(newChild, refChild) {};
/**
* @type {string}
*/
Node.prototype.nodeName;
/**
* @type {number}
*/
Node.prototype.nodeType;
/**
* @type {string}
*/
Node.prototype.nodeValue;
/**
* @type {Document}
*/
Node.prototype.ownerDocument;
/**
* @type {Node}
*/
Node.prototype.parentNode;
/**
* @param {Node} oldChild
* @return {!Node}
*/
Node.prototype.removeChild = function(oldChild) {};
/**
* @constructor
* @implements {IArrayLike<T>}
* @implements {Iterable<T>}
* @template T
*/
function NodeList() {}
/**
* @type {number}
*/
NodeList.prototype.length;
/**
* @constructor
* @extends {Node}
*/
function Element() {}
/**
* @constructor
*/
function Window() {}
/**
* @param {Node} externalNode
* @param {boolean} deep
* @return {Node}
*/
Document.prototype.importNode = function(externalNode, deep) {};
/**
* @constructor
* @extends {Document}
*/
function HTMLDocument() {}
/**
* @constructor
* @extends {Element}
*/
function HTMLElement() {}
/**
* @param {?string} namespaceURI
* @param {string} localName
* @return {string}
* @nosideeffects
*/
Element.prototype.getAttributeNS = function(namespaceURI, localName) {};
/**
* @param {?string} namespaceURI
* @param {string} localName
* @return {boolean}
* @nosideeffects
*/
Element.prototype.hasAttributeNS = function(namespaceURI, localName) {};
/**
* @param {?string} namespaceURI
* @param {string} localName
* @return {undefined}
*/
Element.prototype.removeAttributeNS = function(namespaceURI, localName) {};
/**
* @param {?string} namespaceURI
* @param {string} qualifiedName
* @param {string|number|boolean} value Values are converted to strings with
* @return {undefined}
*/
Element.prototype.setAttributeNS = function(namespaceURI, qualifiedName, value) {};
/**
* @param {Node} arg
* @return {boolean}
* @nosideeffects
*/
Node.prototype.isEqualNode = function(arg) {};
/**
* @type {string}
*/
Node.prototype.namespaceURI;
/**
* @type {string}
* @implicitCast
*/
Node.prototype.textContent;
/**
* @type {!HTMLDocument}
* @const
*/
var document;

View File

@@ -0,0 +1,553 @@
/*
* Copyright 2008 The Closure Compiler Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// This file was auto-generated.
// See https://github.com/google/closure-compiler for the original source.
// See https://github.com/s9e/TextFormatter/blob/master/scripts/generateExterns.php for details.
/**
* @const
*/
var punycode = {};
/**
* @param {string} domain
* @return {string}
*/
punycode.toASCII;
/** @constructor */
function XSLTProcessor() {}
/**
* @type {number}
* @const
*/
var Infinity;
/**
* @type {undefined}
* @const
*/
var undefined;
/** @typedef {?} */
var symbol;
/**
* @param {string=} opt_description
* @return {symbol}
*/
function Symbol(opt_description) {}
/**
* @param {string} uri
* @return {string}
* @throws {URIError} when used wrongly.
*/
function decodeURIComponent(uri) {}
/**
* @param {string} uri
* @return {string}
* @throws {URIError} if one attempts to encode a surrogate which is not part of
*/
function encodeURIComponent(uri) {}
/**
* @param {string} str
* @return {string}
* @nosideeffects
*/
function escape(str) {}
/**
* @param {*} num
* @return {boolean}
* @nosideeffects
*/
function isNaN(num) {}
/**
* @param {*} num
* @param {number|undefined} base
* @return {number}
* @nosideeffects
*/
function parseInt(num, base) {}
/**
* @constructor
* @implements {IArrayLike<T>}
* @implements {Iterable<T>}
* @param {...*} var_args
* @return {!Array<?>}
* @nosideeffects
* @template T
*/
function Array(var_args) {}
/**
* @param {?function(this:S, T, number, !Array<T>): ?} callback
* @param {S=} opt_thisobj
* @this {IArrayLike<T>|string}
* @template T,S
* @return {undefined}
*/
Array.prototype.forEach = function(callback, opt_thisobj) {};
/**
* @param {T} obj
* @param {number=} opt_fromIndex
* @return {number}
* @this {IArrayLike<T>|string}
* @nosideeffects
* @template T
*/
Array.prototype.indexOf = function(obj, opt_fromIndex) {};
/**
* @param {*=} opt_separator Specifies a string to separate each element of the
* @return {string}
* @this {IArrayLike<?>|string}
* @nosideeffects
*/
Array.prototype.join = function(opt_separator) {};
/**
* @type {number}
*/
Array.prototype.length;
/**
* @return {T}
* @this {IArrayLike<T>}
* @modifies {this}
* @template T
*/
Array.prototype.pop = function() {};
/**
* @param {...T} var_args
* @return {number} The new length of the array.
* @this {IArrayLike<T>}
* @template T
* @modifies {this}
*/
Array.prototype.push = function(var_args) {};
/**
* @return {THIS} A reference to the original modified array.
* @this {THIS}
* @template THIS
* @modifies {this}
*/
Array.prototype.reverse = function() {};
/**
* @this {IArrayLike<T>}
* @modifies {this}
* @return {T}
* @template T
*/
Array.prototype.shift = function() {};
/**
* @param {*=} opt_begin Zero-based index at which to begin extraction. A
* @param {*=} opt_end Zero-based index at which to end extraction. slice
* @return {!Array<T>}
* @this {IArrayLike<T>|string}
* @template T
* @nosideeffects
*/
Array.prototype.slice = function(opt_begin, opt_end) {};
/**
* @param {function(T,T):number=} opt_compareFn Specifies a function that
* @this {IArrayLike<T>}
* @template T
* @modifies {this}
* @return {!Array<T>}
*/
Array.prototype.sort = function(opt_compareFn) {};
/**
* @param {*=} opt_index Index at which to start changing the array. If negative, * will begin that many elements from the end. A non-number type will be
* @param {*=} opt_howMany An integer indicating the number of old array elements
* @param {...T} var_args
* @return {!Array<T>}
* @this {IArrayLike<T>}
* @modifies {this}
* @template T
*/
Array.prototype.splice = function(opt_index, opt_howMany, var_args) {};
/**
* @param {...*} var_args
* @return {number} The new length of the array
* @this {IArrayLike<?>}
* @modifies {this}
*/
Array.prototype.unshift = function(var_args) {};
/**
* @param {?=} opt_yr_num
* @param {?=} opt_mo_num
* @param {?=} opt_day_num
* @param {?=} opt_hr_num
* @param {?=} opt_min_num
* @param {?=} opt_sec_num
* @param {?=} opt_ms_num
* @constructor
* @return {string}
* @nosideeffects
*/
function Date(opt_yr_num, opt_mo_num, opt_day_num, opt_hr_num, opt_min_num, opt_sec_num, opt_ms_num) {}
/**
* @param {*} date
* @return {number}
* @nosideeffects
*/
Date.parse = function(date) {};
/**
* @constructor
* @param {...*} var_args
* @throws {Error}
*/
function Function(var_args) {}
/**
* @const
*/
var Math = {};
/**
* @param {?} x
* @return {number}
* @nosideeffects
*/
Math.floor = function(x) {};
/**
* @param {...?} var_args
* @return {number}
* @nosideeffects
*/
Math.max = function(var_args) {};
/**
* @param {...?} var_args
* @return {number}
* @nosideeffects
*/
Math.min = function(var_args) {};
/**
* @return {number}
* @nosideeffects
*/
Math.random = function() {};
/**
* @constructor
* @param {*=} opt_value
* @return {number}
* @nosideeffects
*/
function Number(opt_value) {}
/**
* @this {Number|number}
* @param {(number|Number)=} opt_radix An optional radix.
* @return {string}
* @nosideeffects
* @override
*/
Number.prototype.toString = function(opt_radix) {};
/**
* @constructor
* @param {*=} opt_value
* @return {!Object}
* @nosideeffects
*/
function Object(opt_value) {}
/**
* @this {*}
* @return {string}
* @nosideeffects
*/
Object.prototype.toString = function() {};
/**
* @constructor
* @param {*=} opt_pattern
* @param {*=} opt_flags
* @return {!RegExp}
* @throws {SyntaxError} if opt_pattern is an invalid pattern.
*/
function RegExp(opt_pattern, opt_flags) {}
/**
* @param {*} str The string to search.
* @return {Array<string>} This should really return an Array with a few
*/
RegExp.prototype.exec = function(str) {};
/**
* @type {number}
*/
RegExp.prototype.lastIndex;
/**
* @param {*} str The string to search.
* @return {boolean} Whether the string was matched.
*/
RegExp.prototype.test = function(str) {};
/**
* @constructor
* @implements {Iterable<string>}
* @param {*=} opt_str
* @return {string}
* @nosideeffects
*/
function String(opt_str) {}
/**
* @param {...number} var_args
* @return {string}
* @nosideeffects
*/
String.fromCharCode = function(var_args) {};
/**
* @this {String|string}
* @param {number} index
* @return {string}
* @nosideeffects
*/
String.prototype.charAt = function(index) {};
/**
* @this {String|string}
* @param {number=} opt_index
* @return {number}
* @nosideeffects
*/
String.prototype.charCodeAt = function(opt_index) {};
/**
* @this {String|string}
* @param {string|null} searchValue
* @param {(number|null)=} opt_fromIndex
* @return {number}
* @nosideeffects
*/
String.prototype.indexOf = function(searchValue, opt_fromIndex) {};
/**
* @type {number}
*/
String.prototype.length;
/**
* @this {String|string}
* @param {RegExp|string} pattern
* @param {string|Function} replacement
* @return {string}
*/
String.prototype.replace = function(pattern, replacement) {};
/**
* @this {String|string}
* @param {*=} opt_separator
* @param {number=} opt_limit
* @return {!Array<string>}
* @nosideeffects
*/
String.prototype.split = function(opt_separator, opt_limit) {};
/**
* @this {String|string}
* @param {number} start
* @param {number=} opt_length
* @return {string} The specified substring.
* @nosideeffects
*/
String.prototype.substr = function(start, opt_length) {};
/**
* @this {String|string}
* @return {string}
* @nosideeffects
*/
String.prototype.toLowerCase = function() {};
/**
* @this {String|string}
* @return {string}
* @nosideeffects
*/
String.prototype.toUpperCase = function() {};
/**
* @type {string}
* @implicitCast
*/
Element.prototype.innerHTML;
/**
* @constructor
*/
function DOMParser() {}
/**
* @param {string} src The UTF16 string to be parsed.
* @param {string} type The content type of the string.
* @return {Document}
*/
DOMParser.prototype.parseFromString = function(src, type) {};
/**
* @type {!Window}
*/
var window;
/**
* @constructor
* @extends {Node}
*/
function Document() {}
/**
* @return {!DocumentFragment}
* @nosideeffects
*/
Document.prototype.createDocumentFragment = function() {};
/**
* @param {string} tagName
* @param {string=} opt_typeExtension
* @return {!Element}
* @nosideeffects
*/
Document.prototype.createElement = function(tagName, opt_typeExtension) {};
/**
* @constructor
* @extends {Node}
*/
function DocumentFragment() {}
/**
* @constructor
* @implements {IObject<(string|number), T>}
* @implements {IArrayLike<T>}
* @implements {Iterable<T>}
* @template T
*/
function NamedNodeMap() {}
/**
* @param {number} index
* @return {Node}
* @nosideeffects
*/
NamedNodeMap.prototype.item = function(index) {};
/**
* @type {number}
*/
NamedNodeMap.prototype.length;
/**
* @constructor
*/
function Node() {}
/**
* @param {Node} newChild
* @return {!Node}
*/
Node.prototype.appendChild = function(newChild) {};
/**
* @type {!NodeList<!Node>}
*/
Node.prototype.childNodes;
/**
* @param {boolean} deep
* @return {!Node}
* @nosideeffects
*/
Node.prototype.cloneNode = function(deep) {};
/**
* @type {Node}
*/
Node.prototype.firstChild;
/**
* @param {Node} newChild
* @param {Node} refChild
* @return {!Node}
*/
Node.prototype.insertBefore = function(newChild, refChild) {};
/**
* @type {string}
*/
Node.prototype.nodeName;
/**
* @type {number}
*/
Node.prototype.nodeType;
/**
* @type {string}
*/
Node.prototype.nodeValue;
/**
* @type {Document}
*/
Node.prototype.ownerDocument;
/**
* @type {Node}
*/
Node.prototype.parentNode;
/**
* @param {Node} oldChild
* @return {!Node}
*/
Node.prototype.removeChild = function(oldChild) {};
/**
* @constructor
* @implements {IArrayLike<T>}
* @implements {Iterable<T>}
* @template T
*/
function NodeList() {}
/**
* @type {number}
*/
NodeList.prototype.length;
/**
* @constructor
* @extends {Node}
*/
function Element() {}
/**
* @constructor
*/
function Window() {}
/**
* @param {Node} externalNode
* @param {boolean} deep
* @return {Node}
*/
Document.prototype.importNode = function(externalNode, deep) {};
/**
* @constructor
* @extends {Document}
*/
function HTMLDocument() {}
/**
* @constructor
* @extends {Element}
*/
function HTMLElement() {}
/**
* @param {?string} namespaceURI
* @param {string} localName
* @return {string}
* @nosideeffects
*/
Element.prototype.getAttributeNS = function(namespaceURI, localName) {};
/**
* @param {?string} namespaceURI
* @param {string} localName
* @return {boolean}
* @nosideeffects
*/
Element.prototype.hasAttributeNS = function(namespaceURI, localName) {};
/**
* @param {?string} namespaceURI
* @param {string} localName
* @return {undefined}
*/
Element.prototype.removeAttributeNS = function(namespaceURI, localName) {};
/**
* @param {?string} namespaceURI
* @param {string} qualifiedName
* @param {string|number|boolean} value Values are converted to strings with
* @return {undefined}
*/
Element.prototype.setAttributeNS = function(namespaceURI, qualifiedName, value) {};
/**
* @param {Node} arg
* @return {boolean}
* @nosideeffects
*/
Node.prototype.isEqualNode = function(arg) {};
/**
* @type {string}
*/
Node.prototype.namespaceURI;
/**
* @type {string}
* @implicitCast
*/
Node.prototype.textContent;
/**
* @type {!HTMLDocument}
* @const
*/
var document;

View File

@@ -0,0 +1,18 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\RendererGenerators;
use s9e\TextFormatter\Configurator\RendererGenerator;
use s9e\TextFormatter\Configurator\Rendering;
use s9e\TextFormatter\Renderers\Unformatted as UnformattedRenderer;
class Unformatted implements RendererGenerator
{
public function getRenderer(Rendering $rendering)
{
return new UnformattedRenderer;
}
}

View File

@@ -0,0 +1,67 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\RendererGenerators;
use s9e\TextFormatter\Configurator\Helpers\TemplateHelper;
use s9e\TextFormatter\Configurator\RendererGenerator;
use s9e\TextFormatter\Configurator\RendererGenerators\XSLT\Optimizer;
use s9e\TextFormatter\Configurator\Rendering;
use s9e\TextFormatter\Renderers\XSLT as XSLTRenderer;
class XSLT implements RendererGenerator
{
public $optimizer;
public function __construct()
{
$this->optimizer = new Optimizer;
}
public function getRenderer(Rendering $rendering)
{
return new XSLTRenderer($this->getXSL($rendering));
}
public function getXSL(Rendering $rendering)
{
$groupedTemplates = [];
$prefixes = [];
$templates = $rendering->getTemplates();
TemplateHelper::replaceHomogeneousTemplates($templates, 3);
foreach ($templates as $tagName => $template)
{
$template = $this->optimizer->optimizeTemplate($template);
$groupedTemplates[$template][] = $tagName;
$pos = \strpos($tagName, ':');
if ($pos !== \false)
$prefixes[\substr($tagName, 0, $pos)] = 1;
}
$xsl = '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"';
$prefixes = \array_keys($prefixes);
\sort($prefixes);
foreach ($prefixes as $prefix)
$xsl .= ' xmlns:' . $prefix . '="urn:s9e:TextFormatter:' . $prefix . '"';
if (!empty($prefixes))
$xsl .= ' exclude-result-prefixes="' . \implode(' ', $prefixes) . '"';
$xsl .= '><xsl:output method="html" encoding="utf-8" indent="no"';
$xsl .= '/>';
foreach ($rendering->getAllParameters() as $paramName => $paramValue)
{
$xsl .= '<xsl:param name="' . \htmlspecialchars($paramName) . '"';
if ($paramValue === '')
$xsl .= '/>';
else
$xsl .= '>' . \htmlspecialchars($paramValue) . '</xsl:param>';
}
foreach ($groupedTemplates as $template => $tagNames)
{
$xsl .= '<xsl:template match="' . \implode('|', $tagNames) . '"';
if ($template === '')
$xsl .= '/>';
else
$xsl .= '>' . $template . '</xsl:template>';
}
$xsl .= '</xsl:stylesheet>';
return $xsl;
}
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\RendererGenerators\XSLT;
use s9e\TextFormatter\Configurator\TemplateNormalizer;
class Optimizer
{
public $normalizer;
public function __construct()
{
$this->normalizer = new TemplateNormalizer;
$this->normalizer->clear();
$this->normalizer->append('MergeConsecutiveCopyOf');
$this->normalizer->append('MergeIdenticalConditionalBranches');
$this->normalizer->append('OptimizeNestedConditionals');
$this->normalizer->append('RemoveLivePreviewAttributes');
}
public function optimizeTemplate($template)
{
return $this->normalizer->normalizeTemplate($template);
}
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\RulesGenerators;
use s9e\TextFormatter\Configurator\Helpers\TemplateInspector;
use s9e\TextFormatter\Configurator\RulesGenerators\Interfaces\TargetedRulesGenerator;
class AllowAll implements TargetedRulesGenerator
{
public function generateTargetedRules(TemplateInspector $src, TemplateInspector $trg)
{
return ['allowChild', 'allowDescendant'];
}
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\RulesGenerators;
use s9e\TextFormatter\Configurator\Helpers\TemplateInspector;
use s9e\TextFormatter\Configurator\RulesGenerators\Interfaces\BooleanRulesGenerator;
class ManageParagraphs implements BooleanRulesGenerator
{
protected $p;
public function __construct()
{
$this->p = new TemplateInspector('<p><xsl:apply-templates/></p>');
}
public function generateBooleanRules(TemplateInspector $src)
{
$rules = [];
if ($src->allowsChild($this->p) && $src->isBlock() && !$this->p->closesParent($src))
$rules['createParagraphs'] = \true;
if ($src->closesParent($this->p))
$rules['breakParagraph'] = \true;
return $rules;
}
}

View File

@@ -0,0 +1,30 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\TemplateChecks;
use DOMElement;
use DOMXPath;
use s9e\TextFormatter\Configurator\Exceptions\UnsafeTemplateException;
use s9e\TextFormatter\Configurator\Items\Tag;
use s9e\TextFormatter\Configurator\TemplateCheck;
class DisallowElement extends TemplateCheck
{
public $elName;
public function __construct($elName)
{
$this->elName = \strtolower($elName);
}
public function check(DOMElement $template, Tag $tag)
{
$xpath = new DOMXPath($template->ownerDocument);
$query
= '//*[translate(local-name(), "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz") = "' . $this->elName . '"]|//xsl:element[translate(@name,"ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz") = "' . $this->elName . '"]';
$node = $xpath->query($query)->item(0);
if ($node)
throw new UnsafeTemplateException("Element '" . $this->elName . "' is disallowed", $node);
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\TemplateChecks;
class DisallowFlashFullScreen extends AbstractFlashRestriction
{
public $defaultSetting = 'false';
protected $settingName = 'allowFullScreen';
protected $settings = [
'true' => 1,
'false' => 0
];
public function __construct($onlyIfDynamic = \false)
{
parent::__construct('false', $onlyIfDynamic);
}
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\TemplateChecks;
use DOMElement;
use DOMXPath;
use s9e\TextFormatter\Configurator\Exceptions\UnsafeTemplateException;
use s9e\TextFormatter\Configurator\Items\Tag;
use s9e\TextFormatter\Configurator\TemplateCheck;
class DisallowNodeByXPath extends TemplateCheck
{
public $query;
public function __construct($query)
{
$this->query = $query;
}
public function check(DOMElement $template, Tag $tag)
{
$xpath = new DOMXPath($template->ownerDocument);
foreach ($xpath->query($this->query) as $node)
throw new UnsafeTemplateException("Node '" . $node->nodeName . "' is disallowed because it matches '" . $this->query . "'", $node);
}
}

View File

@@ -0,0 +1,18 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\TemplateChecks;
class RestrictFlashNetworking extends AbstractFlashRestriction
{
public $defaultSetting = 'all';
protected $settingName = 'allowNetworking';
protected $settings = [
'all' => 3,
'internal' => 2,
'none' => 1
];
}

View File

@@ -0,0 +1,45 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
use DOMNode;
class ConvertCurlyExpressionsInText extends AbstractNormalization
{
protected $queries = ['//*[namespace-uri() != $XSL]/text()[contains(., "{@") or contains(., "{$")]'];
protected function insertTextBefore($text, $node)
{
if ($text > '')
$node->parentNode->insertBefore($this->createText($text), $node);
}
protected function normalizeNode(DOMNode $node)
{
$parentNode = $node->parentNode;
\preg_match_all(
'#\\{([$@][-\\w]+)\\}#',
$node->textContent,
$matches,
\PREG_SET_ORDER | \PREG_OFFSET_CAPTURE
);
$lastPos = 0;
foreach ($matches as $m)
{
$pos = $m[0][1];
if ($pos > $lastPos)
{
$text = \substr($node->textContent, $lastPos, $pos - $lastPos);
$this->insertTextBefore($text, $node);
}
$lastPos = $pos + \strlen($m[0][0]);
$parentNode
->insertBefore($this->createElement('xsl:value-of'), $node)
->setAttribute('select', $m[1][0]);
}
$text = \substr($node->textContent, $lastPos);
$this->insertTextBefore($text, $node);
$parentNode->removeChild($node);
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
use DOMElement;
class Custom extends AbstractNormalization
{
protected $callback;
public function __construct(callable $callback)
{
$this->callback = $callback;
}
public function normalize(DOMElement $template)
{
\call_user_func($this->callback, $template);
}
}

View File

@@ -0,0 +1,25 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
use DOMElement;
class MergeConsecutiveCopyOf extends AbstractNormalization
{
protected $queries = ['//xsl:copy-of'];
protected function normalizeElement(DOMElement $element)
{
while ($this->nextSiblingIsCopyOf($element))
{
$element->setAttribute('select', $element->getAttribute('select') . '|' . $element->nextSibling->getAttribute('select'));
$element->parentNode->removeChild($element->nextSibling);
}
}
protected function nextSiblingIsCopyOf(DOMElement $element)
{
return ($element->nextSibling && $this->isXsl($element->nextSibling, 'copy-of'));
}
}

View File

@@ -0,0 +1,89 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
use DOMElement;
use DOMNode;
use s9e\TextFormatter\Configurator\Helpers\XPathHelper;
class MergeIdenticalConditionalBranches extends AbstractNormalization
{
protected $queries = ['//xsl:choose'];
protected function collectCompatibleBranches(DOMNode $node)
{
$nodes = [];
$key = \null;
$values = [];
while ($node && $this->isXsl($node, 'when'))
{
$branch = XPathHelper::parseEqualityExpr($node->getAttribute('test'));
if ($branch === \false || \count($branch) !== 1)
break;
if (isset($key) && \key($branch) !== $key)
break;
if (\array_intersect($values, \end($branch)))
break;
$key = \key($branch);
$values = \array_merge($values, \end($branch));
$nodes[] = $node;
$node = $node->nextSibling;
}
return $nodes;
}
protected function mergeBranches(array $nodes)
{
$sortedNodes = [];
foreach ($nodes as $node)
{
$outerXML = $node->ownerDocument->saveXML($node);
$innerXML = \preg_replace('([^>]+>(.*)<[^<]+)s', '$1', $outerXML);
$sortedNodes[$innerXML][] = $node;
}
foreach ($sortedNodes as $identicalNodes)
{
if (\count($identicalNodes) < 2)
continue;
$expr = [];
foreach ($identicalNodes as $i => $node)
{
$expr[] = $node->getAttribute('test');
if ($i > 0)
$node->parentNode->removeChild($node);
}
$identicalNodes[0]->setAttribute('test', \implode(' or ', $expr));
}
}
protected function mergeCompatibleBranches(DOMElement $choose)
{
$node = $choose->firstChild;
while ($node)
{
$nodes = $this->collectCompatibleBranches($node);
if (\count($nodes) > 1)
{
$node = \end($nodes)->nextSibling;
$this->mergeBranches($nodes);
}
else
$node = $node->nextSibling;
}
}
protected function mergeConsecutiveBranches(DOMElement $choose)
{
$nodes = [];
foreach ($choose->childNodes as $node)
if ($this->isXsl($node, 'when'))
$nodes[] = $node;
$i = \count($nodes);
while (--$i > 0)
$this->mergeBranches([$nodes[$i - 1], $nodes[$i]]);
}
protected function normalizeElement(DOMElement $element)
{
$this->mergeCompatibleBranches($element);
$this->mergeConsecutiveBranches($element);
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
use DOMElement;
class OptimizeNestedConditionals extends AbstractNormalization
{
protected $queries = ['//xsl:choose/xsl:otherwise[count(node()) = 1]/xsl:choose'];
protected function normalizeElement(DOMElement $element)
{
$otherwise = $element->parentNode;
$outerChoose = $otherwise->parentNode;
while ($element->firstChild)
$outerChoose->appendChild($element->removeChild($element->firstChild));
$outerChoose->removeChild($otherwise);
}
}

View File

@@ -0,0 +1,22 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
use DOMElement;
class SortAttributesByName extends AbstractNormalization
{
protected $queries = ['//*[@*]'];
protected function normalizeElement(DOMElement $element)
{
$attributes = [];
foreach ($element->attributes as $name => $attribute)
$attributes[$name] = $element->removeAttributeNode($attribute);
\ksort($attributes);
foreach ($attributes as $attribute)
$element->setAttributeNode($attribute);
}
}

View File

@@ -0,0 +1,18 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
use DOMNode;
class TransposeComments extends AbstractNormalization
{
protected $queries = ['//comment()'];
protected function normalizeNode(DOMNode $node)
{
$xslComment = $this->createElement('xsl:comment', $node->nodeValue);
$node->parentNode->replaceChild($xslComment, $node);
}
}

View File

@@ -0,0 +1,23 @@
<?php
/*
* @package s9e\TextFormatter
* @copyright Copyright (c) 2010-2019 The s9e Authors
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator\Validators;
use InvalidArgumentException;
abstract class TemplateParameterName
{
public static function isValid($name)
{
return (bool) \preg_match('#^[a-z_][-a-z_0-9]*$#Di', $name);
}
public static function normalize($name)
{
$name = (string) $name;
if (!static::isValid($name))
throw new InvalidArgumentException("Invalid parameter name '" . $name . "'");
return $name;
}
}