Augmentation vers version 3.3.0
This commit is contained in:
20
vendor/s9e/regexp-builder/LICENSE
vendored
Normal file
20
vendor/s9e/regexp-builder/LICENSE
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2018 The s9e Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
25
vendor/s9e/regexp-builder/composer.json
vendored
Normal file
25
vendor/s9e/regexp-builder/composer.json
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "s9e/regexp-builder",
|
||||
"type": "library",
|
||||
"description": "Single-purpose library that generates regular expressions that match a list of strings.",
|
||||
"homepage": "https://github.com/s9e/RegexpBuilder/",
|
||||
"keywords": ["regexp"],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"lib-pcre": ">=7.2",
|
||||
"php": ">=5.5.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "<5.8"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"s9e\\RegexpBuilder\\": "src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"s9e\\RegexpBuilder\\Tests\\": "tests"
|
||||
}
|
||||
}
|
||||
}
|
||||
167
vendor/s9e/regexp-builder/src/Builder.php
vendored
Normal file
167
vendor/s9e/regexp-builder/src/Builder.php
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder;
|
||||
|
||||
use s9e\RegexpBuilder\Input\InputInterface;
|
||||
use s9e\RegexpBuilder\Output\OutputInterface;
|
||||
use s9e\RegexpBuilder\Passes\CoalesceOptionalStrings;
|
||||
use s9e\RegexpBuilder\Passes\CoalesceSingleCharacterPrefix;
|
||||
use s9e\RegexpBuilder\Passes\GroupSingleCharacters;
|
||||
use s9e\RegexpBuilder\Passes\MergePrefix;
|
||||
use s9e\RegexpBuilder\Passes\MergeSuffix;
|
||||
use s9e\RegexpBuilder\Passes\PromoteSingleStrings;
|
||||
use s9e\RegexpBuilder\Passes\Recurse;
|
||||
|
||||
class Builder
|
||||
{
|
||||
/**
|
||||
* @var InputInterface
|
||||
*/
|
||||
protected $input;
|
||||
|
||||
/**
|
||||
* @var MetaCharacters
|
||||
*/
|
||||
protected $meta;
|
||||
|
||||
/**
|
||||
* @var Runner
|
||||
*/
|
||||
protected $runner;
|
||||
|
||||
/**
|
||||
* @var Serializer
|
||||
*/
|
||||
protected $serializer;
|
||||
|
||||
/**
|
||||
* @param array $config
|
||||
*/
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$config += [
|
||||
'delimiter' => '/',
|
||||
'input' => 'Bytes',
|
||||
'inputOptions' => [],
|
||||
'meta' => [],
|
||||
'output' => 'Bytes',
|
||||
'outputOptions' => []
|
||||
];
|
||||
|
||||
$this->setInput($config['input'], $config['inputOptions']);
|
||||
$this->setMeta($config['meta']);
|
||||
$this->setSerializer($config['output'], $config['outputOptions'], $config['delimiter']);
|
||||
$this->setRunner();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and return a regular expression that matches all of the given strings
|
||||
*
|
||||
* @param string[] $strings Literal strings to be matched
|
||||
* @return string Regular expression (without delimiters)
|
||||
*/
|
||||
public function build(array $strings)
|
||||
{
|
||||
$strings = array_unique($strings);
|
||||
sort($strings);
|
||||
if ($this->isEmpty($strings))
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
$strings = $this->splitStrings($strings);
|
||||
$strings = $this->meta->replaceMeta($strings);
|
||||
$strings = $this->runner->run($strings);
|
||||
|
||||
return $this->serializer->serializeStrings($strings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the list of strings is empty
|
||||
*
|
||||
* @param string[] $strings
|
||||
* @return bool
|
||||
*/
|
||||
protected function isEmpty(array $strings)
|
||||
{
|
||||
return (empty($strings) || $strings === ['']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the InputInterface instance in $this->input
|
||||
*
|
||||
* @param string $inputType
|
||||
* @param array $inputOptions
|
||||
* @return void
|
||||
*/
|
||||
protected function setInput($inputType, array $inputOptions)
|
||||
{
|
||||
$className = __NAMESPACE__ . '\\Input\\' . $inputType;
|
||||
$this->input = new $className($inputOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the MetaCharacters instance in $this->meta
|
||||
*
|
||||
* @param array $map
|
||||
* @return void
|
||||
*/
|
||||
protected function setMeta(array $map)
|
||||
{
|
||||
$this->meta = new MetaCharacters($this->input);
|
||||
foreach ($map as $char => $expr)
|
||||
{
|
||||
$this->meta->add($char, $expr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Runner instance $in this->runner
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setRunner()
|
||||
{
|
||||
$this->runner = new Runner;
|
||||
$this->runner->addPass(new MergePrefix);
|
||||
$this->runner->addPass(new GroupSingleCharacters);
|
||||
$this->runner->addPass(new Recurse($this->runner));
|
||||
$this->runner->addPass(new PromoteSingleStrings);
|
||||
$this->runner->addPass(new CoalesceOptionalStrings);
|
||||
$this->runner->addPass(new MergeSuffix);
|
||||
$this->runner->addPass(new CoalesceSingleCharacterPrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Serializer instance in $this->serializer
|
||||
*
|
||||
* @param string $outputType
|
||||
* @param array $outputOptions
|
||||
* @param string $delimiter
|
||||
* @return void
|
||||
*/
|
||||
protected function setSerializer($outputType, array $outputOptions, $delimiter)
|
||||
{
|
||||
$className = __NAMESPACE__ . '\\Output\\' . $outputType;
|
||||
$output = new $className($outputOptions);
|
||||
$escaper = new Escaper($delimiter);
|
||||
|
||||
$this->serializer = new Serializer($output, $this->meta, $escaper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Split all given strings by character
|
||||
*
|
||||
* @param string[] $strings List of strings
|
||||
* @return array[] List of arrays
|
||||
*/
|
||||
protected function splitStrings(array $strings)
|
||||
{
|
||||
return array_map([$this->input, 'split'], $strings);
|
||||
}
|
||||
}
|
||||
59
vendor/s9e/regexp-builder/src/Escaper.php
vendored
Normal file
59
vendor/s9e/regexp-builder/src/Escaper.php
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder;
|
||||
|
||||
class Escaper
|
||||
{
|
||||
/**
|
||||
* @var array Characters to escape in a character class
|
||||
*/
|
||||
public $inCharacterClass = ['-' => '\\-', '\\' => '\\\\', ']' => '\\]', '^' => '\\^'];
|
||||
|
||||
/**
|
||||
* @var array Characters to escape outside of a character class
|
||||
*/
|
||||
public $inLiteral = [
|
||||
'$' => '\\$', '(' => '\\(', ')' => '\\)', '*' => '\\*',
|
||||
'+' => '\\+', '.' => '\\.', '?' => '\\?', '[' => '\\]',
|
||||
'\\' => '\\\\', '^' => '\\^', '{' => '\\{', '|' => '\\|'
|
||||
];
|
||||
|
||||
/**
|
||||
* @param string $delimiter Delimiter used in the final regexp
|
||||
*/
|
||||
public function __construct($delimiter = '/')
|
||||
{
|
||||
foreach (str_split($delimiter, 1) as $char)
|
||||
{
|
||||
$this->inCharacterClass[$char] = '\\' . $char;
|
||||
$this->inLiteral[$char] = '\\' . $char;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape given character to be used in a character class
|
||||
*
|
||||
* @param string $char Original character
|
||||
* @return string Escaped character
|
||||
*/
|
||||
public function escapeCharacterClass($char)
|
||||
{
|
||||
return (isset($this->inCharacterClass[$char])) ? $this->inCharacterClass[$char] : $char;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape given character to be used outside of a character class
|
||||
*
|
||||
* @param string $char Original character
|
||||
* @return string Escaped character
|
||||
*/
|
||||
public function escapeLiteral($char)
|
||||
{
|
||||
return (isset($this->inLiteral[$char])) ? $this->inLiteral[$char] : $char;
|
||||
}
|
||||
}
|
||||
23
vendor/s9e/regexp-builder/src/Input/BaseImplementation.php
vendored
Normal file
23
vendor/s9e/regexp-builder/src/Input/BaseImplementation.php
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder\Input;
|
||||
|
||||
abstract class BaseImplementation implements InputInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
abstract public function split($string);
|
||||
}
|
||||
24
vendor/s9e/regexp-builder/src/Input/Bytes.php
vendored
Normal file
24
vendor/s9e/regexp-builder/src/Input/Bytes.php
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder\Input;
|
||||
|
||||
class Bytes extends BaseImplementation
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function split($string)
|
||||
{
|
||||
if ($string === '')
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map('ord', str_split($string, 1));
|
||||
}
|
||||
}
|
||||
24
vendor/s9e/regexp-builder/src/Input/InputInterface.php
vendored
Normal file
24
vendor/s9e/regexp-builder/src/Input/InputInterface.php
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder\Input;
|
||||
|
||||
interface InputInterface
|
||||
{
|
||||
/**
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct(array $options = []);
|
||||
|
||||
/**
|
||||
* Split given string into a list of values
|
||||
*
|
||||
* @param string $string
|
||||
* @return integer[]
|
||||
*/
|
||||
public function split($string);
|
||||
}
|
||||
101
vendor/s9e/regexp-builder/src/Input/Utf8.php
vendored
Normal file
101
vendor/s9e/regexp-builder/src/Input/Utf8.php
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder\Input;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class Utf8 extends BaseImplementation
|
||||
{
|
||||
/**
|
||||
* @var bool Whether to use surrogates to represent higher codepoints
|
||||
*/
|
||||
protected $useSurrogates;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->useSurrogates = !empty($options['useSurrogates']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function split($string)
|
||||
{
|
||||
if (preg_match_all('(.)us', $string, $matches) === false)
|
||||
{
|
||||
throw new InvalidArgumentException('Invalid UTF-8 string');
|
||||
}
|
||||
|
||||
return ($this->useSurrogates) ? $this->charsToCodepointsWithSurrogates($matches[0]) : $this->charsToCodepoints($matches[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a list of UTF-8 characters into a list of Unicode codepoint
|
||||
*
|
||||
* @param string[] $chars
|
||||
* @return integer[]
|
||||
*/
|
||||
protected function charsToCodepoints(array $chars)
|
||||
{
|
||||
return array_map([$this, 'cp'], $chars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a list of UTF-8 characters into a list of Unicode codepoint with surrogates
|
||||
*
|
||||
* @param string[] $chars
|
||||
* @return integer[]
|
||||
*/
|
||||
protected function charsToCodepointsWithSurrogates(array $chars)
|
||||
{
|
||||
$codepoints = [];
|
||||
foreach ($chars as $char)
|
||||
{
|
||||
$cp = $this->cp($char);
|
||||
if ($cp < 0x10000)
|
||||
{
|
||||
$codepoints[] = $cp;
|
||||
}
|
||||
else
|
||||
{
|
||||
$codepoints[] = 0xD7C0 + ($cp >> 10);
|
||||
$codepoints[] = 0xDC00 + ($cp & 0x3FF);
|
||||
}
|
||||
}
|
||||
|
||||
return $codepoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute and return the Unicode codepoint for given UTF-8 char
|
||||
*
|
||||
* @param string $char UTF-8 char
|
||||
* @return integer
|
||||
*/
|
||||
protected function cp($char)
|
||||
{
|
||||
$cp = ord($char[0]);
|
||||
if ($cp >= 0xF0)
|
||||
{
|
||||
$cp = ($cp << 18) + (ord($char[1]) << 12) + (ord($char[2]) << 6) + ord($char[3]) - 0x3C82080;
|
||||
}
|
||||
elseif ($cp >= 0xE0)
|
||||
{
|
||||
$cp = ($cp << 12) + (ord($char[1]) << 6) + ord($char[2]) - 0xE2080;
|
||||
}
|
||||
elseif ($cp >= 0xC0)
|
||||
{
|
||||
$cp = ($cp << 6) + ord($char[1]) - 0x3080;
|
||||
}
|
||||
|
||||
return $cp;
|
||||
}
|
||||
}
|
||||
222
vendor/s9e/regexp-builder/src/MetaCharacters.php
vendored
Normal file
222
vendor/s9e/regexp-builder/src/MetaCharacters.php
vendored
Normal file
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use s9e\RegexpBuilder\Input\InputInterface;
|
||||
|
||||
class MetaCharacters
|
||||
{
|
||||
/**
|
||||
* @const Bit value that indicates whether a meta-character represents a single character usable
|
||||
* in a character class
|
||||
*/
|
||||
const IS_CHAR = 1;
|
||||
|
||||
/**
|
||||
* @const Bit value that indicates whether a meta-character represents a quantifiable expression
|
||||
*/
|
||||
const IS_QUANTIFIABLE = 2;
|
||||
|
||||
/**
|
||||
* @var array Map of meta values and the expression they represent
|
||||
*/
|
||||
protected $exprs = [];
|
||||
|
||||
/**
|
||||
* @var InputInterface
|
||||
*/
|
||||
protected $input;
|
||||
|
||||
/**
|
||||
* @var array Map of meta-characters' codepoints and their value
|
||||
*/
|
||||
protected $meta = [];
|
||||
|
||||
/**
|
||||
* @param InputInterface $input
|
||||
*/
|
||||
public function __construct(InputInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a meta-character to the list
|
||||
*
|
||||
* @param string $char Meta-character
|
||||
* @param string $expr Regular expression
|
||||
* @return void
|
||||
*/
|
||||
public function add($char, $expr)
|
||||
{
|
||||
$split = $this->input->split($char);
|
||||
if (count($split) !== 1)
|
||||
{
|
||||
throw new InvalidArgumentException('Meta-characters must be represented by exactly one character');
|
||||
}
|
||||
if (@preg_match('(' . $expr . ')u', '') === false)
|
||||
{
|
||||
throw new InvalidArgumentException("Invalid expression '" . $expr . "'");
|
||||
}
|
||||
|
||||
$inputValue = $split[0];
|
||||
$metaValue = $this->computeValue($expr);
|
||||
|
||||
$this->exprs[$metaValue] = $expr;
|
||||
$this->meta[$inputValue] = $metaValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the expression associated with a meta value
|
||||
*
|
||||
* @param integer $metaValue
|
||||
* @return string
|
||||
*/
|
||||
public function getExpression($metaValue)
|
||||
{
|
||||
if (!isset($this->exprs[$metaValue]))
|
||||
{
|
||||
throw new InvalidArgumentException('Invalid meta value ' . $metaValue);
|
||||
}
|
||||
|
||||
return $this->exprs[$metaValue];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether a given value represents a single character usable in a character class
|
||||
*
|
||||
* @param integer $value
|
||||
* @return bool
|
||||
*/
|
||||
public static function isChar($value)
|
||||
{
|
||||
return ($value >= 0 || ($value & self::IS_CHAR));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether a given value represents a quantifiable expression
|
||||
*
|
||||
* @param integer $value
|
||||
* @return bool
|
||||
*/
|
||||
public static function isQuantifiable($value)
|
||||
{
|
||||
return ($value >= 0 || ($value & self::IS_QUANTIFIABLE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace values from meta-characters in a list of strings with their meta value
|
||||
*
|
||||
* @param array[] $strings
|
||||
* @return array[]
|
||||
*/
|
||||
public function replaceMeta(array $strings)
|
||||
{
|
||||
foreach ($strings as &$string)
|
||||
{
|
||||
foreach ($string as &$value)
|
||||
{
|
||||
if (isset($this->meta[$value]))
|
||||
{
|
||||
$value = $this->meta[$value];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $strings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute and return a value for given expression
|
||||
*
|
||||
* Values are meant to be a unique negative integer. The last 2 bits indicate whether the
|
||||
* expression is quantifiable and/or represents a single character.
|
||||
*
|
||||
* @param string $expr Regular expression
|
||||
* @return integer
|
||||
*/
|
||||
protected function computeValue($expr)
|
||||
{
|
||||
$properties = [
|
||||
'exprIsChar' => self::IS_CHAR,
|
||||
'exprIsQuantifiable' => self::IS_QUANTIFIABLE
|
||||
];
|
||||
$value = (1 + count($this->meta)) * -pow(2, count($properties));
|
||||
foreach ($properties as $methodName => $bitValue)
|
||||
{
|
||||
if ($this->$methodName($expr))
|
||||
{
|
||||
$value |= $bitValue;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether given expression represents a single character usable in a character class
|
||||
*
|
||||
* @param string $expr
|
||||
* @return bool
|
||||
*/
|
||||
protected function exprIsChar($expr)
|
||||
{
|
||||
$regexps = [
|
||||
// Escaped literal or escape sequence such as \w but not \R
|
||||
'(^\\\\[adefhnrstvwDHNSVW\\W]$)D',
|
||||
|
||||
// Unicode properties such as \pL or \p{Lu}
|
||||
'(^\\\\p(?:.|\\{[^}]+\\})$)Di',
|
||||
|
||||
// An escape sequence such as \x1F or \x{2600}
|
||||
'(^\\\\x(?:[0-9a-f]{2}|\\{[^}]+\\})$)Di'
|
||||
];
|
||||
|
||||
return $this->matchesAny($expr, $regexps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether given expression is quantifiable
|
||||
*
|
||||
* @param string $expr
|
||||
* @return bool
|
||||
*/
|
||||
protected function exprIsQuantifiable($expr)
|
||||
{
|
||||
$regexps = [
|
||||
// A dot or \R
|
||||
'(^(?:\\.|\\\\R)$)D',
|
||||
|
||||
// A character class
|
||||
'(^\\[\\^?(?:([^\\\\\\]]|\\\\.)(?:-(?-1))?)++\\]$)D'
|
||||
];
|
||||
|
||||
return $this->matchesAny($expr, $regexps) || $this->exprIsChar($expr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether given expression matches any of the given regexps
|
||||
*
|
||||
* @param string $expr
|
||||
* @param string[] $regexps
|
||||
* @return bool
|
||||
*/
|
||||
protected function matchesAny($expr, array $regexps)
|
||||
{
|
||||
foreach ($regexps as $regexp)
|
||||
{
|
||||
if (preg_match($regexp, $expr))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
62
vendor/s9e/regexp-builder/src/Output/BaseImplementation.php
vendored
Normal file
62
vendor/s9e/regexp-builder/src/Output/BaseImplementation.php
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder\Output;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
abstract class BaseImplementation implements OutputInterface
|
||||
{
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
protected $maxValue = 0;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
protected $minValue = 0;
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function output($value)
|
||||
{
|
||||
$this->validate($value);
|
||||
|
||||
return $this->outputValidValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate given value
|
||||
*
|
||||
* @param integer $value
|
||||
* @return void
|
||||
*/
|
||||
protected function validate($value)
|
||||
{
|
||||
if ($value < $this->minValue || $value > $this->maxValue)
|
||||
{
|
||||
throw new InvalidArgumentException('Value ' . $value . ' is out of bounds (' . $this->minValue . '..' . $this->maxValue . ')');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a valid value into a character
|
||||
*
|
||||
* @param integer $value
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function outputValidValue($value);
|
||||
}
|
||||
22
vendor/s9e/regexp-builder/src/Output/Bytes.php
vendored
Normal file
22
vendor/s9e/regexp-builder/src/Output/Bytes.php
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder\Output;
|
||||
|
||||
class Bytes extends BaseImplementation
|
||||
{
|
||||
/** {@inheritdoc} */
|
||||
protected $maxValue = 255;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function outputValidValue($value)
|
||||
{
|
||||
return chr($value);
|
||||
}
|
||||
}
|
||||
24
vendor/s9e/regexp-builder/src/Output/JavaScript.php
vendored
Normal file
24
vendor/s9e/regexp-builder/src/Output/JavaScript.php
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder\Output;
|
||||
|
||||
class JavaScript extends PrintableAscii
|
||||
{
|
||||
/** {@inheritdoc} */
|
||||
protected $maxValue = 0x10FFFF;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function escapeUnicode($cp)
|
||||
{
|
||||
$format = ($cp > 0xFFFF) ? '\\u{%' . $this->hexCase . '}' : '\\u%04' . $this->hexCase;
|
||||
|
||||
return sprintf($format, $cp);
|
||||
}
|
||||
}
|
||||
19
vendor/s9e/regexp-builder/src/Output/OutputInterface.php
vendored
Normal file
19
vendor/s9e/regexp-builder/src/Output/OutputInterface.php
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder\Output;
|
||||
|
||||
interface OutputInterface
|
||||
{
|
||||
/**
|
||||
* Serialize a value into a character
|
||||
*
|
||||
* @param integer $value
|
||||
* @return string
|
||||
*/
|
||||
public function output($value);
|
||||
}
|
||||
22
vendor/s9e/regexp-builder/src/Output/PHP.php
vendored
Normal file
22
vendor/s9e/regexp-builder/src/Output/PHP.php
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder\Output;
|
||||
|
||||
class PHP extends PrintableAscii
|
||||
{
|
||||
/** {@inheritdoc} */
|
||||
protected $maxValue = 0x10FFFF;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function escapeUnicode($cp)
|
||||
{
|
||||
return sprintf('\\x{%04' . $this->hexCase . '}', $cp);
|
||||
}
|
||||
}
|
||||
74
vendor/s9e/regexp-builder/src/Output/PrintableAscii.php
vendored
Normal file
74
vendor/s9e/regexp-builder/src/Output/PrintableAscii.php
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder\Output;
|
||||
|
||||
abstract class PrintableAscii extends BaseImplementation
|
||||
{
|
||||
/**
|
||||
* @var string 'x' for lowercase hexadecimal symbols, 'X' for uppercase
|
||||
*/
|
||||
protected $hexCase;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->hexCase = (isset($options['case']) && $options['case'] === 'lower') ? 'x' : 'X';
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape given ASCII codepoint
|
||||
*
|
||||
* @param integer $cp
|
||||
* @return string
|
||||
*/
|
||||
protected function escapeAscii($cp)
|
||||
{
|
||||
return '\\x' . sprintf('%02' . $this->hexCase, $cp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape given control code
|
||||
*
|
||||
* @param integer $cp
|
||||
* @return string
|
||||
*/
|
||||
protected function escapeControlCode($cp)
|
||||
{
|
||||
$table = [9 => '\\t', 10 => '\\n', 13 => '\\r'];
|
||||
|
||||
return (isset($table[$cp])) ? $table[$cp] : $this->escapeAscii($cp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the representation of a unicode character
|
||||
*
|
||||
* @param integer $cp Unicode codepoint
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function escapeUnicode($cp);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function outputValidValue($value)
|
||||
{
|
||||
if ($value < 32)
|
||||
{
|
||||
return $this->escapeControlCode($value);
|
||||
}
|
||||
|
||||
if ($value < 127)
|
||||
{
|
||||
return chr($value);
|
||||
}
|
||||
|
||||
return ($value > 255) ? $this->escapeUnicode($value) : $this->escapeAscii($value);
|
||||
}
|
||||
}
|
||||
54
vendor/s9e/regexp-builder/src/Output/Utf8.php
vendored
Normal file
54
vendor/s9e/regexp-builder/src/Output/Utf8.php
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder\Output;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class Utf8 extends BaseImplementation
|
||||
{
|
||||
/** {@inheritdoc} */
|
||||
protected $maxValue = 0x10FFFF;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function outputValidValue($value)
|
||||
{
|
||||
if ($value < 0x80)
|
||||
{
|
||||
return chr($value);
|
||||
}
|
||||
if ($value < 0x800)
|
||||
{
|
||||
return chr(0xC0 | ($value >> 6)) . chr(0x80 | ($value & 0x3F));
|
||||
}
|
||||
if ($value < 0x10000)
|
||||
{
|
||||
return chr(0xE0 | ($value >> 12))
|
||||
. chr(0x80 | (($value >> 6) & 0x3F))
|
||||
. chr(0x80 | ($value & 0x3F));
|
||||
}
|
||||
return chr(0xF0 | ($value >> 18))
|
||||
. chr(0x80 | (($value >> 12) & 0x3F))
|
||||
. chr(0x80 | (($value >> 6) & 0x3F))
|
||||
. chr(0x80 | ($value & 0x3F));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function validate($value)
|
||||
{
|
||||
if ($value >= 0xD800 && $value <= 0xDFFF)
|
||||
{
|
||||
throw new InvalidArgumentException(sprintf('Surrogate 0x%X is not a valid UTF-8 character', $value));
|
||||
}
|
||||
|
||||
parent::validate($value);
|
||||
}
|
||||
}
|
||||
148
vendor/s9e/regexp-builder/src/Passes/AbstractPass.php
vendored
Normal file
148
vendor/s9e/regexp-builder/src/Passes/AbstractPass.php
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder\Passes;
|
||||
|
||||
abstract class AbstractPass implements PassInterface
|
||||
{
|
||||
/**
|
||||
* @var bool Whether the current set of strings is optional
|
||||
*/
|
||||
protected $isOptional;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function run(array $strings)
|
||||
{
|
||||
$strings = $this->beforeRun($strings);
|
||||
if ($this->canRun($strings))
|
||||
{
|
||||
$strings = $this->runPass($strings);
|
||||
}
|
||||
$strings = $this->afterRun($strings);
|
||||
|
||||
return $strings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the list of strings after the pass is run
|
||||
*
|
||||
* @param array[] $strings
|
||||
* @return array[]
|
||||
*/
|
||||
protected function afterRun(array $strings)
|
||||
{
|
||||
if ($this->isOptional && $strings[0] !== [])
|
||||
{
|
||||
array_unshift($strings, []);
|
||||
}
|
||||
|
||||
return $strings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the list of strings before the pass is run
|
||||
*
|
||||
* @param array[] $strings
|
||||
* @return array[]
|
||||
*/
|
||||
protected function beforeRun(array $strings)
|
||||
{
|
||||
$this->isOptional = (isset($strings[0]) && $strings[0] === []);
|
||||
if ($this->isOptional)
|
||||
{
|
||||
array_shift($strings);
|
||||
}
|
||||
|
||||
return $strings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether this pass can be run on a given list of strings
|
||||
*
|
||||
* @param array[] $strings
|
||||
* @return bool
|
||||
*/
|
||||
protected function canRun(array $strings)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run this pass on a list of strings
|
||||
*
|
||||
* @param array[] $strings
|
||||
* @return array[]
|
||||
*/
|
||||
abstract protected function runPass(array $strings);
|
||||
|
||||
/**
|
||||
* Test whether given string has an optional suffix
|
||||
*
|
||||
* @param array $string
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasOptionalSuffix(array $string)
|
||||
{
|
||||
$suffix = end($string);
|
||||
|
||||
return (is_array($suffix) && $suffix[0] === []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether given string contains a single alternations made of single values
|
||||
*
|
||||
* @param array $string
|
||||
* @return bool
|
||||
*/
|
||||
protected function isCharacterClassString(array $string)
|
||||
{
|
||||
return ($this->isSingleAlternationString($string) && $this->isSingleCharStringList($string[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether given string contains one single element that is an alternation
|
||||
*
|
||||
* @param array
|
||||
* @return bool
|
||||
*/
|
||||
protected function isSingleAlternationString(array $string)
|
||||
{
|
||||
return (count($string) === 1 && is_array($string[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether given string contains a single character value
|
||||
*
|
||||
* @param array $string
|
||||
* @return bool
|
||||
*/
|
||||
protected function isSingleCharString(array $string)
|
||||
{
|
||||
return (count($string) === 1 && !is_array($string[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether given list of strings contains nothing but single-char strings
|
||||
*
|
||||
* @param array[] $strings
|
||||
* @return bool
|
||||
*/
|
||||
protected function isSingleCharStringList(array $strings)
|
||||
{
|
||||
foreach ($strings as $string)
|
||||
{
|
||||
if (!$this->isSingleCharString($string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
138
vendor/s9e/regexp-builder/src/Passes/CoalesceOptionalStrings.php
vendored
Normal file
138
vendor/s9e/regexp-builder/src/Passes/CoalesceOptionalStrings.php
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder\Passes;
|
||||
|
||||
/**
|
||||
* Replaces (?:ab?|b)? with a?b?
|
||||
*/
|
||||
class CoalesceOptionalStrings extends AbstractPass
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function canRun(array $strings)
|
||||
{
|
||||
return ($this->isOptional && count($strings) > 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function runPass(array $strings)
|
||||
{
|
||||
foreach ($this->getPrefixGroups($strings) as $suffix => $prefixStrings)
|
||||
{
|
||||
$suffix = unserialize($suffix);
|
||||
$suffixStrings = array_diff_key($strings, $prefixStrings);
|
||||
if ($suffix === $this->buildSuffix($suffixStrings))
|
||||
{
|
||||
$this->isOptional = false;
|
||||
|
||||
return $this->buildCoalescedStrings($prefixStrings, $suffix);
|
||||
}
|
||||
}
|
||||
|
||||
return $strings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the final list of coalesced strings
|
||||
*
|
||||
* @param array[] $prefixStrings
|
||||
* @param array $suffix
|
||||
* @return array[]
|
||||
*/
|
||||
protected function buildCoalescedStrings(array $prefixStrings, array $suffix)
|
||||
{
|
||||
$strings = $this->runPass($this->buildPrefix($prefixStrings));
|
||||
if (count($strings) === 1 && $strings[0][0][0] === [])
|
||||
{
|
||||
// If the prefix has been remerged into a list of strings which contains only one string
|
||||
// of which the first element is an optional alternations, we only need to append the
|
||||
// suffix
|
||||
$strings[0][] = $suffix;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Put the current list of strings that form the prefix into a new list of strings, of
|
||||
// which the only string is composed of our optional prefix followed by the suffix
|
||||
array_unshift($strings, []);
|
||||
$strings = [[$strings, $suffix]];
|
||||
}
|
||||
|
||||
return $strings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the list of strings used as prefix
|
||||
*
|
||||
* @param array[] $strings
|
||||
* @return array[]
|
||||
*/
|
||||
protected function buildPrefix(array $strings)
|
||||
{
|
||||
$prefix = [];
|
||||
foreach ($strings as $string)
|
||||
{
|
||||
// Remove the last element (suffix) of each string before adding it
|
||||
array_pop($string);
|
||||
$prefix[] = $string;
|
||||
}
|
||||
|
||||
return $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a list of strings that matches any given strings or nothing
|
||||
*
|
||||
* Will unpack groups of single characters
|
||||
*
|
||||
* @param array[] $strings
|
||||
* @return array[]
|
||||
*/
|
||||
protected function buildSuffix(array $strings)
|
||||
{
|
||||
$suffix = [[]];
|
||||
foreach ($strings as $string)
|
||||
{
|
||||
if ($this->isCharacterClassString($string))
|
||||
{
|
||||
foreach ($string[0] as $element)
|
||||
{
|
||||
$suffix[] = $element;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$suffix[] = $string;
|
||||
}
|
||||
}
|
||||
|
||||
return $suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of potential prefix strings grouped by identical suffix
|
||||
*
|
||||
* @param array[] $strings
|
||||
* @return array
|
||||
*/
|
||||
protected function getPrefixGroups(array $strings)
|
||||
{
|
||||
$groups = [];
|
||||
foreach ($strings as $k => $string)
|
||||
{
|
||||
if ($this->hasOptionalSuffix($string))
|
||||
{
|
||||
$groups[serialize(end($string))][$k] = $string;
|
||||
}
|
||||
}
|
||||
|
||||
return $groups;
|
||||
}
|
||||
}
|
||||
81
vendor/s9e/regexp-builder/src/Passes/CoalesceSingleCharacterPrefix.php
vendored
Normal file
81
vendor/s9e/regexp-builder/src/Passes/CoalesceSingleCharacterPrefix.php
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder\Passes;
|
||||
|
||||
/**
|
||||
* Replaces (?:ab|bb|c) with (?:[ab]b|c)
|
||||
*/
|
||||
class CoalesceSingleCharacterPrefix extends AbstractPass
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function runPass(array $strings)
|
||||
{
|
||||
$newStrings = [];
|
||||
foreach ($this->getEligibleKeys($strings) as $keys)
|
||||
{
|
||||
// Create a new string to hold the merged strings and replace the first element with
|
||||
// an empty character class
|
||||
$newString = $strings[$keys[0]];
|
||||
$newString[0] = [];
|
||||
|
||||
// Fill the character class with the prefix of each string in this group before removing
|
||||
// the original string
|
||||
foreach ($keys as $key)
|
||||
{
|
||||
$newString[0][] = [$strings[$key][0]];
|
||||
unset($strings[$key]);
|
||||
}
|
||||
$newStrings[] = $newString;
|
||||
}
|
||||
|
||||
return array_merge($newStrings, $strings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the list of eligible keys and keep those that have at least two matches
|
||||
*
|
||||
* @param array[] $eligibleKeys List of lists of keys
|
||||
* @return array[]
|
||||
*/
|
||||
protected function filterEligibleKeys(array $eligibleKeys)
|
||||
{
|
||||
$filteredKeys = [];
|
||||
foreach ($eligibleKeys as $k => $keys)
|
||||
{
|
||||
if (count($keys) > 1)
|
||||
{
|
||||
$filteredKeys[] = $keys;
|
||||
}
|
||||
}
|
||||
|
||||
return $filteredKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of keys of strings eligible to be merged together, grouped by suffix
|
||||
*
|
||||
* @param array[] $strings
|
||||
* @return array[]
|
||||
*/
|
||||
protected function getEligibleKeys(array $strings)
|
||||
{
|
||||
$eligibleKeys = [];
|
||||
foreach ($strings as $k => $string)
|
||||
{
|
||||
if (!is_array($string[0]) && isset($string[1]))
|
||||
{
|
||||
$suffix = serialize(array_slice($string, 1));
|
||||
$eligibleKeys[$suffix][] = $k;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->filterEligibleKeys($eligibleKeys);
|
||||
}
|
||||
}
|
||||
42
vendor/s9e/regexp-builder/src/Passes/GroupSingleCharacters.php
vendored
Normal file
42
vendor/s9e/regexp-builder/src/Passes/GroupSingleCharacters.php
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder\Passes;
|
||||
|
||||
/**
|
||||
* Enables other passes to replace (?:[xy]|a[xy]) with a?[xy]
|
||||
*/
|
||||
class GroupSingleCharacters extends AbstractPass
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function runPass(array $strings)
|
||||
{
|
||||
$singles = $this->getSingleCharStrings($strings);
|
||||
$cnt = count($singles);
|
||||
if ($cnt > 1 && $cnt < count($strings))
|
||||
{
|
||||
// Remove the singles from the input, then prepend them as a new string
|
||||
$strings = array_diff_key($strings, $singles);
|
||||
array_unshift($strings, [array_values($singles)]);
|
||||
}
|
||||
|
||||
return $strings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of every single-char string in given list of strings
|
||||
*
|
||||
* @param array[] $strings
|
||||
* @return array[]
|
||||
*/
|
||||
protected function getSingleCharStrings(array $strings)
|
||||
{
|
||||
return array_filter($strings, [$this, 'isSingleCharString']);
|
||||
}
|
||||
}
|
||||
104
vendor/s9e/regexp-builder/src/Passes/MergePrefix.php
vendored
Normal file
104
vendor/s9e/regexp-builder/src/Passes/MergePrefix.php
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder\Passes;
|
||||
|
||||
/**
|
||||
* Replaces (?:axx|ayy) with a(?:xx|yy)
|
||||
*/
|
||||
class MergePrefix extends AbstractPass
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function runPass(array $strings)
|
||||
{
|
||||
$newStrings = [];
|
||||
foreach ($this->getStringsByPrefix($strings) as $prefix => $strings)
|
||||
{
|
||||
$newStrings[] = (isset($strings[1])) ? $this->mergeStrings($strings) : $strings[0];
|
||||
}
|
||||
|
||||
return $newStrings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of leading elements common to all given strings
|
||||
*
|
||||
* @param array[] $strings
|
||||
* @return integer
|
||||
*/
|
||||
protected function getPrefixLength(array $strings)
|
||||
{
|
||||
$len = 1;
|
||||
$cnt = count($strings[0]);
|
||||
while ($len < $cnt && $this->stringsMatch($strings, $len))
|
||||
{
|
||||
++$len;
|
||||
}
|
||||
|
||||
return $len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return given strings grouped by their first element
|
||||
*
|
||||
* NOTE: assumes that this pass is run before the first element of any string could be replaced
|
||||
*
|
||||
* @param array[] $strings
|
||||
* @return array[]
|
||||
*/
|
||||
protected function getStringsByPrefix(array $strings)
|
||||
{
|
||||
$byPrefix = [];
|
||||
foreach ($strings as $string)
|
||||
{
|
||||
$byPrefix[$string[0]][] = $string;
|
||||
}
|
||||
|
||||
return $byPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge given strings into a new single string
|
||||
*
|
||||
* @param array[] $strings
|
||||
* @return array
|
||||
*/
|
||||
protected function mergeStrings(array $strings)
|
||||
{
|
||||
$len = $this->getPrefixLength($strings);
|
||||
$newString = array_slice($strings[0], 0, $len);
|
||||
foreach ($strings as $string)
|
||||
{
|
||||
$newString[$len][] = array_slice($string, $len);
|
||||
}
|
||||
|
||||
return $newString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether all given strings' elements match at given position
|
||||
*
|
||||
* @param array[] $strings
|
||||
* @param integer $pos
|
||||
* @return bool
|
||||
*/
|
||||
protected function stringsMatch(array $strings, $pos)
|
||||
{
|
||||
$value = $strings[0][$pos];
|
||||
foreach ($strings as $string)
|
||||
{
|
||||
if (!isset($string[$pos]) || $string[$pos] !== $value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
83
vendor/s9e/regexp-builder/src/Passes/MergeSuffix.php
vendored
Normal file
83
vendor/s9e/regexp-builder/src/Passes/MergeSuffix.php
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder\Passes;
|
||||
|
||||
/**
|
||||
* Replaces (?:aax|bbx) with (?:aa|bb)x
|
||||
*/
|
||||
class MergeSuffix extends AbstractPass
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function canRun(array $strings)
|
||||
{
|
||||
return (count($strings) > 1 && $this->hasMatchingSuffix($strings));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function runPass(array $strings)
|
||||
{
|
||||
$newString = [];
|
||||
while ($this->hasMatchingSuffix($strings))
|
||||
{
|
||||
array_unshift($newString, end($strings[0]));
|
||||
$strings = $this->pop($strings);
|
||||
}
|
||||
array_unshift($newString, $strings);
|
||||
|
||||
return [$newString];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether all given strings have the same last element
|
||||
*
|
||||
* @param array[] $strings
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasMatchingSuffix(array $strings)
|
||||
{
|
||||
$suffix = end($strings[1]);
|
||||
foreach ($strings as $string)
|
||||
{
|
||||
if (end($string) !== $suffix)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return ($suffix !== false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the last element of every string
|
||||
*
|
||||
* @param array[] $strings Original strings
|
||||
* @return array[] Processed strings
|
||||
*/
|
||||
protected function pop(array $strings)
|
||||
{
|
||||
$cnt = count($strings);
|
||||
$i = $cnt;
|
||||
while (--$i >= 0)
|
||||
{
|
||||
array_pop($strings[$i]);
|
||||
}
|
||||
|
||||
// Remove empty elements then prepend one back at the start of the array if applicable
|
||||
$strings = array_filter($strings);
|
||||
if (count($strings) < $cnt)
|
||||
{
|
||||
array_unshift($strings, []);
|
||||
}
|
||||
|
||||
return $strings;
|
||||
}
|
||||
}
|
||||
19
vendor/s9e/regexp-builder/src/Passes/PassInterface.php
vendored
Normal file
19
vendor/s9e/regexp-builder/src/Passes/PassInterface.php
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder\Passes;
|
||||
|
||||
interface PassInterface
|
||||
{
|
||||
/**
|
||||
* Run this pass
|
||||
*
|
||||
* @param array[] $strings Original strings
|
||||
* @return array[] Modified strings
|
||||
*/
|
||||
public function run(array $strings);
|
||||
}
|
||||
47
vendor/s9e/regexp-builder/src/Passes/PromoteSingleStrings.php
vendored
Normal file
47
vendor/s9e/regexp-builder/src/Passes/PromoteSingleStrings.php
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder\Passes;
|
||||
|
||||
/**
|
||||
* Replaces alternations that only contain one string to allow other passes to replace
|
||||
* (?:a0?x|bx) with (?:a0?|b)x
|
||||
*/
|
||||
class PromoteSingleStrings extends AbstractPass
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function runPass(array $strings)
|
||||
{
|
||||
return array_map([$this, 'promoteSingleStrings'], $strings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Promote single strings found inside given string
|
||||
*
|
||||
* @param array $string Original string
|
||||
* @return array Modified string
|
||||
*/
|
||||
protected function promoteSingleStrings(array $string)
|
||||
{
|
||||
$newString = [];
|
||||
foreach ($string as $element)
|
||||
{
|
||||
if (is_array($element) && count($element) === 1)
|
||||
{
|
||||
$newString = array_merge($newString, $element[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$newString[] = $element;
|
||||
}
|
||||
}
|
||||
|
||||
return $newString;
|
||||
}
|
||||
}
|
||||
58
vendor/s9e/regexp-builder/src/Passes/Recurse.php
vendored
Normal file
58
vendor/s9e/regexp-builder/src/Passes/Recurse.php
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder\Passes;
|
||||
|
||||
use s9e\RegexpBuilder\Runner;
|
||||
|
||||
/**
|
||||
* Enables passes to be run recursively into alternations to replace a(?:x0|x1|y0|y1) with a[xy][01]
|
||||
*/
|
||||
class Recurse extends AbstractPass
|
||||
{
|
||||
/**
|
||||
* @var Runner
|
||||
*/
|
||||
protected $runner;
|
||||
|
||||
/**
|
||||
* @param Runner $runner
|
||||
*/
|
||||
public function __construct(Runner $runner)
|
||||
{
|
||||
$this->runner = $runner;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function runPass(array $strings)
|
||||
{
|
||||
return array_map([$this, 'recurseString'], $strings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recurse into given string and run all passes on each element
|
||||
*
|
||||
* @param array $string
|
||||
* @return array
|
||||
*/
|
||||
protected function recurseString(array $string)
|
||||
{
|
||||
$isOptional = $this->isOptional;
|
||||
foreach ($string as $k => $element)
|
||||
{
|
||||
if (is_array($element))
|
||||
{
|
||||
$string[$k] = $this->runner->run($element);
|
||||
}
|
||||
}
|
||||
$this->isOptional = $isOptional;
|
||||
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
45
vendor/s9e/regexp-builder/src/Runner.php
vendored
Normal file
45
vendor/s9e/regexp-builder/src/Runner.php
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder;
|
||||
|
||||
use s9e\RegexpBuilder\Passes\PassInterface;
|
||||
|
||||
class Runner
|
||||
{
|
||||
/**
|
||||
* @var PassInterface[]
|
||||
*/
|
||||
protected $passes = [];
|
||||
|
||||
/**
|
||||
* Add a pass to the list
|
||||
*
|
||||
* @param PassInterface $pass
|
||||
* @return void
|
||||
*/
|
||||
public function addPass(PassInterface $pass)
|
||||
{
|
||||
$this->passes[] = $pass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all passes on the list of strings
|
||||
*
|
||||
* @param array[] $strings
|
||||
* @return array[]
|
||||
*/
|
||||
public function run(array $strings)
|
||||
{
|
||||
foreach ($this->passes as $pass)
|
||||
{
|
||||
$strings = $pass->run($strings);
|
||||
}
|
||||
|
||||
return $strings;
|
||||
}
|
||||
}
|
||||
279
vendor/s9e/regexp-builder/src/Serializer.php
vendored
Normal file
279
vendor/s9e/regexp-builder/src/Serializer.php
vendored
Normal file
@@ -0,0 +1,279 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package s9e\RegexpBuilder
|
||||
* @copyright Copyright (c) 2016-2018 The s9e Authors
|
||||
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
|
||||
*/
|
||||
namespace s9e\RegexpBuilder;
|
||||
|
||||
use s9e\RegexpBuilder\MetaCharacters;
|
||||
use s9e\RegexpBuilder\Output\OutputInterface;
|
||||
|
||||
class Serializer
|
||||
{
|
||||
/**
|
||||
* @var Escaper
|
||||
*/
|
||||
protected $escaper;
|
||||
|
||||
/**
|
||||
* @var MetaCharacters
|
||||
*/
|
||||
protected $meta;
|
||||
|
||||
/**
|
||||
* @var OutputInterface
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* @param OutputInterface $output
|
||||
* @parm MetaCharacters $meta
|
||||
* @param Escaper $escaper
|
||||
*/
|
||||
public function __construct(OutputInterface $output, MetaCharacters $meta, Escaper $escaper)
|
||||
{
|
||||
$this->escaper = $escaper;
|
||||
$this->meta = $meta;
|
||||
$this->output = $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize given strings into a regular expression
|
||||
*
|
||||
* @param array[] $strings
|
||||
* @return string
|
||||
*/
|
||||
public function serializeStrings(array $strings)
|
||||
{
|
||||
$info = $this->analyzeStrings($strings);
|
||||
$alternations = array_map([$this, 'serializeString'], $info['strings']);
|
||||
if (!empty($info['chars']))
|
||||
{
|
||||
// Prepend the character class to the list of alternations
|
||||
array_unshift($alternations, $this->serializeCharacterClass($info['chars']));
|
||||
}
|
||||
|
||||
$expr = implode('|', $alternations);
|
||||
if ($this->needsParentheses($info))
|
||||
{
|
||||
$expr = '(?:' . $expr . ')';
|
||||
}
|
||||
|
||||
return $expr . $info['quantifier'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze given strings to determine how to serialize them
|
||||
*
|
||||
* The returned array may contains any of the following elements:
|
||||
*
|
||||
* - (string) quantifier Either '' or '?'
|
||||
* - (array) chars List of values from single-char strings
|
||||
* - (array) strings List of multi-char strings
|
||||
*
|
||||
* @param array[] $strings
|
||||
* @return array
|
||||
*/
|
||||
protected function analyzeStrings(array $strings)
|
||||
{
|
||||
$info = ['alternationsCount' => 0, 'quantifier' => ''];
|
||||
if ($strings[0] === [])
|
||||
{
|
||||
$info['quantifier'] = '?';
|
||||
unset($strings[0]);
|
||||
}
|
||||
|
||||
$chars = $this->getChars($strings);
|
||||
if (count($chars) > 1)
|
||||
{
|
||||
++$info['alternationsCount'];
|
||||
$info['chars'] = array_values($chars);
|
||||
$strings = array_diff_key($strings, $chars);
|
||||
}
|
||||
|
||||
$info['strings'] = array_values($strings);
|
||||
$info['alternationsCount'] += count($strings);
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the portion of strings that are composed of a single character
|
||||
*
|
||||
* @param array[]
|
||||
* @return array String key => value
|
||||
*/
|
||||
protected function getChars(array $strings)
|
||||
{
|
||||
$chars = [];
|
||||
foreach ($strings as $k => $string)
|
||||
{
|
||||
if ($this->isChar($string))
|
||||
{
|
||||
$chars[$k] = $string[0];
|
||||
}
|
||||
}
|
||||
|
||||
return $chars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of ranges that cover all given values
|
||||
*
|
||||
* @param integer[] $values Ordered list of values
|
||||
* @return array[] List of ranges in the form [start, end]
|
||||
*/
|
||||
protected function getRanges(array $values)
|
||||
{
|
||||
$i = 0;
|
||||
$cnt = count($values);
|
||||
$start = $values[0];
|
||||
$end = $start;
|
||||
$ranges = [];
|
||||
while (++$i < $cnt)
|
||||
{
|
||||
if ($values[$i] === $end + 1)
|
||||
{
|
||||
++$end;
|
||||
}
|
||||
else
|
||||
{
|
||||
$ranges[] = [$start, $end];
|
||||
$start = $end = $values[$i];
|
||||
}
|
||||
}
|
||||
$ranges[] = [$start, $end];
|
||||
|
||||
return $ranges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether given string represents a single character
|
||||
*
|
||||
* @param array $string
|
||||
* @return bool
|
||||
*/
|
||||
protected function isChar(array $string)
|
||||
{
|
||||
return count($string) === 1 && is_int($string[0]) && MetaCharacters::isChar($string[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether an expression is quantifiable based on the strings info
|
||||
*
|
||||
* @param array $info
|
||||
* @return bool
|
||||
*/
|
||||
protected function isQuantifiable(array $info)
|
||||
{
|
||||
$strings = $info['strings'];
|
||||
|
||||
return empty($strings) || $this->isSingleQuantifiableString($strings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a list of strings contains only one single quantifiable string
|
||||
*
|
||||
* @param array[] $strings
|
||||
* @return bool
|
||||
*/
|
||||
protected function isSingleQuantifiableString(array $strings)
|
||||
{
|
||||
return count($strings) === 1 && count($strings[0]) === 1 && MetaCharacters::isQuantifiable($strings[0][0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether an expression needs parentheses based on the strings info
|
||||
*
|
||||
* @param array $info
|
||||
* @return bool
|
||||
*/
|
||||
protected function needsParentheses(array $info)
|
||||
{
|
||||
return ($info['alternationsCount'] > 1 || ($info['quantifier'] && !$this->isQuantifiable($info)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a given list of values into a character class
|
||||
*
|
||||
* @param integer[] $values
|
||||
* @return string
|
||||
*/
|
||||
protected function serializeCharacterClass(array $values)
|
||||
{
|
||||
$expr = '[';
|
||||
foreach ($this->getRanges($values) as list($start, $end))
|
||||
{
|
||||
$expr .= $this->serializeCharacterClassUnit($start);
|
||||
if ($end > $start)
|
||||
{
|
||||
if ($end > $start + 1)
|
||||
{
|
||||
$expr .= '-';
|
||||
}
|
||||
$expr .= $this->serializeCharacterClassUnit($end);
|
||||
}
|
||||
}
|
||||
$expr .= ']';
|
||||
|
||||
return $expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a given value to be used in a character class
|
||||
*
|
||||
* @param integer $value
|
||||
* @return string
|
||||
*/
|
||||
protected function serializeCharacterClassUnit($value)
|
||||
{
|
||||
return $this->serializeValue($value, 'escapeCharacterClass');
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize an element from a string
|
||||
*
|
||||
* @param array|integer $element
|
||||
* @return string
|
||||
*/
|
||||
protected function serializeElement($element)
|
||||
{
|
||||
return (is_array($element)) ? $this->serializeStrings($element) : $this->serializeLiteral($element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a given value to be used as a literal
|
||||
*
|
||||
* @param integer $value
|
||||
* @return string
|
||||
*/
|
||||
protected function serializeLiteral($value)
|
||||
{
|
||||
return $this->serializeValue($value, 'escapeLiteral');
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a given string into a regular expression
|
||||
*
|
||||
* @param array $string
|
||||
* @return string
|
||||
*/
|
||||
protected function serializeString(array $string)
|
||||
{
|
||||
return implode('', array_map([$this, 'serializeElement'], $string));
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a given value
|
||||
*
|
||||
* @param integer $value
|
||||
* @param string $escapeMethod
|
||||
* @return string
|
||||
*/
|
||||
protected function serializeValue($value, $escapeMethod)
|
||||
{
|
||||
return ($value < 0) ? $this->meta->getExpression($value) : $this->escaper->$escapeMethod($this->output->output($value));
|
||||
}
|
||||
}
|
||||
15
vendor/s9e/text-formatter/composer.json
vendored
15
vendor/s9e/text-formatter/composer.json
vendored
@@ -6,15 +6,15 @@
|
||||
"keywords": ["bbcode","bbcodes","blog","censor","embed","emoji","emoticons","engine","forum","html","markdown","markup","media","parser","shortcodes"],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": ">=5.4.7",
|
||||
"php": ">=7.1",
|
||||
"ext-dom": "*",
|
||||
"ext-filter": "*",
|
||||
"lib-pcre": ">=7.2"
|
||||
"lib-pcre": ">=8.13",
|
||||
"s9e/regexp-builder": "^1.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"matthiasmullie/minify": "*",
|
||||
"php-coveralls/php-coveralls": "*",
|
||||
"s9e/regexp-builder": "1.*"
|
||||
"matthiasmullie/minify": "*",
|
||||
"phpunit/phpunit": "^7 || 8.2.*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-curl": "Improves the performance of the MediaEmbed plugin and some JavaScript minifiers",
|
||||
@@ -34,5 +34,8 @@
|
||||
"psr-4": {
|
||||
"s9e\\TextFormatter\\Tests\\": "tests"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"version": "2.3.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
113
vendor/s9e/text-formatter/src/Bundle.php
vendored
113
vendor/s9e/text-formatter/src/Bundle.php
vendored
@@ -1,58 +1,153 @@
|
||||
<?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;
|
||||
|
||||
abstract class Bundle
|
||||
{
|
||||
/**
|
||||
* Return a cached instance of the parser
|
||||
*
|
||||
* @return Parser
|
||||
*/
|
||||
public static function getCachedParser()
|
||||
{
|
||||
if (!isset(static::$parser))
|
||||
{
|
||||
static::$parser = static::getParser();
|
||||
}
|
||||
|
||||
return static::$parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a cached instance of the renderer
|
||||
*
|
||||
* @return Renderer
|
||||
*/
|
||||
public static function getCachedRenderer()
|
||||
{
|
||||
if (!isset(static::$renderer))
|
||||
{
|
||||
static::$renderer = static::getRenderer();
|
||||
}
|
||||
|
||||
return static::$renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new instance of s9e\TextFormatter\Parser
|
||||
*
|
||||
* @return Parser
|
||||
*/
|
||||
abstract public static function getParser();
|
||||
|
||||
/**
|
||||
* Return a new instance of s9e\TextFormatter\Renderer
|
||||
*
|
||||
* @return Renderer
|
||||
*/
|
||||
abstract public static function getRenderer();
|
||||
|
||||
/**
|
||||
* Return the source of the JavaScript parser if available
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getJS()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse given text using a singleton instance of the bundled Parser
|
||||
*
|
||||
* @param string $text Original text
|
||||
* @return string Intermediate representation
|
||||
*/
|
||||
public static function parse($text)
|
||||
{
|
||||
if (isset(static::$beforeParse))
|
||||
$text = \call_user_func(static::$beforeParse, $text);
|
||||
{
|
||||
$text = call_user_func(static::$beforeParse, $text);
|
||||
}
|
||||
|
||||
$xml = static::getCachedParser()->parse($text);
|
||||
|
||||
if (isset(static::$afterParse))
|
||||
$xml = \call_user_func(static::$afterParse, $xml);
|
||||
{
|
||||
$xml = call_user_func(static::$afterParse, $xml);
|
||||
}
|
||||
|
||||
return $xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an intermediate representation using a singleton instance of the bundled Renderer
|
||||
*
|
||||
* @param string $xml Intermediate representation
|
||||
* @param array $params Stylesheet parameters
|
||||
* @return string Rendered result
|
||||
*/
|
||||
public static function render($xml, array $params = [])
|
||||
{
|
||||
$renderer = static::getCachedRenderer();
|
||||
|
||||
if (!empty($params))
|
||||
{
|
||||
$renderer->setParameters($params);
|
||||
}
|
||||
|
||||
if (isset(static::$beforeRender))
|
||||
$xml = \call_user_func(static::$beforeRender, $xml);
|
||||
{
|
||||
$xml = call_user_func(static::$beforeRender, $xml);
|
||||
}
|
||||
|
||||
$output = $renderer->render($xml);
|
||||
|
||||
if (isset(static::$afterRender))
|
||||
$output = \call_user_func(static::$afterRender, $output);
|
||||
{
|
||||
$output = call_user_func(static::$afterRender, $output);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the cached parser and renderer
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function reset()
|
||||
{
|
||||
static::$parser = \null;
|
||||
static::$renderer = \null;
|
||||
static::$parser = null;
|
||||
static::$renderer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform an intermediate representation back to its original form
|
||||
*
|
||||
* @param string $xml Intermediate representation
|
||||
* @return string Original text
|
||||
*/
|
||||
public static function unparse($xml)
|
||||
{
|
||||
if (isset(static::$beforeUnparse))
|
||||
$xml = \call_user_func(static::$beforeUnparse, $xml);
|
||||
{
|
||||
$xml = call_user_func(static::$beforeUnparse, $xml);
|
||||
}
|
||||
|
||||
$text = Unparser::unparse($xml);
|
||||
|
||||
if (isset(static::$afterUnparse))
|
||||
$text = \call_user_func(static::$afterUnparse, $text);
|
||||
{
|
||||
$text = call_user_func(static::$afterUnparse, $text);
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
105
vendor/s9e/text-formatter/src/Bundles/Fatdown.php
vendored
105
vendor/s9e/text-formatter/src/Bundles/Fatdown.php
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
349
vendor/s9e/text-formatter/src/Bundles/Forum.php
vendored
349
vendor/s9e/text-formatter/src/Bundles/Forum.php
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
101
vendor/s9e/text-formatter/src/Bundles/MediaPack.php
vendored
101
vendor/s9e/text-formatter/src/Bundles/MediaPack.php
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
8652
vendor/s9e/text-formatter/src/Configurator.php
vendored
8652
vendor/s9e/text-formatter/src/Configurator.php
vendored
File diff suppressed because it is too large
Load Diff
@@ -1,22 +1,46 @@
|
||||
<?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
|
||||
{
|
||||
/**
|
||||
* Configure a Configurator instance with this bundle's settings
|
||||
*
|
||||
* @param Configurator $configurator
|
||||
* @return void
|
||||
*/
|
||||
abstract public function configure(Configurator $configurator);
|
||||
|
||||
/**
|
||||
* Create and return a configured instance of Configurator
|
||||
*
|
||||
* @return Configurator
|
||||
*/
|
||||
public static function getConfigurator()
|
||||
{
|
||||
$configurator = new Configurator;
|
||||
|
||||
$bundle = new static;
|
||||
$bundle->configure($configurator);
|
||||
|
||||
return $configurator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return extra options to be passed to the bundle generator
|
||||
*
|
||||
* Used by scripts/generateBundles.php
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getOptions()
|
||||
{
|
||||
return [];
|
||||
|
||||
250
vendor/s9e/text-formatter/src/Configurator/BundleGenerator.php
vendored
Normal file
250
vendor/s9e/text-formatter/src/Configurator/BundleGenerator.php
vendored
Normal file
@@ -0,0 +1,250 @@
|
||||
<?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;
|
||||
use s9e\TextFormatter\Configurator\RendererGenerators\PHP;
|
||||
|
||||
class BundleGenerator
|
||||
{
|
||||
/**
|
||||
* @var Configurator Configurator this instance belongs to
|
||||
*/
|
||||
protected $configurator;
|
||||
|
||||
/**
|
||||
* @var callback Callback used to serialize the objects
|
||||
*/
|
||||
public $serializer = 'serialize';
|
||||
|
||||
/**
|
||||
* @var string Callback used to unserialize the serialized objects (must be a string)
|
||||
*/
|
||||
public $unserializer = 'unserialize';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Configurator $configurator Configurator
|
||||
*/
|
||||
public function __construct(Configurator $configurator)
|
||||
{
|
||||
$this->configurator = $configurator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return the source of a bundle based on given Configurator instance
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - autoInclude: automatically load the source of the PHP renderer (default: true)
|
||||
*
|
||||
* @param string $className Name of the bundle class
|
||||
* @param array $options Associative array of optional settings
|
||||
* @return string PHP source for the bundle
|
||||
*/
|
||||
public function generate($className, array $options = [])
|
||||
{
|
||||
// Add default options
|
||||
$options += ['autoInclude' => true];
|
||||
|
||||
// Copy the PHP files header if applicable
|
||||
if ($this->configurator->rendering->engine instanceof PHP)
|
||||
{
|
||||
$this->configurator->rendering->engine->phpHeader = $this->configurator->phpHeader;
|
||||
}
|
||||
|
||||
// Get the parser and renderer
|
||||
$objects = $this->configurator->finalize();
|
||||
$parser = $objects['parser'];
|
||||
$renderer = $objects['renderer'];
|
||||
|
||||
// Split the bundle's class name and its namespace
|
||||
$namespace = '';
|
||||
if (preg_match('#(.*)\\\\([^\\\\]+)$#', $className, $m))
|
||||
{
|
||||
$namespace = $m[1];
|
||||
$className = $m[2];
|
||||
}
|
||||
|
||||
// Start with the standard header
|
||||
$php = [];
|
||||
$php[] = $this->configurator->phpHeader;
|
||||
|
||||
if ($namespace)
|
||||
{
|
||||
$php[] = 'namespace ' . $namespace . ';';
|
||||
$php[] = '';
|
||||
}
|
||||
|
||||
// Generate and append the bundle class
|
||||
$php[] = 'abstract class ' . $className . ' extends \\s9e\\TextFormatter\\Bundle';
|
||||
$php[] = '{';
|
||||
$php[] = ' /**';
|
||||
$php[] = ' * @var s9e\\TextFormatter\\Parser Singleton instance used by parse()';
|
||||
$php[] = ' */';
|
||||
$php[] = ' protected static $parser;';
|
||||
$php[] = '';
|
||||
$php[] = ' /**';
|
||||
$php[] = ' * @var s9e\\TextFormatter\\Renderer Singleton instance used by render()';
|
||||
$php[] = ' */';
|
||||
$php[] = ' protected static $renderer;';
|
||||
$php[] = '';
|
||||
|
||||
// Add the event callbacks if applicable
|
||||
$events = [
|
||||
'beforeParse'
|
||||
=> 'Callback executed before parse(), receives the original text as argument',
|
||||
'afterParse'
|
||||
=> 'Callback executed after parse(), receives the parsed text as argument',
|
||||
'beforeRender'
|
||||
=> 'Callback executed before render(), receives the parsed text as argument',
|
||||
'afterRender'
|
||||
=> 'Callback executed after render(), receives the output as argument',
|
||||
'beforeUnparse'
|
||||
=> 'Callback executed before unparse(), receives the parsed text as argument',
|
||||
'afterUnparse'
|
||||
=> 'Callback executed after unparse(), receives the original text as argument'
|
||||
];
|
||||
foreach ($events as $eventName => $eventDesc)
|
||||
{
|
||||
if (isset($options[$eventName]))
|
||||
{
|
||||
$php[] = ' /**';
|
||||
$php[] = ' * @var ' . $eventDesc;
|
||||
$php[] = ' */';
|
||||
$php[] = ' public static $' . $eventName . ' = ' . var_export($options[$eventName], true) . ';';
|
||||
$php[] = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($objects['js']))
|
||||
{
|
||||
$php[] = ' /**';
|
||||
$php[] = ' * {@inheritdoc}';
|
||||
$php[] = ' */';
|
||||
$php[] = ' public static function getJS()';
|
||||
$php[] = ' {';
|
||||
$php[] = ' return ' . var_export($objects['js'], true) . ';';
|
||||
$php[] = ' }';
|
||||
$php[] = '';
|
||||
}
|
||||
|
||||
$php[] = ' /**';
|
||||
$php[] = ' * {@inheritdoc}';
|
||||
$php[] = ' */';
|
||||
$php[] = ' public static function getParser()';
|
||||
$php[] = ' {';
|
||||
|
||||
if (isset($options['parserSetup']))
|
||||
{
|
||||
$php[] = ' $parser = ' . $this->exportObject($parser) . ';';
|
||||
$php[] = ' ' . $this->exportCallback($namespace, $options['parserSetup'], '$parser') . ';';
|
||||
$php[] = '';
|
||||
$php[] = ' return $parser;';
|
||||
}
|
||||
else
|
||||
{
|
||||
$php[] = ' return ' . $this->exportObject($parser) . ';';
|
||||
}
|
||||
|
||||
$php[] = ' }';
|
||||
$php[] = '';
|
||||
$php[] = ' /**';
|
||||
$php[] = ' * {@inheritdoc}';
|
||||
$php[] = ' */';
|
||||
$php[] = ' public static function getRenderer()';
|
||||
$php[] = ' {';
|
||||
|
||||
// If this is a PHP renderer and we know where it's saved, automatically load it as needed
|
||||
if (!empty($options['autoInclude'])
|
||||
&& $this->configurator->rendering->engine instanceof PHP
|
||||
&& isset($this->configurator->rendering->engine->lastFilepath))
|
||||
{
|
||||
$className = get_class($renderer);
|
||||
$filepath = realpath($this->configurator->rendering->engine->lastFilepath);
|
||||
|
||||
$php[] = ' if (!class_exists(' . var_export($className, true) . ', false)';
|
||||
$php[] = ' && file_exists(' . var_export($filepath, true) . '))';
|
||||
$php[] = ' {';
|
||||
$php[] = ' include ' . var_export($filepath, true) . ';';
|
||||
$php[] = ' }';
|
||||
$php[] = '';
|
||||
}
|
||||
|
||||
if (isset($options['rendererSetup']))
|
||||
{
|
||||
$php[] = ' $renderer = ' . $this->exportObject($renderer) . ';';
|
||||
$php[] = ' ' . $this->exportCallback($namespace, $options['rendererSetup'], '$renderer') . ';';
|
||||
$php[] = '';
|
||||
$php[] = ' return $renderer;';
|
||||
}
|
||||
else
|
||||
{
|
||||
$php[] = ' return ' . $this->exportObject($renderer) . ';';
|
||||
}
|
||||
|
||||
$php[] = ' }';
|
||||
$php[] = '}';
|
||||
|
||||
return implode("\n", $php);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a given callback as PHP code
|
||||
*
|
||||
* @param string $namespace Namespace in which the callback is execute
|
||||
* @param callable $callback Original callback
|
||||
* @param string $argument Callback's argument (as PHP code)
|
||||
* @return string PHP code
|
||||
*/
|
||||
protected function exportCallback($namespace, callable $callback, $argument)
|
||||
{
|
||||
if (is_array($callback) && is_string($callback[0]))
|
||||
{
|
||||
// Replace ['foo', 'bar'] with 'foo::bar'
|
||||
$callback = $callback[0] . '::' . $callback[1];
|
||||
}
|
||||
|
||||
if (!is_string($callback))
|
||||
{
|
||||
return 'call_user_func(' . var_export($callback, true) . ', ' . $argument . ')';
|
||||
}
|
||||
|
||||
// Ensure that the callback starts with a \
|
||||
if ($callback[0] !== '\\')
|
||||
{
|
||||
$callback = '\\' . $callback;
|
||||
}
|
||||
|
||||
// Replace \foo\bar::baz() with bar::baz() if we're in namespace foo
|
||||
if (substr($callback, 0, 2 + strlen($namespace)) === '\\' . $namespace . '\\')
|
||||
{
|
||||
$callback = substr($callback, 2 + strlen($namespace));
|
||||
}
|
||||
|
||||
return $callback . '(' . $argument . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize and export a given object as PHP code
|
||||
*
|
||||
* @param object $obj Original object
|
||||
* @return string PHP code
|
||||
*/
|
||||
protected function exportObject($obj)
|
||||
{
|
||||
// Serialize the object
|
||||
$str = call_user_func($this->serializer, $obj);
|
||||
|
||||
// Export the object's source
|
||||
$str = var_export($str, true);
|
||||
|
||||
return $this->unserializer . '(' . $str . ')';
|
||||
}
|
||||
}
|
||||
@@ -1,19 +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\Bundles;
|
||||
|
||||
use s9e\TextFormatter\Configurator;
|
||||
use s9e\TextFormatter\Configurator\Bundle;
|
||||
|
||||
class Fatdown extends Bundle
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(Configurator $configurator)
|
||||
{
|
||||
$configurator->urlConfig->allowScheme('ftp');
|
||||
$configurator->Litedown->decodeHtmlEntities = \true;
|
||||
$configurator->urlConfig->allowScheme('mailto');
|
||||
|
||||
$configurator->Litedown->decodeHtmlEntities = true;
|
||||
$configurator->Autoemail;
|
||||
$configurator->Autolink;
|
||||
$configurator->Escaper;
|
||||
@@ -21,6 +28,7 @@ class Fatdown extends Bundle
|
||||
$configurator->HTMLComments;
|
||||
$configurator->HTMLEntities;
|
||||
$configurator->PipeTables;
|
||||
|
||||
$htmlAliases = [
|
||||
'a' => ['URL', 'href' => 'url'],
|
||||
'hr' => 'HR',
|
||||
@@ -30,15 +38,23 @@ class Fatdown extends Bundle
|
||||
'sup' => 'SUP'
|
||||
];
|
||||
foreach ($htmlAliases as $elName => $alias)
|
||||
if (\is_array($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',
|
||||
@@ -76,7 +92,7 @@ class Fatdown extends Bundle
|
||||
];
|
||||
foreach ($htmlElements as $k => $v)
|
||||
{
|
||||
if (\is_numeric($k))
|
||||
if (is_numeric($k))
|
||||
{
|
||||
$elName = $v;
|
||||
$attrNames = [];
|
||||
@@ -86,15 +102,20 @@ class Fatdown extends Bundle
|
||||
$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]);
|
||||
|
||||
$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',
|
||||
@@ -108,6 +129,8 @@ class Fatdown extends Bundle
|
||||
'youtube'
|
||||
];
|
||||
foreach ($sites as $site)
|
||||
{
|
||||
$configurator->MediaEmbed->add($site);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +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 s9e\TextFormatter\Configurator;
|
||||
use s9e\TextFormatter\Configurator\Bundle;
|
||||
|
||||
class Forum extends Bundle
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(Configurator $configurator)
|
||||
{
|
||||
$configurator->rootRules->enableAutoLineBreaks();
|
||||
|
||||
$configurator->BBCodes->addFromRepository('B');
|
||||
$configurator->BBCodes->addFromRepository('CENTER');
|
||||
$configurator->BBCodes->addFromRepository('CODE');
|
||||
@@ -42,12 +48,14 @@ class Forum extends Bundle
|
||||
$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',
|
||||
@@ -73,14 +81,19 @@ class Forum extends Bundle
|
||||
':o' => '1F62E',
|
||||
':lol:' => '1F602'
|
||||
];
|
||||
|
||||
foreach ($emoticons as $code => $hex)
|
||||
$configurator->Emoji->addAlias($code, \html_entity_decode('&#x' . $hex . ';'));
|
||||
{
|
||||
$configurator->Emoji->aliases[$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;
|
||||
}
|
||||
|
||||
@@ -1,24 +1,34 @@
|
||||
<?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
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(Configurator $configurator)
|
||||
{
|
||||
if (!isset($configurator->MediaEmbed))
|
||||
{
|
||||
// Only create BBCodes if the BBCodes plugin is already loaded
|
||||
$pluginOptions = ['createMediaBBCode' => isset($configurator->BBCodes)];
|
||||
|
||||
$configurator->plugins->load('MediaEmbed', $pluginOptions);
|
||||
}
|
||||
|
||||
foreach ($configurator->MediaEmbed->defaultSites as $siteId => $siteConfig)
|
||||
{
|
||||
$configurator->MediaEmbed->add($siteId);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
vendor/s9e/text-formatter/src/Configurator/Collections/AttributeCollection.php
vendored
Normal file
60
vendor/s9e/text-formatter/src/Configurator/Collections/AttributeCollection.php
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
<?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 RuntimeException;
|
||||
use s9e\TextFormatter\Configurator\Items\Attribute;
|
||||
use s9e\TextFormatter\Configurator\Validators\AttributeName;
|
||||
|
||||
class AttributeCollection extends NormalizedCollection
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $onDuplicateAction = 'replace';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getAlreadyExistsException($key)
|
||||
{
|
||||
return new RuntimeException("Attribute '" . $key . "' already exists");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNotExistException($key)
|
||||
{
|
||||
return new RuntimeException("Attribute '" . $key . "' does not exist");
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a key as an attribute name
|
||||
*
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
public function normalizeKey($key)
|
||||
{
|
||||
return AttributeName::normalize($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a value to an instance of Attribute
|
||||
*
|
||||
* @param array|null|Attribute $value
|
||||
* @return Attribute
|
||||
*/
|
||||
public function normalizeValue($value)
|
||||
{
|
||||
return ($value instanceof Attribute)
|
||||
? $value
|
||||
: new Attribute($value);
|
||||
}
|
||||
}
|
||||
35
vendor/s9e/text-formatter/src/Configurator/Collections/AttributeFilterChain.php
vendored
Normal file
35
vendor/s9e/text-formatter/src/Configurator/Collections/AttributeFilterChain.php
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
<?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;
|
||||
|
||||
class AttributeFilterChain extends FilterChain
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFilterClassName()
|
||||
{
|
||||
return 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilter';
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a value into an AttributeFilter instance
|
||||
*
|
||||
* @param mixed $value Either a valid callback or an instance of AttributeFilter
|
||||
* @return \s9e\TextFormatter\Configurator\Items\AttributeFilter Normalized filter
|
||||
*/
|
||||
public function normalizeValue($value)
|
||||
{
|
||||
if (is_string($value) && preg_match('(^#\\w+$)', $value))
|
||||
{
|
||||
$value = AttributeFilterCollection::getDefaultFilter(substr($value, 1));
|
||||
}
|
||||
|
||||
return parent::normalizeValue($value);
|
||||
}
|
||||
}
|
||||
108
vendor/s9e/text-formatter/src/Configurator/Collections/AttributeFilterCollection.php
vendored
Normal file
108
vendor/s9e/text-formatter/src/Configurator/Collections/AttributeFilterCollection.php
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
<?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 s9e\TextFormatter\Configurator\Items\AttributeFilter;
|
||||
|
||||
class AttributeFilterCollection extends NormalizedCollection
|
||||
{
|
||||
/**
|
||||
* Return a value from this collection
|
||||
*
|
||||
* @param string $key
|
||||
* @return \s9e\TextFormatter\Configurator\Items\ProgrammableCallback
|
||||
*/
|
||||
public function get($key)
|
||||
{
|
||||
$key = $this->normalizeKey($key);
|
||||
|
||||
if (!$this->exists($key))
|
||||
{
|
||||
if ($key[0] === '#')
|
||||
{
|
||||
$this->set($key, self::getDefaultFilter(substr($key, 1)));
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->set($key, new AttributeFilter($key));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the filter from the collection
|
||||
$filter = parent::get($key);
|
||||
|
||||
// Clone it to preserve the original instance
|
||||
$filter = clone $filter;
|
||||
|
||||
return $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of the default filter for given name
|
||||
*
|
||||
* @param string $filterName Filter name, e.g. "int" or "color"
|
||||
* @return AttributeFilter
|
||||
*/
|
||||
public static function getDefaultFilter($filterName)
|
||||
{
|
||||
$filterName = ucfirst(strtolower($filterName));
|
||||
$className = 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\' . $filterName . 'Filter';
|
||||
|
||||
if (!class_exists($className))
|
||||
{
|
||||
throw new InvalidArgumentException("Unknown attribute filter '" . $filterName . "'");
|
||||
}
|
||||
|
||||
return new $className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the name of an attribute filter
|
||||
*
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
public function normalizeKey($key)
|
||||
{
|
||||
// Built-in/custom filter, normalized to lowercase
|
||||
if (preg_match('/^#[a-z_0-9]+$/Di', $key))
|
||||
{
|
||||
return strtolower($key);
|
||||
}
|
||||
|
||||
// Valid callback
|
||||
if (is_string($key) && is_callable($key))
|
||||
{
|
||||
return $key;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException("Invalid filter name '" . $key . "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a value to an instance of AttributeFilter
|
||||
*
|
||||
* @param callable|AttributeFilter $value
|
||||
* @return AttributeFilter
|
||||
*/
|
||||
public function normalizeValue($value)
|
||||
{
|
||||
if ($value instanceof AttributeFilter)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (is_callable($value))
|
||||
{
|
||||
return new AttributeFilter($value);
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Argument 1 passed to ' . __METHOD__ . ' must be a valid callback or an instance of s9e\\TextFormatter\\Configurator\\Items\\AttributeFilter');
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,39 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Hosts a list of attribute names. The config array it returns contains the names, deduplicated and
|
||||
* sorted
|
||||
*/
|
||||
class AttributeList extends NormalizedList
|
||||
{
|
||||
/**
|
||||
* Normalize the name of an attribute
|
||||
*
|
||||
* @param string $attrName
|
||||
* @return string
|
||||
*/
|
||||
public function normalizeValue($attrName)
|
||||
{
|
||||
return AttributeName::normalize($attrName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function asConfig()
|
||||
{
|
||||
$list = \array_unique($this->items);
|
||||
\sort($list);
|
||||
$list = array_unique($this->items);
|
||||
sort($list);
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
110
vendor/s9e/text-formatter/src/Configurator/Collections/AttributePreprocessorCollection.php
vendored
Normal file
110
vendor/s9e/text-formatter/src/Configurator/Collections/AttributePreprocessorCollection.php
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
<?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 s9e\TextFormatter\Configurator\Helpers\RegexpParser;
|
||||
use s9e\TextFormatter\Configurator\Items\AttributePreprocessor;
|
||||
use s9e\TextFormatter\Configurator\Items\Regexp;
|
||||
use s9e\TextFormatter\Configurator\JavaScript\RegexpConvertor;
|
||||
use s9e\TextFormatter\Configurator\Validators\AttributeName;
|
||||
|
||||
class AttributePreprocessorCollection extends Collection
|
||||
{
|
||||
/**
|
||||
* Add an attribute preprocessor
|
||||
*
|
||||
* @param string $attrName Original name
|
||||
* @param string $regexp Preprocessor's regexp
|
||||
* @return AttributePreprocessor
|
||||
*/
|
||||
public function add($attrName, $regexp)
|
||||
{
|
||||
$attrName = AttributeName::normalize($attrName);
|
||||
|
||||
$k = serialize([$attrName, $regexp]);
|
||||
$this->items[$k] = new AttributePreprocessor($regexp);
|
||||
|
||||
return $this->items[$k];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Name of the attribute the attribute processor uses as source
|
||||
*/
|
||||
public function key()
|
||||
{
|
||||
list($attrName) = unserialize(key($this->items));
|
||||
|
||||
return $attrName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge a set of attribute preprocessors into this collection
|
||||
*
|
||||
* @param array|AttributePreprocessorCollection $attributePreprocessors Instance of AttributePreprocessorCollection or 2D array of [[attrName,regexp|AttributePreprocessor]]
|
||||
*/
|
||||
public function merge($attributePreprocessors)
|
||||
{
|
||||
$error = false;
|
||||
|
||||
if ($attributePreprocessors instanceof AttributePreprocessorCollection)
|
||||
{
|
||||
foreach ($attributePreprocessors as $attrName => $attributePreprocessor)
|
||||
{
|
||||
$this->add($attrName, $attributePreprocessor->getRegexp());
|
||||
}
|
||||
}
|
||||
elseif (is_array($attributePreprocessors))
|
||||
{
|
||||
// This should be a list where each element is a [attrName,regexp] pair, or
|
||||
// [attrName,AttributePreprocessor]
|
||||
foreach ($attributePreprocessors as $values)
|
||||
{
|
||||
if (!is_array($values))
|
||||
{
|
||||
$error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
list($attrName, $value) = $values;
|
||||
|
||||
if ($value instanceof AttributePreprocessor)
|
||||
{
|
||||
$value = $value->getRegexp();
|
||||
}
|
||||
|
||||
$this->add($attrName, $value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$error = true;
|
||||
}
|
||||
|
||||
if ($error)
|
||||
{
|
||||
throw new InvalidArgumentException('merge() expects an instance of AttributePreprocessorCollection or a 2D array where each element is a [attribute name, regexp] pair');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function asConfig()
|
||||
{
|
||||
$config = [];
|
||||
|
||||
foreach ($this->items as $k => $ap)
|
||||
{
|
||||
list($attrName) = unserialize($k);
|
||||
$config[] = [$attrName, $ap, $ap->getCaptureNames()];
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
}
|
||||
93
vendor/s9e/text-formatter/src/Configurator/Collections/Collection.php
vendored
Normal file
93
vendor/s9e/text-formatter/src/Configurator/Collections/Collection.php
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
<?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 Countable;
|
||||
use Iterator;
|
||||
use s9e\TextFormatter\Configurator\ConfigProvider;
|
||||
use s9e\TextFormatter\Configurator\Helpers\ConfigHelper;
|
||||
|
||||
class Collection implements ConfigProvider, Countable, Iterator
|
||||
{
|
||||
/**
|
||||
* @var array Items that this collection holds
|
||||
*/
|
||||
protected $items = [];
|
||||
|
||||
/**
|
||||
* Empty this collection
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->items = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function asConfig()
|
||||
{
|
||||
return ConfigHelper::toArray($this->items, true);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
// Countable stuff
|
||||
//==========================================================================
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return count($this->items);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
// Iterator stuff
|
||||
//==========================================================================
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
return current($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return integer|string
|
||||
*/
|
||||
public function key()
|
||||
{
|
||||
return key($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
return next($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
reset($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function valid()
|
||||
{
|
||||
return (key($this->items) !== null);
|
||||
}
|
||||
}
|
||||
95
vendor/s9e/text-formatter/src/Configurator/Collections/FilterChain.php
vendored
Normal file
95
vendor/s9e/text-formatter/src/Configurator/Collections/FilterChain.php
vendored
Normal 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\Collections;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use s9e\TextFormatter\Configurator\Helpers\FilterHelper;
|
||||
use s9e\TextFormatter\Configurator\Items\Filter;
|
||||
use s9e\TextFormatter\Configurator\Items\ProgrammableCallback;
|
||||
|
||||
abstract class FilterChain extends NormalizedList
|
||||
{
|
||||
/**
|
||||
* Get the name of the filter class
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function getFilterClassName();
|
||||
|
||||
/**
|
||||
* Test whether this filter chain contains given callback
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return bool
|
||||
*/
|
||||
public function containsCallback(callable $callback)
|
||||
{
|
||||
// Normalize the callback
|
||||
$pc = new ProgrammableCallback($callback);
|
||||
$callback = $pc->getCallback();
|
||||
foreach ($this->items as $filter)
|
||||
{
|
||||
if ($callback === $filter->getCallback())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a value into an TagFilter instance
|
||||
*
|
||||
* @param mixed $value Either a valid callback or an instance of TagFilter
|
||||
* @return Filter Normalized filter
|
||||
*/
|
||||
public function normalizeValue($value)
|
||||
{
|
||||
if (is_string($value) && strpos($value, '(') !== false)
|
||||
{
|
||||
return $this->createFilter($value);
|
||||
}
|
||||
|
||||
$className = $this->getFilterClassName();
|
||||
if ($value instanceof $className)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (!is_callable($value))
|
||||
{
|
||||
throw new InvalidArgumentException('Filter ' . var_export($value, true) . ' is neither callable nor an instance of ' . $className);
|
||||
}
|
||||
|
||||
return new $className($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a filter
|
||||
*
|
||||
* @param string $filterString
|
||||
* @return Filter
|
||||
*/
|
||||
protected function createFilter($filterString)
|
||||
{
|
||||
$config = FilterHelper::parse($filterString);
|
||||
$filter = $this->normalizeValue($config['filter']);
|
||||
if (isset($config['params']))
|
||||
{
|
||||
$filter->resetParameters();
|
||||
foreach ($config['params'] as [$type, $value])
|
||||
{
|
||||
$methodName = 'addParameterBy' . $type;
|
||||
$filter->$methodName($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $filter;
|
||||
}
|
||||
}
|
||||
96
vendor/s9e/text-formatter/src/Configurator/Collections/HostnameList.php
vendored
Normal file
96
vendor/s9e/text-formatter/src/Configurator/Collections/HostnameList.php
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
<?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\Helpers\RegexpBuilder;
|
||||
use s9e\TextFormatter\Configurator\Items\Regexp;
|
||||
|
||||
class HostnameList extends NormalizedList
|
||||
{
|
||||
/**
|
||||
* Return this hostname list as a regexp's config
|
||||
*
|
||||
* @return Regexp|null A Regexp instance, or NULL if the collection is empty
|
||||
*/
|
||||
public function asConfig()
|
||||
{
|
||||
if (empty($this->items))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Regexp($this->getRegexp());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a regexp that matches the list of hostnames
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRegexp()
|
||||
{
|
||||
$hosts = [];
|
||||
foreach ($this->items as $host)
|
||||
{
|
||||
$hosts[] = $this->normalizeHostmask($host);
|
||||
}
|
||||
|
||||
$regexp = RegexpBuilder::fromList(
|
||||
$hosts,
|
||||
[
|
||||
// Asterisks * are turned into a catch-all expression, while ^ and $ are preserved
|
||||
'specialChars' => [
|
||||
'*' => '.*',
|
||||
'^' => '^',
|
||||
'$' => '$'
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
return '/' . $regexp . '/DSis';
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a hostmask to a regular expression
|
||||
*
|
||||
* @param string $host Hostname or hostmask
|
||||
* @return string
|
||||
*/
|
||||
protected function normalizeHostmask($host)
|
||||
{
|
||||
if (preg_match('#[\\x80-\xff]#', $host) && function_exists('idn_to_ascii'))
|
||||
{
|
||||
$variant = (defined('INTL_IDNA_VARIANT_UTS46')) ? INTL_IDNA_VARIANT_UTS46 : 0;
|
||||
$host = idn_to_ascii($host, 0, $variant);
|
||||
}
|
||||
|
||||
if (substr($host, 0, 1) === '*')
|
||||
{
|
||||
// *.example.com => /\.example\.com$/
|
||||
$host = ltrim($host, '*');
|
||||
}
|
||||
else
|
||||
{
|
||||
// example.com => /^example\.com$/
|
||||
$host = '^' . $host;
|
||||
}
|
||||
|
||||
if (substr($host, -1) === '*')
|
||||
{
|
||||
// example.* => /^example\./
|
||||
$host = rtrim($host, '*');
|
||||
}
|
||||
else
|
||||
{
|
||||
// example.com => /^example\.com$/
|
||||
$host .= '$';
|
||||
}
|
||||
|
||||
return $host;
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,61 @@
|
||||
<?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
|
||||
{
|
||||
/**
|
||||
* Normalize the value to an object
|
||||
*
|
||||
* @param Minifier|string $minifier
|
||||
* @return Minifier
|
||||
*/
|
||||
public function normalizeValue($minifier)
|
||||
{
|
||||
if (\is_string($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));
|
||||
}
|
||||
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));
|
||||
{
|
||||
throw new InvalidArgumentException('Invalid minifier ' . var_export($minifier, true));
|
||||
}
|
||||
|
||||
return $minifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a Minifier instance
|
||||
*
|
||||
* @param string Minifier's name
|
||||
* @param array Constructor's arguments
|
||||
* @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));
|
||||
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;
|
||||
}
|
||||
}
|
||||
262
vendor/s9e/text-formatter/src/Configurator/Collections/NormalizedCollection.php
vendored
Normal file
262
vendor/s9e/text-formatter/src/Configurator/Collections/NormalizedCollection.php
vendored
Normal file
@@ -0,0 +1,262 @@
|
||||
<?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 ArrayAccess;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
class NormalizedCollection extends Collection implements ArrayAccess
|
||||
{
|
||||
/**
|
||||
* @var string Action to take when add() is called with a key that already exists
|
||||
*/
|
||||
protected $onDuplicateAction = 'error';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function asConfig()
|
||||
{
|
||||
$config = parent::asConfig();
|
||||
ksort($config);
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query and set the action to take when add() is called with a key that already exists
|
||||
*
|
||||
* @param string|null $action If specified: either "error", "ignore" or "replace"
|
||||
* @return string Old action
|
||||
*/
|
||||
public function onDuplicate($action = null)
|
||||
{
|
||||
// Save the old action so it can be returned
|
||||
$old = $this->onDuplicateAction;
|
||||
|
||||
if (func_num_args() && $action !== 'error' && $action !== 'ignore' && $action !== 'replace')
|
||||
{
|
||||
throw new InvalidArgumentException("Invalid onDuplicate action '" . $action . "'. Expected: 'error', 'ignore' or 'replace'");
|
||||
}
|
||||
|
||||
$this->onDuplicateAction = $action;
|
||||
|
||||
return $old;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
// Overridable methods
|
||||
//==========================================================================
|
||||
|
||||
/**
|
||||
* Return the exception that is thrown when creating an item using a key that already exists
|
||||
*
|
||||
* @param string $key Item's key
|
||||
* @return RuntimeException
|
||||
*/
|
||||
protected function getAlreadyExistsException($key)
|
||||
{
|
||||
return new RuntimeException("Item '" . $key . "' already exists");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the exception that is thrown when accessing an item that does not exist
|
||||
*
|
||||
* @param string $key Item's key
|
||||
* @return RuntimeException
|
||||
*/
|
||||
protected function getNotExistException($key)
|
||||
{
|
||||
return new RuntimeException("Item '" . $key . "' does not exist");
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize an item's key
|
||||
*
|
||||
* This method can be overridden to implement keys normalization or implement constraints
|
||||
*
|
||||
* @param string $key Original key
|
||||
* @return string Normalized key
|
||||
*/
|
||||
public function normalizeKey($key)
|
||||
{
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a value for storage
|
||||
*
|
||||
* This method can be overridden to implement value normalization
|
||||
*
|
||||
* @param mixed $value Original value
|
||||
* @return mixed Normalized value
|
||||
*/
|
||||
public function normalizeValue($value)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
// Items access/manipulation
|
||||
//==========================================================================
|
||||
|
||||
/**
|
||||
* Add an item to this collection
|
||||
*
|
||||
* NOTE: relies on exists() to check the key for invalid values and on set() to normalize it
|
||||
*
|
||||
* @param string $key Item's key
|
||||
* @param mixed $value Item's value
|
||||
* @return mixed Normalized value
|
||||
*/
|
||||
public function add($key, $value = null)
|
||||
{
|
||||
// Test whether this key is already in use
|
||||
if ($this->exists($key))
|
||||
{
|
||||
// If the action is "ignore" we return the old value, if it's "error" we throw an
|
||||
// exception. Otherwise, we keep going and replace the value
|
||||
if ($this->onDuplicateAction === 'ignore')
|
||||
{
|
||||
return $this->get($key);
|
||||
}
|
||||
elseif ($this->onDuplicateAction === 'error')
|
||||
{
|
||||
throw $this->getAlreadyExistsException($key);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->set($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a given value is present in this collection
|
||||
*
|
||||
* @param mixed $value Original value
|
||||
* @return bool Whether the normalized value was found in this collection
|
||||
*/
|
||||
public function contains($value)
|
||||
{
|
||||
return in_array($this->normalizeValue($value), $this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an item from this collection
|
||||
*
|
||||
* @param string $key Item's key
|
||||
* @return void
|
||||
*/
|
||||
public function delete($key)
|
||||
{
|
||||
$key = $this->normalizeKey($key);
|
||||
|
||||
unset($this->items[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether an item of given key exists
|
||||
*
|
||||
* @param string $key Item's key
|
||||
* @return bool Whether this key exists in this collection
|
||||
*/
|
||||
public function exists($key)
|
||||
{
|
||||
$key = $this->normalizeKey($key);
|
||||
|
||||
return array_key_exists($key, $this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a value from this collection
|
||||
*
|
||||
* @param string $key Item's key
|
||||
* @return mixed Normalized value
|
||||
*/
|
||||
public function get($key)
|
||||
{
|
||||
if (!$this->exists($key))
|
||||
{
|
||||
throw $this->getNotExistException($key);
|
||||
}
|
||||
|
||||
$key = $this->normalizeKey($key);
|
||||
|
||||
return $this->items[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index of a given value
|
||||
*
|
||||
* Will return the first key associated with the given value, or FALSE if the value is not found
|
||||
*
|
||||
* @param mixed $value Original value
|
||||
* @return mixed Index of the value, or FALSE if not found
|
||||
*/
|
||||
public function indexOf($value)
|
||||
{
|
||||
return array_search($this->normalizeValue($value), $this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set and overwrite a value in this collection
|
||||
*
|
||||
* @param string $key Item's key
|
||||
* @param mixed $value Item's value
|
||||
* @return mixed Normalized value
|
||||
*/
|
||||
public function set($key, $value)
|
||||
{
|
||||
$key = $this->normalizeKey($key);
|
||||
|
||||
$this->items[$key] = $this->normalizeValue($value);
|
||||
|
||||
return $this->items[$key];
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
// ArrayAccess stuff
|
||||
//==========================================================================
|
||||
|
||||
/**
|
||||
* @param string|integer $offset
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return $this->exists($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|integer $offset
|
||||
* @return mixed
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->get($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|integer $offset
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
$this->set($offset, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|integer $offset
|
||||
* @return void
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
$this->delete($offset);
|
||||
}
|
||||
}
|
||||
162
vendor/s9e/text-formatter/src/Configurator/Collections/NormalizedList.php
vendored
Normal file
162
vendor/s9e/text-formatter/src/Configurator/Collections/NormalizedList.php
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
<?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;
|
||||
|
||||
class NormalizedList extends NormalizedCollection
|
||||
{
|
||||
/**
|
||||
* Add (append) a value to this list
|
||||
*
|
||||
* Alias for append(). Overrides NormalizedCollection::add()
|
||||
*
|
||||
* @param mixed $value Original value
|
||||
* @param null $void Unused
|
||||
* @return mixed Normalized value
|
||||
*/
|
||||
public function add($value, $void = null)
|
||||
{
|
||||
return $this->append($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a value to this list
|
||||
*
|
||||
* @param mixed $value Original value
|
||||
* @return mixed Normalized value
|
||||
*/
|
||||
public function append($value)
|
||||
{
|
||||
$value = $this->normalizeValue($value);
|
||||
|
||||
$this->items[] = $value;
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a value from this list and remove gaps in keys
|
||||
*
|
||||
* NOTE: parent::offsetUnset() maps to $this->delete() so this method covers both usages
|
||||
*
|
||||
* @param string $key
|
||||
* @return void
|
||||
*/
|
||||
public function delete($key)
|
||||
{
|
||||
parent::delete($key);
|
||||
|
||||
// Reindex the array to eliminate any gaps
|
||||
$this->items = array_values($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a value at an arbitrary 0-based position
|
||||
*
|
||||
* @param integer $offset
|
||||
* @param mixed $value
|
||||
* @return mixed Normalized value
|
||||
*/
|
||||
public function insert($offset, $value)
|
||||
{
|
||||
$offset = $this->normalizeKey($offset);
|
||||
$value = $this->normalizeValue($value);
|
||||
|
||||
// Insert the value at given offset. We put the value into an array so that array_splice()
|
||||
// won't insert it as multiple elements if it happens to be an array
|
||||
array_splice($this->items, $offset, 0, [$value]);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the key is a valid offset
|
||||
*
|
||||
* Negative values count from the end of the list
|
||||
*
|
||||
* @param mixed $key
|
||||
* @return integer
|
||||
*/
|
||||
public function normalizeKey($key)
|
||||
{
|
||||
$normalizedKey = filter_var(
|
||||
(preg_match('(^-\\d+$)D', $key)) ? count($this->items) + $key : $key,
|
||||
FILTER_VALIDATE_INT,
|
||||
[
|
||||
'options' => [
|
||||
'min_range' => 0,
|
||||
'max_range' => count($this->items)
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
if ($normalizedKey === false)
|
||||
{
|
||||
throw new InvalidArgumentException("Invalid offset '" . $key . "'");
|
||||
}
|
||||
|
||||
return $normalizedKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom offsetSet() implementation to allow assignment with a null offset to append to the
|
||||
* chain
|
||||
*
|
||||
* @param mixed $offset
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
if ($offset === null)
|
||||
{
|
||||
// $list[] = 'foo' maps to $list->append('foo')
|
||||
$this->append($value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use the default implementation
|
||||
parent::offsetSet($offset, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend a value to this list
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return mixed Normalized value
|
||||
*/
|
||||
public function prepend($value)
|
||||
{
|
||||
$value = $this->normalizeValue($value);
|
||||
|
||||
array_unshift($this->items, $value);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all items matching given value
|
||||
*
|
||||
* @param mixed $value Original value
|
||||
* @return integer Number of items removed
|
||||
*/
|
||||
public function remove($value)
|
||||
{
|
||||
$keys = array_keys($this->items, $this->normalizeValue($value));
|
||||
foreach ($keys as $k)
|
||||
{
|
||||
unset($this->items[$k]);
|
||||
}
|
||||
|
||||
$this->items = array_values($this->items);
|
||||
|
||||
return count($keys);
|
||||
}
|
||||
}
|
||||
149
vendor/s9e/text-formatter/src/Configurator/Collections/PluginCollection.php
vendored
Normal file
149
vendor/s9e/text-formatter/src/Configurator/Collections/PluginCollection.php
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
<?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 RuntimeException;
|
||||
use s9e\TextFormatter\Configurator;
|
||||
use s9e\TextFormatter\Plugins\ConfiguratorBase;
|
||||
|
||||
class PluginCollection extends NormalizedCollection
|
||||
{
|
||||
/**
|
||||
* @var Configurator
|
||||
*/
|
||||
protected $configurator;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Configurator $configurator
|
||||
*/
|
||||
public function __construct(Configurator $configurator)
|
||||
{
|
||||
$this->configurator = $configurator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize all of this collection's plugins
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function finalize()
|
||||
{
|
||||
foreach ($this->items as $plugin)
|
||||
{
|
||||
$plugin->finalize();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a plugin name
|
||||
*
|
||||
* @param string $pluginName
|
||||
* @return string
|
||||
*/
|
||||
public function normalizeKey($pluginName)
|
||||
{
|
||||
if (!preg_match('#^[A-Z][A-Za-z_0-9]+$#D', $pluginName))
|
||||
{
|
||||
throw new InvalidArgumentException("Invalid plugin name '" . $pluginName . "'");
|
||||
}
|
||||
|
||||
return $pluginName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a plugin instance/ensure it implements the correct interface
|
||||
*
|
||||
* @param mixed Either a class name or an object that implements ConfiguratorBase
|
||||
* @return ConfiguratorBase
|
||||
*/
|
||||
public function normalizeValue($value)
|
||||
{
|
||||
if (is_string($value) && class_exists($value))
|
||||
{
|
||||
$value = new $value($this->configurator);
|
||||
}
|
||||
|
||||
if ($value instanceof ConfiguratorBase)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('PluginCollection::normalizeValue() expects a class name or an object that implements s9e\\TextFormatter\\Plugins\\ConfiguratorBase');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a default plugin
|
||||
*
|
||||
* @param string $pluginName Name of the plugin
|
||||
* @param array $overrideProps Properties of the plugin will be overwritten with those
|
||||
* @return ConfiguratorBase
|
||||
*/
|
||||
public function load($pluginName, array $overrideProps = [])
|
||||
{
|
||||
// Validate the plugin name / class
|
||||
$pluginName = $this->normalizeKey($pluginName);
|
||||
$className = 's9e\\TextFormatter\\Plugins\\' . $pluginName . '\\Configurator';
|
||||
|
||||
if (!class_exists($className))
|
||||
{
|
||||
throw new RuntimeException("Class '" . $className . "' does not exist");
|
||||
}
|
||||
|
||||
// Create the plugin
|
||||
$plugin = new $className($this->configurator, $overrideProps);
|
||||
|
||||
// Save it
|
||||
$this->set($pluginName, $plugin);
|
||||
|
||||
// Return it
|
||||
return $plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function asConfig()
|
||||
{
|
||||
$plugins = parent::asConfig();
|
||||
|
||||
// Adjust plugins' default properties
|
||||
foreach ($plugins as $pluginName => &$pluginConfig)
|
||||
{
|
||||
$plugin = $this->get($pluginName);
|
||||
|
||||
// Add base properties
|
||||
$pluginConfig += $plugin->getBaseProperties();
|
||||
|
||||
// Remove quickMatch if it's false
|
||||
if ($pluginConfig['quickMatch'] === false)
|
||||
{
|
||||
unset($pluginConfig['quickMatch']);
|
||||
}
|
||||
|
||||
// Remove regexpLimit if there's no regexp
|
||||
if (!isset($pluginConfig['regexp']))
|
||||
{
|
||||
unset($pluginConfig['regexpLimit']);
|
||||
}
|
||||
|
||||
// Remove className if it's a default plugin using its default name. Its class name will
|
||||
// be generated by the parser automatically
|
||||
$className = 's9e\\TextFormatter\\Plugins\\' . $pluginName . '\\Parser';
|
||||
if ($pluginConfig['className'] === $className)
|
||||
{
|
||||
unset($pluginConfig['className']);
|
||||
}
|
||||
}
|
||||
unset($pluginConfig);
|
||||
|
||||
return $plugins;
|
||||
}
|
||||
}
|
||||
42
vendor/s9e/text-formatter/src/Configurator/Collections/RulesGeneratorList.php
vendored
Normal file
42
vendor/s9e/text-formatter/src/Configurator/Collections/RulesGeneratorList.php
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
<?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 s9e\TextFormatter\Configurator\RulesGenerators\Interfaces\BooleanRulesGenerator;
|
||||
use s9e\TextFormatter\Configurator\RulesGenerators\Interfaces\TargetedRulesGenerator;
|
||||
|
||||
class RulesGeneratorList extends NormalizedList
|
||||
{
|
||||
/**
|
||||
* Normalize the value to an object
|
||||
*
|
||||
* @param string|BooleanRulesGenerator|TargetedRulesGenerator $generator Either a string, or an instance of a rules generator
|
||||
* @return BooleanRulesGenerator|TargetedRulesGenerator
|
||||
*/
|
||||
public function normalizeValue($generator)
|
||||
{
|
||||
if (is_string($generator))
|
||||
{
|
||||
$className = 's9e\\TextFormatter\\Configurator\\RulesGenerators\\' . $generator;
|
||||
|
||||
if (class_exists($className))
|
||||
{
|
||||
$generator = new $className;
|
||||
}
|
||||
}
|
||||
|
||||
if (!($generator instanceof BooleanRulesGenerator)
|
||||
&& !($generator instanceof TargetedRulesGenerator))
|
||||
{
|
||||
throw new InvalidArgumentException('Invalid rules generator ' . var_export($generator, true));
|
||||
}
|
||||
|
||||
return $generator;
|
||||
}
|
||||
}
|
||||
310
vendor/s9e/text-formatter/src/Configurator/Collections/Ruleset.php
vendored
Normal file
310
vendor/s9e/text-formatter/src/Configurator/Collections/Ruleset.php
vendored
Normal file
@@ -0,0 +1,310 @@
|
||||
<?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 ArrayAccess;
|
||||
use BadMethodCallException;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use s9e\TextFormatter\Configurator\ConfigProvider;
|
||||
use s9e\TextFormatter\Configurator\JavaScript\Dictionary;
|
||||
use s9e\TextFormatter\Configurator\Validators\TagName;
|
||||
use s9e\TextFormatter\Parser;
|
||||
|
||||
/**
|
||||
* @method void allowChild(string $tagName)
|
||||
* @method void allowDescendant(string $tagName)
|
||||
* @method void autoClose(bool $bool = true)
|
||||
* @method void autoReopen(bool $bool = true)
|
||||
* @method void breakParagraph(bool $bool = true)
|
||||
* @method void closeAncestor(string $tagName)
|
||||
* @method void closeParent(string $tagName)
|
||||
* @method void createChild(string $tagName)
|
||||
* @method void createParagraphs(bool $bool = true)
|
||||
* @method void denyChild(string $tagName)
|
||||
* @method void denyDescendant(string $tagName)
|
||||
* @method void disableAutoLineBreaks(bool $bool = true)
|
||||
* @method void enableAutoLineBreaks(bool $bool = true)
|
||||
* @method void fosterParent(string $tagName)
|
||||
* @method void ignoreSurroundingWhitespace(bool $bool = true)
|
||||
* @method void ignoreTags(bool $bool = true)
|
||||
* @method void ignoreText(bool $bool = true)
|
||||
* @method void isTransparent(bool $bool = true)
|
||||
* @method void preventLineBreaks(bool $bool = true)
|
||||
* @method void requireParent(string $tagName)
|
||||
* @method void requireAncestor(string $tagName)
|
||||
* @method void suspendAutoLineBreaks(bool $bool = true)
|
||||
* @method void trimFirstLine(bool $bool = true)
|
||||
* @see /docs/Rules.md
|
||||
*/
|
||||
class Ruleset extends Collection implements ArrayAccess, ConfigProvider
|
||||
{
|
||||
/**
|
||||
* @var array Supported rules and the method used to add them
|
||||
*/
|
||||
protected $rules = [
|
||||
'allowChild' => 'addTargetedRule',
|
||||
'allowDescendant' => 'addTargetedRule',
|
||||
'autoClose' => 'addBooleanRule',
|
||||
'autoReopen' => 'addBooleanRule',
|
||||
'breakParagraph' => 'addBooleanRule',
|
||||
'closeAncestor' => 'addTargetedRule',
|
||||
'closeParent' => 'addTargetedRule',
|
||||
'createChild' => 'addTargetedRule',
|
||||
'createParagraphs' => 'addBooleanRule',
|
||||
'denyChild' => 'addTargetedRule',
|
||||
'denyDescendant' => 'addTargetedRule',
|
||||
'disableAutoLineBreaks' => 'addBooleanRule',
|
||||
'enableAutoLineBreaks' => 'addBooleanRule',
|
||||
'fosterParent' => 'addTargetedRule',
|
||||
'ignoreSurroundingWhitespace' => 'addBooleanRule',
|
||||
'ignoreTags' => 'addBooleanRule',
|
||||
'ignoreText' => 'addBooleanRule',
|
||||
'isTransparent' => 'addBooleanRule',
|
||||
'preventLineBreaks' => 'addBooleanRule',
|
||||
'requireParent' => 'addTargetedRule',
|
||||
'requireAncestor' => 'addTargetedRule',
|
||||
'suspendAutoLineBreaks' => 'addBooleanRule',
|
||||
'trimFirstLine' => 'addBooleanRule'
|
||||
];
|
||||
|
||||
/**
|
||||
* Add a rule to this set
|
||||
*
|
||||
* @param string $methodName Rule name
|
||||
* @param array $args Arguments used to add given rule
|
||||
* @return self
|
||||
*/
|
||||
public function __call($methodName, array $args)
|
||||
{
|
||||
if (!isset($this->rules[$methodName]))
|
||||
{
|
||||
throw new BadMethodCallException("Undefined method '" . $methodName . "'");
|
||||
}
|
||||
|
||||
array_unshift($args, $methodName);
|
||||
call_user_func_array([$this, $this->rules[$methodName]], $args);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
// ArrayAccess methods
|
||||
//==========================================================================
|
||||
|
||||
/**
|
||||
* Test whether a rule category exists
|
||||
*
|
||||
* @param string $k Rule name, e.g. "allowChild" or "isTransparent"
|
||||
*/
|
||||
public function offsetExists($k)
|
||||
{
|
||||
return isset($this->items[$k]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the content of a rule category
|
||||
*
|
||||
* @param string $k Rule name, e.g. "allowChild" or "isTransparent"
|
||||
* @return mixed
|
||||
*/
|
||||
public function offsetGet($k)
|
||||
{
|
||||
return $this->items[$k];
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported
|
||||
*/
|
||||
public function offsetSet($k, $v)
|
||||
{
|
||||
throw new RuntimeException('Not supported');
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear a subset of the rules
|
||||
*
|
||||
* @see clear()
|
||||
*
|
||||
* @param string $k Rule name, e.g. "allowChild" or "isTransparent"
|
||||
*/
|
||||
public function offsetUnset($k)
|
||||
{
|
||||
return $this->remove($k);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
// Generic methods
|
||||
//==========================================================================
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function asConfig()
|
||||
{
|
||||
$config = $this->items;
|
||||
|
||||
// Remove rules that are not needed at parsing time. All of those are resolved when building
|
||||
// the allowed bitfields
|
||||
unset($config['allowChild']);
|
||||
unset($config['allowDescendant']);
|
||||
unset($config['denyChild']);
|
||||
unset($config['denyDescendant']);
|
||||
unset($config['requireParent']);
|
||||
|
||||
// Pack boolean rules into a bitfield
|
||||
$bitValues = [
|
||||
'autoClose' => Parser::RULE_AUTO_CLOSE,
|
||||
'autoReopen' => Parser::RULE_AUTO_REOPEN,
|
||||
'breakParagraph' => Parser::RULE_BREAK_PARAGRAPH,
|
||||
'createParagraphs' => Parser::RULE_CREATE_PARAGRAPHS,
|
||||
'disableAutoLineBreaks' => Parser::RULE_DISABLE_AUTO_BR,
|
||||
'enableAutoLineBreaks' => Parser::RULE_ENABLE_AUTO_BR,
|
||||
'ignoreSurroundingWhitespace' => Parser::RULE_IGNORE_WHITESPACE,
|
||||
'ignoreTags' => Parser::RULE_IGNORE_TAGS,
|
||||
'ignoreText' => Parser::RULE_IGNORE_TEXT,
|
||||
'isTransparent' => Parser::RULE_IS_TRANSPARENT,
|
||||
'preventLineBreaks' => Parser::RULE_PREVENT_BR,
|
||||
'suspendAutoLineBreaks' => Parser::RULE_SUSPEND_AUTO_BR,
|
||||
'trimFirstLine' => Parser::RULE_TRIM_FIRST_LINE
|
||||
];
|
||||
|
||||
$bitfield = 0;
|
||||
foreach ($bitValues as $ruleName => $bitValue)
|
||||
{
|
||||
if (!empty($config[$ruleName]))
|
||||
{
|
||||
$bitfield |= $bitValue;
|
||||
}
|
||||
|
||||
unset($config[$ruleName]);
|
||||
}
|
||||
|
||||
// In order to speed up lookups, we use the tag names as keys
|
||||
foreach (['closeAncestor', 'closeParent', 'fosterParent'] as $ruleName)
|
||||
{
|
||||
if (isset($config[$ruleName]))
|
||||
{
|
||||
$targets = array_fill_keys($config[$ruleName], 1);
|
||||
$config[$ruleName] = new Dictionary($targets);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the bitfield to the config
|
||||
$config['flags'] = $bitfield;
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge a set of rules into this collection
|
||||
*
|
||||
* @param array|Ruleset $rules 2D array of rule definitions, or instance of Ruleset
|
||||
* @param bool $overwrite Whether to overwrite scalar rules (e.g. boolean rules)
|
||||
*/
|
||||
public function merge($rules, $overwrite = true)
|
||||
{
|
||||
if (!is_array($rules)
|
||||
&& !($rules instanceof self))
|
||||
{
|
||||
throw new InvalidArgumentException('merge() expects an array or an instance of Ruleset');
|
||||
}
|
||||
|
||||
foreach ($rules as $action => $value)
|
||||
{
|
||||
if (is_array($value))
|
||||
{
|
||||
foreach ($value as $tagName)
|
||||
{
|
||||
$this->$action($tagName);
|
||||
}
|
||||
}
|
||||
elseif ($overwrite || !isset($this->items[$action]))
|
||||
{
|
||||
$this->$action($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a specific rule, or all the rules of a given type
|
||||
*
|
||||
* @param string $type Type of rules to clear
|
||||
* @param string $tagName Name of the target tag, or none to remove all rules of given type
|
||||
* @return void
|
||||
*/
|
||||
public function remove($type, $tagName = null)
|
||||
{
|
||||
if (preg_match('(^default(?:Child|Descendant)Rule)', $type))
|
||||
{
|
||||
throw new InvalidArgumentException('Cannot remove ' . $type);
|
||||
}
|
||||
|
||||
if (isset($tagName))
|
||||
{
|
||||
$tagName = TagName::normalize($tagName);
|
||||
|
||||
if (isset($this->items[$type]))
|
||||
{
|
||||
// Compute the difference between current list and our one tag name
|
||||
$this->items[$type] = array_diff(
|
||||
$this->items[$type],
|
||||
[$tagName]
|
||||
);
|
||||
|
||||
if (empty($this->items[$type]))
|
||||
{
|
||||
// If the list is now empty, keep it neat and unset it
|
||||
unset($this->items[$type]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the list still have names, keep it neat and rearrange keys
|
||||
$this->items[$type] = array_values($this->items[$type]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
unset($this->items[$type]);
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
// Rules
|
||||
//==========================================================================
|
||||
|
||||
/**
|
||||
* Add a boolean rule
|
||||
*
|
||||
* @param string $ruleName Name of the rule
|
||||
* @param bool $bool Whether to enable or disable the rule
|
||||
* @return self
|
||||
*/
|
||||
protected function addBooleanRule($ruleName, $bool = true)
|
||||
{
|
||||
if (!is_bool($bool))
|
||||
{
|
||||
throw new InvalidArgumentException($ruleName . '() expects a boolean');
|
||||
}
|
||||
|
||||
$this->items[$ruleName] = $bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a targeted rule
|
||||
*
|
||||
* @param string $ruleName Name of the rule
|
||||
* @param string $tagName Name of the target tag
|
||||
* @return self
|
||||
*/
|
||||
protected function addTargetedRule($ruleName, $tagName)
|
||||
{
|
||||
$this->items[$ruleName][] = TagName::normalize($tagName);
|
||||
}
|
||||
}
|
||||
43
vendor/s9e/text-formatter/src/Configurator/Collections/SchemeList.php
vendored
Normal file
43
vendor/s9e/text-formatter/src/Configurator/Collections/SchemeList.php
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
<?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 s9e\TextFormatter\Configurator\Helpers\RegexpBuilder;
|
||||
use s9e\TextFormatter\Configurator\Items\Regexp;
|
||||
|
||||
class SchemeList extends NormalizedList
|
||||
{
|
||||
/**
|
||||
* Return this scheme list as a regexp
|
||||
*
|
||||
* @return Regexp
|
||||
*/
|
||||
public function asConfig()
|
||||
{
|
||||
return new Regexp('/^' . RegexpBuilder::fromList($this->items) . '$/Di');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and normalize a scheme name to lowercase, or throw an exception if invalid
|
||||
*
|
||||
* @link http://tools.ietf.org/html/rfc3986#section-3.1
|
||||
*
|
||||
* @param string $scheme URL scheme, e.g. "file" or "ed2k"
|
||||
* @return string
|
||||
*/
|
||||
public function normalizeValue($scheme)
|
||||
{
|
||||
if (!preg_match('#^[a-z][a-z0-9+\\-.]*$#Di', $scheme))
|
||||
{
|
||||
throw new InvalidArgumentException("Invalid scheme name '" . $scheme . "'");
|
||||
}
|
||||
|
||||
return strtolower($scheme);
|
||||
}
|
||||
}
|
||||
60
vendor/s9e/text-formatter/src/Configurator/Collections/TagCollection.php
vendored
Normal file
60
vendor/s9e/text-formatter/src/Configurator/Collections/TagCollection.php
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
<?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 RuntimeException;
|
||||
use s9e\TextFormatter\Configurator\Items\Tag;
|
||||
use s9e\TextFormatter\Configurator\Validators\TagName;
|
||||
|
||||
class TagCollection extends NormalizedCollection
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $onDuplicateAction = 'replace';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getAlreadyExistsException($key)
|
||||
{
|
||||
return new RuntimeException("Tag '" . $key . "' already exists");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNotExistException($key)
|
||||
{
|
||||
return new RuntimeException("Tag '" . $key . "' does not exist");
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a tag name used as a key in this colelction
|
||||
*
|
||||
* @param string $key Original name
|
||||
* @return string Normalized name
|
||||
*/
|
||||
public function normalizeKey($key)
|
||||
{
|
||||
return TagName::normalize($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a value to an instance of Tag
|
||||
*
|
||||
* @param array|null|Tag $value
|
||||
* @return Tag
|
||||
*/
|
||||
public function normalizeValue($value)
|
||||
{
|
||||
return ($value instanceof Tag)
|
||||
? $value
|
||||
: new Tag($value);
|
||||
}
|
||||
}
|
||||
19
vendor/s9e/text-formatter/src/Configurator/Collections/TagFilterChain.php
vendored
Normal file
19
vendor/s9e/text-formatter/src/Configurator/Collections/TagFilterChain.php
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
<?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;
|
||||
|
||||
class TagFilterChain extends FilterChain
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFilterClassName()
|
||||
{
|
||||
return 's9e\\TextFormatter\\Configurator\\Items\\TagFilter';
|
||||
}
|
||||
}
|
||||
@@ -1,22 +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\Collections;
|
||||
|
||||
use s9e\TextFormatter\Configurator\Validators\TagName;
|
||||
|
||||
/**
|
||||
* Hosts a list of tag names. The config array it returns contains the names, deduplicated and sorted
|
||||
*/
|
||||
class TagList extends NormalizedList
|
||||
{
|
||||
/**
|
||||
* Normalize a value to a tag name
|
||||
*
|
||||
* @param string $attrName
|
||||
* @return string
|
||||
*/
|
||||
public function normalizeValue($attrName)
|
||||
{
|
||||
return TagName::normalize($attrName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function asConfig()
|
||||
{
|
||||
$list = \array_unique($this->items);
|
||||
\sort($list);
|
||||
$list = array_unique($this->items);
|
||||
sort($list);
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
30
vendor/s9e/text-formatter/src/Configurator/Collections/TemplateCheckList.php
vendored
Normal file
30
vendor/s9e/text-formatter/src/Configurator/Collections/TemplateCheckList.php
vendored
Normal 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\Collections;
|
||||
|
||||
use s9e\TextFormatter\Configurator\TemplateCheck;
|
||||
|
||||
class TemplateCheckList extends NormalizedList
|
||||
{
|
||||
/**
|
||||
* Normalize the value to an instance of TemplateCheck
|
||||
*
|
||||
* @param mixed $check Either a string, or an instance of TemplateCheck
|
||||
* @return TemplateCheck An instance of TemplateCheck
|
||||
*/
|
||||
public function normalizeValue($check)
|
||||
{
|
||||
if (!($check instanceof TemplateCheck))
|
||||
{
|
||||
$className = 's9e\\TextFormatter\\Configurator\\TemplateChecks\\' . $check;
|
||||
$check = new $className;
|
||||
}
|
||||
|
||||
return $check;
|
||||
}
|
||||
}
|
||||
37
vendor/s9e/text-formatter/src/Configurator/Collections/TemplateNormalizationList.php
vendored
Normal file
37
vendor/s9e/text-formatter/src/Configurator/Collections/TemplateNormalizationList.php
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
<?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\TemplateNormalizations\AbstractNormalization;
|
||||
use s9e\TextFormatter\Configurator\TemplateNormalizations\Custom;
|
||||
|
||||
class TemplateNormalizationList extends NormalizedList
|
||||
{
|
||||
/**
|
||||
* Normalize the value to an instance of AbstractNormalization
|
||||
*
|
||||
* @param mixed $value Either a string, or an instance of AbstractNormalization
|
||||
* @return AbstractNormalization An instance of AbstractNormalization
|
||||
*/
|
||||
public function normalizeValue($value)
|
||||
{
|
||||
if ($value instanceof AbstractNormalization)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (is_callable($value))
|
||||
{
|
||||
return new Custom($value);
|
||||
}
|
||||
|
||||
$className = 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\' . $value;
|
||||
|
||||
return new $className;
|
||||
}
|
||||
}
|
||||
35
vendor/s9e/text-formatter/src/Configurator/Collections/TemplateParameterCollection.php
vendored
Normal file
35
vendor/s9e/text-formatter/src/Configurator/Collections/TemplateParameterCollection.php
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
<?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\TemplateParameterName;
|
||||
|
||||
class TemplateParameterCollection extends NormalizedCollection
|
||||
{
|
||||
/**
|
||||
* Normalize a parameter name
|
||||
*
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
public function normalizeKey($key)
|
||||
{
|
||||
return TemplateParameterName::normalize($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a parameter value
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return string
|
||||
*/
|
||||
public function normalizeValue($value)
|
||||
{
|
||||
return (string) $value;
|
||||
}
|
||||
}
|
||||
21
vendor/s9e/text-formatter/src/Configurator/ConfigProvider.php
vendored
Normal file
21
vendor/s9e/text-formatter/src/Configurator/ConfigProvider.php
vendored
Normal 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;
|
||||
|
||||
interface ConfigProvider
|
||||
{
|
||||
/**
|
||||
* Return an array-based representation of this object to be used for parsing
|
||||
*
|
||||
* NOTE: if this method was named getConfig() it could interfere with magic getters from
|
||||
* the Configurable trait
|
||||
*
|
||||
* @return array|\s9e\TextFormatter\Configurator\JavaScript\Dictionary|null
|
||||
*/
|
||||
public function asConfig();
|
||||
}
|
||||
@@ -1,30 +1,61 @@
|
||||
<?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
|
||||
{
|
||||
/**
|
||||
* @var DOMNode The node that is responsible for this exception
|
||||
*/
|
||||
protected $node;
|
||||
|
||||
/**
|
||||
* @param string $msg Exception message
|
||||
* @param DOMNode $node The node that is responsible for this exception
|
||||
*/
|
||||
public function __construct($msg, DOMNode $node)
|
||||
{
|
||||
parent::__construct($msg);
|
||||
$this->node = $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the node that has caused this exception
|
||||
*
|
||||
* @return DOMNode
|
||||
*/
|
||||
public function getNode()
|
||||
{
|
||||
return $this->node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight the source of the template that has caused this exception, with the node highlighted
|
||||
*
|
||||
* @param string $prepend HTML to prepend
|
||||
* @param string $append HTML to append
|
||||
* @return string Template's source, as HTML
|
||||
*/
|
||||
public function highlightNode($prepend = '<span style="background-color:#ff0">', $append = '</span>')
|
||||
{
|
||||
return TemplateHelper::highlightNode($this->node, $prepend, $append);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the node associated with this exception
|
||||
*
|
||||
* @param DOMNode $node
|
||||
* @return void
|
||||
*/
|
||||
public function setNode(DOMNode $node)
|
||||
{
|
||||
$this->node = $node;
|
||||
|
||||
19
vendor/s9e/text-formatter/src/Configurator/FilterableConfigValue.php
vendored
Normal file
19
vendor/s9e/text-formatter/src/Configurator/FilterableConfigValue.php
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
<?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;
|
||||
|
||||
interface FilterableConfigValue
|
||||
{
|
||||
/**
|
||||
* Return the config value for given target
|
||||
*
|
||||
* @param $target
|
||||
* @return mixed
|
||||
*/
|
||||
public function filterConfig($target);
|
||||
}
|
||||
119
vendor/s9e/text-formatter/src/Configurator/Helpers/AVTHelper.php
vendored
Normal file
119
vendor/s9e/text-formatter/src/Configurator/Helpers/AVTHelper.php
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
<?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 RuntimeException;
|
||||
|
||||
abstract class AVTHelper
|
||||
{
|
||||
/**
|
||||
* Parse an attribute value template
|
||||
*
|
||||
* @link https://www.w3.org/TR/1999/REC-xslt-19991116#dt-attribute-value-template
|
||||
*
|
||||
* @param string $attrValue Attribute value
|
||||
* @return array Array of tokens
|
||||
*/
|
||||
public static function parse($attrValue)
|
||||
{
|
||||
preg_match_all('((*MARK:literal)(?:[^{]|\\{\\{)++|(*MARK:expression)\\{(?:[^}"\']|"[^"]*+"|\'[^\']*+\')++\\}|(*MARK:junk).++)s', $attrValue, $matches);
|
||||
|
||||
$tokens = [];
|
||||
foreach ($matches[0] as $i => $str)
|
||||
{
|
||||
if ($matches['MARK'][$i] === 'expression')
|
||||
{
|
||||
$tokens[] = ['expression', substr($str, 1, -1)];
|
||||
}
|
||||
else
|
||||
{
|
||||
$tokens[] = ['literal', strtr($str, ['{{' => '{', '}}' => '}'])];
|
||||
}
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the value of an attribute via the provided callback
|
||||
*
|
||||
* The callback will receive an array containing the type and value of each token in the AVT.
|
||||
* Its return value should use the same format
|
||||
*
|
||||
* @param DOMAttr $attribute
|
||||
* @param callable $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function replace(DOMAttr $attribute, callable $callback)
|
||||
{
|
||||
$tokens = self::parse($attribute->value);
|
||||
foreach ($tokens as $k => $token)
|
||||
{
|
||||
$tokens[$k] = $callback($token);
|
||||
}
|
||||
|
||||
$attribute->value = htmlspecialchars(self::serialize($tokens), ENT_NOQUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize an array of AVT tokens back into an attribute value
|
||||
*
|
||||
* @param array $tokens
|
||||
* @return string
|
||||
*/
|
||||
public static function serialize(array $tokens)
|
||||
{
|
||||
$attrValue = '';
|
||||
foreach ($tokens as $token)
|
||||
{
|
||||
if ($token[0] === 'literal')
|
||||
{
|
||||
$attrValue .= preg_replace('([{}])', '$0$0', $token[1]);
|
||||
}
|
||||
elseif ($token[0] === 'expression')
|
||||
{
|
||||
$attrValue .= '{' . $token[1] . '}';
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RuntimeException('Unknown token type');
|
||||
}
|
||||
}
|
||||
|
||||
return $attrValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform given attribute value template into an XSL fragment
|
||||
*
|
||||
* @param string $attrValue
|
||||
* @return string
|
||||
*/
|
||||
public static function toXSL($attrValue)
|
||||
{
|
||||
$xsl = '';
|
||||
foreach (self::parse($attrValue) as list($type, $content))
|
||||
{
|
||||
if ($type === 'expression')
|
||||
{
|
||||
$xsl .= '<xsl:value-of select="' . htmlspecialchars($content, ENT_COMPAT, 'UTF-8') . '"/>';
|
||||
}
|
||||
elseif (trim($content) !== $content)
|
||||
{
|
||||
$xsl .= '<xsl:text>' . htmlspecialchars($content, ENT_NOQUOTES, 'UTF-8') . '</xsl:text>';
|
||||
}
|
||||
else
|
||||
{
|
||||
$xsl .= htmlspecialchars($content, ENT_NOQUOTES, 'UTF-8');
|
||||
}
|
||||
}
|
||||
|
||||
return $xsl;
|
||||
}
|
||||
}
|
||||
186
vendor/s9e/text-formatter/src/Configurator/Helpers/ConfigHelper.php
vendored
Normal file
186
vendor/s9e/text-formatter/src/Configurator/Helpers/ConfigHelper.php
vendored
Normal file
@@ -0,0 +1,186 @@
|
||||
<?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;
|
||||
use Traversable;
|
||||
use s9e\TextFormatter\Configurator\ConfigProvider;
|
||||
use s9e\TextFormatter\Configurator\FilterableConfigValue;
|
||||
use s9e\TextFormatter\Configurator\JavaScript\Dictionary;
|
||||
|
||||
abstract class ConfigHelper
|
||||
{
|
||||
/**
|
||||
* Recursively filter a config array to replace variants with the desired value
|
||||
*
|
||||
* @param array $config Config array
|
||||
* @param string $target Target parser
|
||||
* @return array Filtered config
|
||||
*/
|
||||
public static function filterConfig(array $config, $target = 'PHP')
|
||||
{
|
||||
$filteredConfig = [];
|
||||
foreach ($config as $name => $value)
|
||||
{
|
||||
if ($value instanceof FilterableConfigValue)
|
||||
{
|
||||
$value = $value->filterConfig($target);
|
||||
if (!isset($value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (is_array($value))
|
||||
{
|
||||
$value = self::filterConfig($value, $target);
|
||||
}
|
||||
$filteredConfig[$name] = $value;
|
||||
}
|
||||
|
||||
return $filteredConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a quickMatch string from a list of strings
|
||||
*
|
||||
* This is basically a LCS implementation, tuned for small strings and fast failure
|
||||
*
|
||||
* @param array $strings Array of strings
|
||||
* @return mixed quickMatch string, or FALSE if none could be generated
|
||||
*/
|
||||
public static function generateQuickMatchFromList(array $strings)
|
||||
{
|
||||
foreach ($strings as $string)
|
||||
{
|
||||
$stringLen = strlen($string);
|
||||
$substrings = [];
|
||||
|
||||
for ($len = $stringLen; $len; --$len)
|
||||
{
|
||||
$pos = $stringLen - $len;
|
||||
|
||||
do
|
||||
{
|
||||
$substrings[substr($string, $pos, $len)] = 1;
|
||||
}
|
||||
while (--$pos >= 0);
|
||||
}
|
||||
|
||||
if (isset($goodStrings))
|
||||
{
|
||||
$goodStrings = array_intersect_key($goodStrings, $substrings);
|
||||
|
||||
if (empty($goodStrings))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$goodStrings = $substrings;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($goodStrings))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// The strings are stored by length descending, so we return the first in the list
|
||||
return strval(key($goodStrings));
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimize the size of a deep array by deduplicating identical structures
|
||||
*
|
||||
* This method is meant to be used on a config array which is only read and never modified
|
||||
*
|
||||
* @param array &$config
|
||||
* @param array &$cache
|
||||
* @return array
|
||||
*/
|
||||
public static function optimizeArray(array &$config, array &$cache = [])
|
||||
{
|
||||
foreach ($config as $k => &$v)
|
||||
{
|
||||
if (!is_array($v))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Dig deeper into this array
|
||||
self::optimizeArray($v, $cache);
|
||||
|
||||
// Look for a matching structure
|
||||
$cacheKey = serialize($v);
|
||||
if (!isset($cache[$cacheKey]))
|
||||
{
|
||||
// Record this value in the cache
|
||||
$cache[$cacheKey] = $v;
|
||||
}
|
||||
|
||||
// Replace the entry in $config with a reference to the cached value
|
||||
$config[$k] =& $cache[$cacheKey];
|
||||
}
|
||||
unset($v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a structure to a (possibly multidimensional) array
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param bool $keepEmpty Whether to keep empty arrays instead of removing them
|
||||
* @param bool $keepNull Whether to keep NULL values instead of removing them
|
||||
* @return array
|
||||
*/
|
||||
public static function toArray($value, $keepEmpty = false, $keepNull = false)
|
||||
{
|
||||
$array = [];
|
||||
|
||||
foreach ($value as $k => $v)
|
||||
{
|
||||
$isDictionary = $v instanceof Dictionary;
|
||||
if ($v instanceof ConfigProvider)
|
||||
{
|
||||
$v = $v->asConfig();
|
||||
}
|
||||
elseif ($v instanceof Traversable || is_array($v))
|
||||
{
|
||||
$v = self::toArray($v, $keepEmpty, $keepNull);
|
||||
}
|
||||
elseif (is_scalar($v) || is_null($v))
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
else
|
||||
{
|
||||
$type = (is_object($v))
|
||||
? 'an instance of ' . get_class($v)
|
||||
: 'a ' . gettype($v);
|
||||
|
||||
throw new RuntimeException('Cannot convert ' . $type . ' to array');
|
||||
}
|
||||
|
||||
if (!isset($v) && !$keepNull)
|
||||
{
|
||||
// We don't record NULL values
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$keepEmpty && $v === [])
|
||||
{
|
||||
// We don't record empty structures
|
||||
continue;
|
||||
}
|
||||
|
||||
$array[$k] = ($isDictionary) ? new Dictionary($v) : $v;
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,62 @@
|
||||
<?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
|
||||
{
|
||||
/**
|
||||
* Get the list of UTF-8 characters that are disallowed as a URL
|
||||
*
|
||||
* ":" is disallowed to prevent the URL to have a scheme.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getDisallowedCharactersAsURL()
|
||||
{
|
||||
return [':'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of UTF-8 characters that are disallowed in CSS
|
||||
*
|
||||
* - "(" and ")" are disallowed to prevent executing CSS functions or proprietary extensions that
|
||||
* may execute JavaScript.
|
||||
* - ":" is disallowed to prevent setting extra CSS properties as well as possibly misusing the
|
||||
* url() function with javascript: URIs.
|
||||
* - "\", '"' and "'" are disallowed to prevent breaking out of or interfering with strings.
|
||||
* - ";", "{" and "}" to prevent breaking out of a declaration
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getDisallowedCharactersInCSS()
|
||||
{
|
||||
return ['(', ')', ':', '\\', '"', "'", ';', '{', '}'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of UTF-8 characters that are disallowed in JS
|
||||
*
|
||||
* Allowing *any* input inside of a JavaScript context is a risky proposition. The use cases are
|
||||
* also pretty rare. This list of disallowed characters attempts to block any character that is
|
||||
* potentially unsafe either inside or outside of a string.
|
||||
*
|
||||
* - "(" and ")" are disallowed to prevent executing functions.
|
||||
* - '"', "'", "\" and "`" are disallowed to prevent breaking out of or interfering with strings.
|
||||
* - "\r", "\n", U+2028 and U+2029 are disallowed inside of JavaScript strings.
|
||||
* - ":" and "%" are disallowed to prevent potential exploits that set document.location to a
|
||||
* javascript: URI.
|
||||
* - "=" is disallowed to prevent overwriting existing vars (or constructors, such as Array's) if
|
||||
* the input is used outside of a string
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getDisallowedCharactersInJS()
|
||||
{
|
||||
return ['(', ')', '"', "'", '\\', "\r", "\n", "\xE2\x80\xA8", "\xE2\x80\xA9", ':', '%', '='];
|
||||
return ['(', ')', '"', "'", '\\', '`', "\r", "\n", "\xE2\x80\xA8", "\xE2\x80\xA9", ':', '%', '='];
|
||||
}
|
||||
}
|
||||
399
vendor/s9e/text-formatter/src/Configurator/Helpers/ElementInspector.php
vendored
Normal file
399
vendor/s9e/text-formatter/src/Configurator/Helpers/ElementInspector.php
vendored
Normal file
@@ -0,0 +1,399 @@
|
||||
<?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 DOMElement;
|
||||
use DOMXPath;
|
||||
|
||||
class ElementInspector
|
||||
{
|
||||
/**
|
||||
* This is an abridged version of the HTML5 content models and rules, with some liberties taken.
|
||||
*
|
||||
* For each element, up to three bitfields are defined: "c", "ac" and "dd". Bitfields are stored
|
||||
* as raw bytes, formatted using the octal notation to keep the sources ASCII.
|
||||
*
|
||||
* "c" represents the categories the element belongs to. The categories are comprised of HTML5
|
||||
* content models (such as "phrasing content" or "interactive content") plus a few special
|
||||
* categories created to cover the parts of the specs that refer to "a group of X and Y
|
||||
* elements" rather than a specific content model.
|
||||
*
|
||||
* "ac" represents the categories that are allowed as children of given element.
|
||||
*
|
||||
* "dd" represents the categories that must not appear as a descendant of given element.
|
||||
*
|
||||
* Sometimes, HTML5 specifies some restrictions on when an element can accept certain children,
|
||||
* or what categories the element belongs to. For example, an <img> element is only part of the
|
||||
* "interactive content" category if it has a "usemap" attribute. Those restrictions are
|
||||
* expressed as an XPath expression and stored using the concatenation of the key of the bitfield
|
||||
* plus the bit number of the category. For instance, if "interactive content" got assigned to
|
||||
* bit 2, the definition of the <img> element will contain a key "c2" with value "@usemap".
|
||||
*
|
||||
* Additionally, other flags are set:
|
||||
*
|
||||
* "t" indicates that the element uses the "transparent" content model.
|
||||
* "e" indicates that the element uses the "empty" content model.
|
||||
* "v" indicates that the element is a void element.
|
||||
* "nt" indicates that the element does not accept text nodes. (no text)
|
||||
* "to" indicates that the element should only contain text. (text-only)
|
||||
* "fe" indicates that the element is a formatting element. It will automatically be reopened
|
||||
* when closed by an end tag of a different name.
|
||||
* "b" indicates that the element is not phrasing content, which makes it likely to act like
|
||||
* a block element.
|
||||
*
|
||||
* Finally, HTML5 defines "optional end tag" rules, where one element automatically closes its
|
||||
* predecessor. Those are used to generate closeParent rules and are stored in the "cp" key.
|
||||
*
|
||||
* @var array
|
||||
* @see /scripts/patchElementInspector.php
|
||||
*/
|
||||
protected static $htmlElements = [
|
||||
'a'=>['c'=>"\17\0\0\0\0\2",'c3'=>'@href','ac'=>"\0",'dd'=>"\10\0\0\0\0\2",'t'=>1,'fe'=>1],
|
||||
'abbr'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
|
||||
'address'=>['c'=>"\3\10",'ac'=>"\1",'dd'=>"\200\14",'b'=>1,'cp'=>['p']],
|
||||
'article'=>['c'=>"\3\4",'ac'=>"\1",'dd'=>"\0\0\0\0\20",'b'=>1,'cp'=>['p']],
|
||||
'aside'=>['c'=>"\3\4",'ac'=>"\1",'dd'=>"\0\0\0\0\20",'b'=>1,'cp'=>['p']],
|
||||
'audio'=>['c'=>"\57",'c3'=>'@controls','c1'=>'@controls','ac'=>"\0\0\0\40\1",'ac29'=>'not(@src)','dd'=>"\0\0\0\0\0\4",'dd42'=>'@src','t'=>1],
|
||||
'b'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0",'fe'=>1],
|
||||
'base'=>['c'=>"\20",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1,'b'=>1],
|
||||
'bdi'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
|
||||
'bdo'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
|
||||
'blockquote'=>['c'=>"\103",'ac'=>"\1",'dd'=>"\0",'b'=>1,'cp'=>['p']],
|
||||
'body'=>['c'=>"\100\0\40",'ac'=>"\1",'dd'=>"\0",'b'=>1],
|
||||
'br'=>['c'=>"\5",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1],
|
||||
'button'=>['c'=>"\17\2",'ac'=>"\4",'dd'=>"\10"],
|
||||
'canvas'=>['c'=>"\47",'ac'=>"\0",'dd'=>"\0",'t'=>1],
|
||||
'caption'=>['c'=>"\0\1",'ac'=>"\1",'dd'=>"\0\0\20",'b'=>1],
|
||||
'cite'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
|
||||
'code'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0",'fe'=>1],
|
||||
'col'=>['c'=>"\0\0\200",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1,'b'=>1],
|
||||
'colgroup'=>['c'=>"\0\1",'ac'=>"\0\0\200",'ac23'=>'not(@span)','dd'=>"\0",'nt'=>1,'e'=>1,'e?'=>'@span','b'=>1],
|
||||
'data'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
|
||||
'datalist'=>['c'=>"\5",'ac'=>"\4\0\1\100",'dd'=>"\0"],
|
||||
'dd'=>['c'=>"\0\200\0\4",'ac'=>"\1",'dd'=>"\0",'b'=>1,'cp'=>['dd','dt']],
|
||||
'del'=>['c'=>"\5",'ac'=>"\0",'dd'=>"\0",'t'=>1],
|
||||
'details'=>['c'=>"\113",'ac'=>"\1\0\0\20",'dd'=>"\0",'b'=>1,'cp'=>['p']],
|
||||
'dfn'=>['c'=>"\7\0\0\0\100",'ac'=>"\4",'dd'=>"\0\0\0\0\100"],
|
||||
'dialog'=>['c'=>"\101",'ac'=>"\1",'dd'=>"\0",'b'=>1],
|
||||
'div'=>['c'=>"\3\200",'ac'=>"\1\0\1\4",'ac0'=>'not(ancestor::dl)','dd'=>"\0",'b'=>1,'cp'=>['p']],
|
||||
'dl'=>['c'=>"\3",'c1'=>'dt and dd','ac'=>"\0\200\1",'dd'=>"\0",'nt'=>1,'b'=>1,'cp'=>['p']],
|
||||
'dt'=>['c'=>"\0\200\0\4",'ac'=>"\1",'dd'=>"\200\4\10",'b'=>1,'cp'=>['dd','dt']],
|
||||
'em'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0",'fe'=>1],
|
||||
'embed'=>['c'=>"\57",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1],
|
||||
'fieldset'=>['c'=>"\103\2",'ac'=>"\1\0\0\200",'dd'=>"\0",'b'=>1,'cp'=>['p']],
|
||||
'figcaption'=>['c'=>"\0\0\0\0\0\10",'ac'=>"\1",'dd'=>"\0",'b'=>1,'cp'=>['p']],
|
||||
'figure'=>['c'=>"\103",'ac'=>"\1\0\0\0\0\10",'dd'=>"\0",'b'=>1,'cp'=>['p']],
|
||||
'footer'=>['c'=>"\3\110\10",'ac'=>"\1",'dd'=>"\0\0\0\0\20",'b'=>1,'cp'=>['p']],
|
||||
'form'=>['c'=>"\3\0\0\0\40",'ac'=>"\1",'dd'=>"\0\0\0\0\40",'b'=>1,'cp'=>['p']],
|
||||
'h1'=>['c'=>"\203",'ac'=>"\4",'dd'=>"\0",'b'=>1,'cp'=>['p']],
|
||||
'h2'=>['c'=>"\203",'ac'=>"\4",'dd'=>"\0",'b'=>1,'cp'=>['p']],
|
||||
'h3'=>['c'=>"\203",'ac'=>"\4",'dd'=>"\0",'b'=>1,'cp'=>['p']],
|
||||
'h4'=>['c'=>"\203",'ac'=>"\4",'dd'=>"\0",'b'=>1,'cp'=>['p']],
|
||||
'h5'=>['c'=>"\203",'ac'=>"\4",'dd'=>"\0",'b'=>1,'cp'=>['p']],
|
||||
'h6'=>['c'=>"\203",'ac'=>"\4",'dd'=>"\0",'b'=>1,'cp'=>['p']],
|
||||
'head'=>['c'=>"\0\0\40",'ac'=>"\20",'dd'=>"\0",'nt'=>1,'b'=>1],
|
||||
'header'=>['c'=>"\3\110\10",'ac'=>"\1",'dd'=>"\0\0\0\0\20",'b'=>1,'cp'=>['p']],
|
||||
'hr'=>['c'=>"\1",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1,'b'=>1,'cp'=>['p']],
|
||||
'html'=>['c'=>"\0",'ac'=>"\0\0\40",'dd'=>"\0",'nt'=>1,'b'=>1],
|
||||
'i'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0",'fe'=>1],
|
||||
'iframe'=>['c'=>"\57",'ac'=>"\4",'dd'=>"\0"],
|
||||
'img'=>['c'=>"\57\40\100",'c3'=>'@usemap','ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1],
|
||||
'input'=>['c'=>"\17\40",'c3'=>'@type!="hidden"','c13'=>'@type!="hidden" or @type="hidden"','c1'=>'@type!="hidden"','ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1],
|
||||
'ins'=>['c'=>"\7",'ac'=>"\0",'dd'=>"\0",'t'=>1],
|
||||
'kbd'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
|
||||
'label'=>['c'=>"\17\40\0\0\10",'ac'=>"\4",'dd'=>"\0\0\2\0\10"],
|
||||
'legend'=>['c'=>"\0\0\0\200",'ac'=>"\204",'dd'=>"\0",'b'=>1],
|
||||
'li'=>['c'=>"\0\0\0\0\0\1",'ac'=>"\1",'dd'=>"\0",'b'=>1,'cp'=>['li']],
|
||||
'link'=>['c'=>"\25",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1],
|
||||
'main'=>['c'=>"\3\110\20\0\20",'ac'=>"\1",'dd'=>"\0",'b'=>1,'cp'=>['p']],
|
||||
'mark'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
|
||||
'media element'=>['c'=>"\0\0\0\0\0\4",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'b'=>1],
|
||||
'meta'=>['c'=>"\20",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1,'b'=>1],
|
||||
'meter'=>['c'=>"\7\0\2\0\4",'ac'=>"\4",'dd'=>"\0\0\0\0\4"],
|
||||
'nav'=>['c'=>"\3\4",'ac'=>"\1",'dd'=>"\0\0\0\0\20",'b'=>1,'cp'=>['p']],
|
||||
'noscript'=>['c'=>"\25",'ac'=>"\0",'dd'=>"\0",'nt'=>1],
|
||||
'object'=>['c'=>"\47",'ac'=>"\0\0\0\0\2",'dd'=>"\0",'t'=>1],
|
||||
'ol'=>['c'=>"\3",'c1'=>'li','ac'=>"\0\0\1\0\0\1",'dd'=>"\0",'nt'=>1,'b'=>1,'cp'=>['p']],
|
||||
'optgroup'=>['c'=>"\0\0\4",'ac'=>"\0\0\1\100",'dd'=>"\0",'nt'=>1,'b'=>1,'cp'=>['optgroup','option']],
|
||||
'option'=>['c'=>"\0\0\4\100",'ac'=>"\0",'dd'=>"\0",'b'=>1,'cp'=>['option']],
|
||||
'output'=>['c'=>"\7\2",'ac'=>"\4",'dd'=>"\0"],
|
||||
'p'=>['c'=>"\3",'ac'=>"\4",'dd'=>"\0",'b'=>1,'cp'=>['p']],
|
||||
'param'=>['c'=>"\0\0\0\0\2",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1,'b'=>1],
|
||||
'picture'=>['c'=>"\45",'ac'=>"\0\0\101",'dd'=>"\0",'nt'=>1],
|
||||
'pre'=>['c'=>"\3",'ac'=>"\4",'dd'=>"\0",'pre'=>1,'b'=>1,'cp'=>['p']],
|
||||
'progress'=>['c'=>"\7\0\2\10",'ac'=>"\4",'dd'=>"\0\0\0\10"],
|
||||
'q'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
|
||||
'rb'=>['c'=>"\0\20",'ac'=>"\4",'dd'=>"\0",'b'=>1,'cp'=>['rb','rt','rtc']],
|
||||
'rp'=>['c'=>"\0\20\0\2",'ac'=>"\4",'dd'=>"\0",'b'=>1],
|
||||
'rt'=>['c'=>"\0\20\0\2",'ac'=>"\4",'dd'=>"\0",'b'=>1,'cp'=>['rb','rt']],
|
||||
'rtc'=>['c'=>"\0\20",'ac'=>"\4\0\0\2",'dd'=>"\0",'b'=>1,'cp'=>['rt','rtc']],
|
||||
'ruby'=>['c'=>"\7",'ac'=>"\4\20",'dd'=>"\0"],
|
||||
's'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0",'fe'=>1],
|
||||
'samp'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
|
||||
'script'=>['c'=>"\25\0\1",'ac'=>"\0",'dd'=>"\0",'to'=>1],
|
||||
'section'=>['c'=>"\3\4",'ac'=>"\1",'dd'=>"\0",'b'=>1,'cp'=>['p']],
|
||||
'select'=>['c'=>"\17\2",'ac'=>"\0\0\5",'dd'=>"\0",'nt'=>1],
|
||||
'slot'=>['c'=>"\5",'ac'=>"\0",'dd'=>"\0",'t'=>1],
|
||||
'small'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0",'fe'=>1],
|
||||
'source'=>['c'=>"\0\0\100\40",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1,'b'=>1],
|
||||
'span'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
|
||||
'strong'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0",'fe'=>1],
|
||||
'style'=>['c'=>"\21",'ac'=>"\0",'dd'=>"\0",'to'=>1,'b'=>1],
|
||||
'sub'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
|
||||
'summary'=>['c'=>"\0\0\0\20",'ac'=>"\204",'dd'=>"\0",'b'=>1],
|
||||
'sup'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
|
||||
'table'=>['c'=>"\3\0\20",'ac'=>"\0\1\1",'dd'=>"\0",'nt'=>1,'b'=>1,'cp'=>['p']],
|
||||
'tbody'=>['c'=>"\0\1",'ac'=>"\0\0\1\0\200",'dd'=>"\0",'nt'=>1,'b'=>1,'cp'=>['tbody','td','th','thead','tr']],
|
||||
'td'=>['c'=>"\100\0\0\1",'ac'=>"\1",'dd'=>"\0\0\0\0\20",'b'=>1,'cp'=>['td','th']],
|
||||
'template'=>['c'=>"\25\0\201",'ac'=>"\0",'dd'=>"\0",'nt'=>1],
|
||||
'textarea'=>['c'=>"\17\2",'ac'=>"\0",'dd'=>"\0",'pre'=>1,'to'=>1],
|
||||
'tfoot'=>['c'=>"\0\1",'ac'=>"\0\0\1\0\200",'dd'=>"\0",'nt'=>1,'b'=>1,'cp'=>['tbody','td','th','thead','tr']],
|
||||
'th'=>['c'=>"\0\0\0\1",'ac'=>"\1",'dd'=>"\200\104",'b'=>1,'cp'=>['td','th']],
|
||||
'thead'=>['c'=>"\0\1",'ac'=>"\0\0\1\0\200",'dd'=>"\0",'nt'=>1,'b'=>1],
|
||||
'time'=>['c'=>"\7",'ac'=>"\4",'ac2'=>'@datetime','dd'=>"\0"],
|
||||
'title'=>['c'=>"\20",'ac'=>"\0",'dd'=>"\0",'to'=>1,'b'=>1],
|
||||
'tr'=>['c'=>"\0\1\0\0\200",'ac'=>"\0\0\1\1",'dd'=>"\0",'nt'=>1,'b'=>1,'cp'=>['td','th','tr']],
|
||||
'track'=>['c'=>"\0\0\0\0\1",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1,'b'=>1],
|
||||
'u'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0",'fe'=>1],
|
||||
'ul'=>['c'=>"\3",'c1'=>'li','ac'=>"\0\0\1\0\0\1",'dd'=>"\0",'nt'=>1,'b'=>1,'cp'=>['p']],
|
||||
'var'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
|
||||
'video'=>['c'=>"\57",'c3'=>'@controls','ac'=>"\0\0\0\40\1",'ac29'=>'not(@src)','dd'=>"\0\0\0\0\0\4",'dd42'=>'@src','t'=>1],
|
||||
'wbr'=>['c'=>"\5",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1]
|
||||
];
|
||||
|
||||
/**
|
||||
* Test whether given child element closes given parent element
|
||||
*
|
||||
* @param DOMElement $child
|
||||
* @param DOMElement $parent
|
||||
* @return bool
|
||||
*/
|
||||
public static function closesParent(DOMElement $child, DOMElement $parent)
|
||||
{
|
||||
$parentName = $parent->nodeName;
|
||||
$childName = $child->nodeName;
|
||||
|
||||
return !empty(self::$htmlElements[$childName]['cp']) && in_array($parentName, self::$htmlElements[$childName]['cp'], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether given element disallows text nodes
|
||||
*
|
||||
* @param DOMElement $element
|
||||
* @return bool
|
||||
*/
|
||||
public static function disallowsText(DOMElement $element)
|
||||
{
|
||||
return self::hasProperty($element, 'nt');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the "allowChild" bitfield for given element
|
||||
*
|
||||
* @param DOMElement $element
|
||||
* @return string
|
||||
*/
|
||||
public static function getAllowChildBitfield(DOMElement $element)
|
||||
{
|
||||
return self::getBitfield($element, 'ac');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the "category" bitfield for given element
|
||||
*
|
||||
* @param DOMElement $element
|
||||
* @return string
|
||||
*/
|
||||
public static function getCategoryBitfield(DOMElement $element)
|
||||
{
|
||||
return self::getBitfield($element, 'c');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the "denyDescendant" bitfield for given element
|
||||
*
|
||||
* @param DOMElement $element
|
||||
* @return string
|
||||
*/
|
||||
public static function getDenyDescendantBitfield(DOMElement $element)
|
||||
{
|
||||
return self::getBitfield($element, 'dd');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether given element is a block element
|
||||
*
|
||||
* @param DOMElement $element
|
||||
* @return bool
|
||||
*/
|
||||
public static function isBlock(DOMElement $element)
|
||||
{
|
||||
return self::hasProperty($element, 'b');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether given element uses the empty content model
|
||||
*
|
||||
* @param DOMElement $element
|
||||
* @return bool
|
||||
*/
|
||||
public static function isEmpty(DOMElement $element)
|
||||
{
|
||||
return self::hasProperty($element, 'e');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether given element is a formatting element
|
||||
*
|
||||
* @param DOMElement $element
|
||||
* @return bool
|
||||
*/
|
||||
public static function isFormattingElement(DOMElement $element)
|
||||
{
|
||||
return self::hasProperty($element, 'fe');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether given element only accepts text nodes
|
||||
*
|
||||
* @param DOMElement $element
|
||||
* @return bool
|
||||
*/
|
||||
public static function isTextOnly(DOMElement $element)
|
||||
{
|
||||
return self::hasProperty($element, 'to');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether given element uses the transparent content model
|
||||
*
|
||||
* @param DOMElement $element
|
||||
* @return bool
|
||||
*/
|
||||
public static function isTransparent(DOMElement $element)
|
||||
{
|
||||
return self::hasProperty($element, 't');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether given element uses the void content model
|
||||
*
|
||||
* @param DOMElement $element
|
||||
* @return bool
|
||||
*/
|
||||
public static function isVoid(DOMElement $element)
|
||||
{
|
||||
return self::hasProperty($element, 'v');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether given element preserves whitespace in its content
|
||||
*
|
||||
* @param DOMElement $element
|
||||
* @return bool
|
||||
*/
|
||||
public static function preservesWhitespace(DOMElement $element)
|
||||
{
|
||||
return self::hasProperty($element, 'pre');
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate an XPath query using given element as context node
|
||||
*
|
||||
* @param string $query XPath query
|
||||
* @param DOMElement $element Context node
|
||||
* @return bool
|
||||
*/
|
||||
protected static function evaluate($query, DOMElement $element)
|
||||
{
|
||||
$xpath = new DOMXPath($element->ownerDocument);
|
||||
|
||||
return $xpath->evaluate('boolean(' . $query . ')', $element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bitfield value for a given element
|
||||
*
|
||||
* @param DOMElement $element Context node
|
||||
* @param string $name Bitfield name: either 'c', 'ac' or 'dd'
|
||||
* @return string
|
||||
*/
|
||||
protected static function getBitfield(DOMElement $element, $name)
|
||||
{
|
||||
$props = self::getProperties($element);
|
||||
$bitfield = self::toBin($props[$name]);
|
||||
|
||||
// For each bit set to 1, test whether there is an XPath condition to it and whether it is
|
||||
// fulfilled. If not, turn the bit to 0
|
||||
foreach (array_keys(array_filter(str_split($bitfield, 1))) as $bitNumber)
|
||||
{
|
||||
$conditionName = $name . $bitNumber;
|
||||
if (isset($props[$conditionName]) && !self::evaluate($props[$conditionName], $element))
|
||||
{
|
||||
$bitfield[$bitNumber] = '0';
|
||||
}
|
||||
}
|
||||
|
||||
return self::toRaw($bitfield);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the properties associated with given element
|
||||
*
|
||||
* Returns span's properties if the element is not defined
|
||||
*
|
||||
* @param DOMElement $element
|
||||
* @return array
|
||||
*/
|
||||
protected static function getProperties(DOMElement $element)
|
||||
{
|
||||
return (isset(self::$htmlElements[$element->nodeName])) ? self::$htmlElements[$element->nodeName] : self::$htmlElements['span'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether given element has given property in context
|
||||
*
|
||||
* @param DOMElement $element Context node
|
||||
* @param string $propName Property name, see self::$htmlElements
|
||||
* @return bool
|
||||
*/
|
||||
protected static function hasProperty(DOMElement $element, $propName)
|
||||
{
|
||||
$props = self::getProperties($element);
|
||||
|
||||
return !empty($props[$propName]) && (!isset($props[$propName . '?']) || self::evaluate($props[$propName . '?'], $element));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a raw string to a series of 0 and 1 in LSB order
|
||||
*
|
||||
* @param string $raw
|
||||
* @return string
|
||||
*/
|
||||
protected static function toBin($raw)
|
||||
{
|
||||
$bin = '';
|
||||
foreach (str_split($raw, 1) as $char)
|
||||
{
|
||||
$bin .= strrev(substr('0000000' . decbin(ord($char)), -8));
|
||||
}
|
||||
|
||||
return $bin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a series of 0 and 1 in LSB order to a raw string
|
||||
*
|
||||
* @param string $bin
|
||||
* @return string
|
||||
*/
|
||||
protected static function toRaw($bin)
|
||||
{
|
||||
return implode('', array_map('chr', array_map('bindec', array_map('strrev', str_split($bin, 8)))));
|
||||
}
|
||||
}
|
||||
70
vendor/s9e/text-formatter/src/Configurator/Helpers/FilterHelper.php
vendored
Normal file
70
vendor/s9e/text-formatter/src/Configurator/Helpers/FilterHelper.php
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @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 InvalidArgumentException;
|
||||
use s9e\TextFormatter\Configurator\Items\Filter;
|
||||
use s9e\TextFormatter\Configurator\RecursiveParser;
|
||||
|
||||
abstract class FilterHelper
|
||||
{
|
||||
/**
|
||||
* @var RecursiveParser
|
||||
*/
|
||||
protected static $parser;
|
||||
|
||||
/**
|
||||
* Return the cached instance of RecursiveParser
|
||||
*
|
||||
* @return RecursiveParser
|
||||
*/
|
||||
public static function getParser(): RecursiveParser
|
||||
{
|
||||
if (!isset(self::$parser))
|
||||
{
|
||||
self::$parser = new RecursiveParser;
|
||||
self::$parser->setMatchers([new FilterSyntaxMatcher(self::$parser)]);
|
||||
}
|
||||
|
||||
return self::$parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether given filter is a default filter or is in the list of allowed filters
|
||||
*
|
||||
* @param string $filter
|
||||
* @param string[] $allowedFilters
|
||||
* @return bool
|
||||
*/
|
||||
public static function isAllowed(string $filter, array $allowedFilters): bool
|
||||
{
|
||||
if (substr($filter, 0, 1) === '#')
|
||||
{
|
||||
// Default filters are always allowed
|
||||
return true;
|
||||
}
|
||||
$filter = trim(preg_replace('(^\\\\|\\(.*)s', '', $filter));
|
||||
|
||||
return in_array($filter, $allowedFilters, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a filter definition
|
||||
*
|
||||
* @param string $filterString Filter definition such as "#number" or "strtolower($attrValue)"
|
||||
* @return array Associative array with a "filter" element and optionally a
|
||||
* "params" array
|
||||
*/
|
||||
public static function parse(string $filterString): array
|
||||
{
|
||||
$filterConfig = self::getParser()->parse($filterString)['value'];
|
||||
$filterConfig['filter'] = ltrim($filterConfig['filter'], '\\');
|
||||
|
||||
return $filterConfig;
|
||||
}
|
||||
}
|
||||
248
vendor/s9e/text-formatter/src/Configurator/Helpers/FilterSyntaxMatcher.php
vendored
Normal file
248
vendor/s9e/text-formatter/src/Configurator/Helpers/FilterSyntaxMatcher.php
vendored
Normal file
@@ -0,0 +1,248 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @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 s9e\TextFormatter\Configurator\Items\Regexp;
|
||||
use s9e\TextFormatter\Configurator\RecursiveParser\AbstractRecursiveMatcher;
|
||||
|
||||
class FilterSyntaxMatcher extends AbstractRecursiveMatcher
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMatchers(): array
|
||||
{
|
||||
return [
|
||||
'Array' => [
|
||||
'groups' => ['FilterArg', 'Literal'],
|
||||
'regexp' => '\\[ ((?&ArrayElements))? \\]',
|
||||
],
|
||||
'ArrayElement' => [
|
||||
'regexp' => '(?:((?&Scalar)) => )?((?&Literal))',
|
||||
],
|
||||
'ArrayElements' => [
|
||||
'regexp' => '((?&ArrayElement))(?: , ((?&ArrayElements)))?',
|
||||
],
|
||||
'DoubleQuotedString' => [
|
||||
'groups' => ['FilterArg', 'Literal', 'Scalar'],
|
||||
'regexp' => '"((?:[^\\\\"]|\\\\.)*)"',
|
||||
],
|
||||
'False' => [
|
||||
'groups' => ['FilterArg', 'Literal', 'Scalar'],
|
||||
'regexp' => '[Ff][Aa][Ll][Ss][Ee]',
|
||||
],
|
||||
'FilterArgs' => [
|
||||
'regexp' => '((?&FilterArg))(?: , ((?&FilterArgs)))?',
|
||||
],
|
||||
'FilterCallback' => [
|
||||
'regexp' => '([#:\\\\\\w]+)(?: \\( ((?&FilterArgs)?) \\))?',
|
||||
],
|
||||
'Float' => [
|
||||
'groups' => ['FilterArg', 'Literal', 'Scalar'],
|
||||
'regexp' => '([-+]?(?:\\.[0-9]+|[0-9]+\\.[0-9]*|[0-9]+(?=[Ee]))(?:[Ee]-?[0-9]+)?)',
|
||||
],
|
||||
'Integer' => [
|
||||
'groups' => ['FilterArg', 'Literal', 'Scalar'],
|
||||
'regexp' => '(-?(?:0[Bb][01]+|0[Xx][0-9A-Fa-f]+|[0-9]+))',
|
||||
],
|
||||
'Null' => [
|
||||
'groups' => ['FilterArg', 'Literal', 'Scalar'],
|
||||
'regexp' => '[Nn][Uu][Ll][Ll]',
|
||||
],
|
||||
'Param' => [
|
||||
'groups' => ['FilterArg'],
|
||||
'regexp' => '\\$(\\w+(?:\\.\\w+)*)',
|
||||
],
|
||||
'Regexp' => [
|
||||
'groups' => ['FilterArg', 'Literal'],
|
||||
'regexp' => '(/(?:[^\\\\/]|\\\\.)*/)([Sgimsu]*)',
|
||||
],
|
||||
'SingleQuotedString' => [
|
||||
'groups' => ['FilterArg', 'Literal', 'Scalar'],
|
||||
'regexp' => "'((?:[^\\\\']|\\\\.)*)'",
|
||||
],
|
||||
'True' => [
|
||||
'groups' => ['FilterArg', 'Literal', 'Scalar'],
|
||||
'regexp' => '[Tt][Rr][Uu][Ee]'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $elements
|
||||
* @return array
|
||||
*/
|
||||
public function parseArray(string $elements = ''): array
|
||||
{
|
||||
$array = [];
|
||||
if ($elements !== '')
|
||||
{
|
||||
foreach ($this->recurse($elements, 'ArrayElements') as $element)
|
||||
{
|
||||
if (array_key_exists('key', $element))
|
||||
{
|
||||
$array[$element['key']] = $element['value'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$array[] = $element['value'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
* @return array
|
||||
*/
|
||||
public function parseArrayElement(string $key, string $value): array
|
||||
{
|
||||
$element = ['value' => $this->recurse($value, 'Literal')];
|
||||
if ($key !== '')
|
||||
{
|
||||
$element['key'] = $this->recurse($key, 'Scalar');
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $firstElement
|
||||
* @param string $otherElements
|
||||
* @return array
|
||||
*/
|
||||
public function parseArrayElements(string $firstElement, string $otherElements = null)
|
||||
{
|
||||
$elements = [$this->recurse($firstElement, 'ArrayElement')];
|
||||
if (isset($otherElements))
|
||||
{
|
||||
$elements = array_merge($elements, $this->recurse($otherElements, 'ArrayElements'));
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $str
|
||||
* @return string
|
||||
*/
|
||||
public function parseDoubleQuotedString(string $str): string
|
||||
{
|
||||
return stripcslashes($str);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function parseFalse(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $callback
|
||||
* @param string $args
|
||||
* @return array
|
||||
*/
|
||||
public function parseFilterCallback(string $callback, string $args = null): array
|
||||
{
|
||||
$config = ['filter' => $callback];
|
||||
if (isset($args))
|
||||
{
|
||||
$config['params'] = ($args === '') ? [] : $this->recurse($args, 'FilterArgs');
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $firstArg
|
||||
* @param string $otherArgs
|
||||
* @return array
|
||||
*/
|
||||
public function parseFilterArgs(string $firstArg, string $otherArgs = null)
|
||||
{
|
||||
$parsedArg = $this->parser->parse($firstArg, 'FilterArg');
|
||||
|
||||
$type = ($parsedArg['match'] === 'Param') ? 'Name' : 'Value';
|
||||
$args = [[$type, $parsedArg['value']]];
|
||||
if (isset($otherArgs))
|
||||
{
|
||||
$args = array_merge($args, $this->recurse($otherArgs, 'FilterArgs'));
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null
|
||||
*/
|
||||
public function parseNull()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $str
|
||||
* @return float
|
||||
*/
|
||||
public function parseFloat(string $str): float
|
||||
{
|
||||
return (float) $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $str
|
||||
* @return integer
|
||||
*/
|
||||
public function parseInteger(string $str): int
|
||||
{
|
||||
return intval($str, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $str
|
||||
* @return string
|
||||
*/
|
||||
public function parseParam(string $str): string
|
||||
{
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $regexp
|
||||
* @param string $flags
|
||||
* @return Regexp
|
||||
*/
|
||||
public function parseRegexp(string $regexp, string $flags): Regexp
|
||||
{
|
||||
$regexp .= str_replace('g', '', $flags);
|
||||
|
||||
return new Regexp($regexp, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $str
|
||||
* @return string
|
||||
*/
|
||||
public function parseSingleQuotedString(string $str): string
|
||||
{
|
||||
return preg_replace("(\\\\([\\\\']))", '$1', $str);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function parseTrue(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
205
vendor/s9e/text-formatter/src/Configurator/Helpers/NodeLocator.php
vendored
Normal file
205
vendor/s9e/text-formatter/src/Configurator/Helpers/NodeLocator.php
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
<?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 DOMDocument;
|
||||
use DOMXPath;
|
||||
|
||||
abstract class NodeLocator
|
||||
{
|
||||
/**
|
||||
* Return all attributes (literal or generated) that match given regexp
|
||||
*
|
||||
* @param DOMDocument $dom Document
|
||||
* @param string $regexp Regexp
|
||||
* @return DOMNode[] List of DOMNode instances
|
||||
*/
|
||||
public static function getAttributesByRegexp(DOMDocument $dom, $regexp)
|
||||
{
|
||||
return self::getNodesByRegexp($dom, $regexp, 'attribute');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all DOMNodes whose content is CSS
|
||||
*
|
||||
* @param DOMDocument $dom Document
|
||||
* @return DOMNode[] List of DOMNode instances
|
||||
*/
|
||||
public static function getCSSNodes(DOMDocument $dom)
|
||||
{
|
||||
$regexp = '/^(?:color|style)$/i';
|
||||
$nodes = array_merge(
|
||||
self::getAttributesByRegexp($dom, $regexp),
|
||||
self::getElementsByRegexp($dom, '/^style$/i')
|
||||
);
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all elements (literal or generated) that match given regexp
|
||||
*
|
||||
* @param DOMDocument $dom Document
|
||||
* @param string $regexp Regexp
|
||||
* @return DOMNode[] List of DOMNode instances
|
||||
*/
|
||||
public static function getElementsByRegexp(DOMDocument $dom, $regexp)
|
||||
{
|
||||
return self::getNodesByRegexp($dom, $regexp, 'element');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all DOMNodes whose content is JavaScript
|
||||
*
|
||||
* @param DOMDocument $dom Document
|
||||
* @return DOMNode[] List of DOMNode instances
|
||||
*/
|
||||
public static function getJSNodes(DOMDocument $dom)
|
||||
{
|
||||
$regexp = '/^(?:data-s9e-livepreview-)?on/i';
|
||||
$nodes = array_merge(
|
||||
self::getAttributesByRegexp($dom, $regexp),
|
||||
self::getElementsByRegexp($dom, '/^script$/i')
|
||||
);
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all elements (literal or generated) that match given regexp
|
||||
*
|
||||
* Will return all <param/> descendants of <object/> and all attributes of <embed/> whose name
|
||||
* matches given regexp. This method will NOT catch <param/> elements whose 'name' attribute is
|
||||
* set via an <xsl:attribute/>
|
||||
*
|
||||
* @param DOMDocument $dom Document
|
||||
* @param string $regexp
|
||||
* @return DOMNode[] List of DOMNode instances
|
||||
*/
|
||||
public static function getObjectParamsByRegexp(DOMDocument $dom, $regexp)
|
||||
{
|
||||
$xpath = new DOMXPath($dom);
|
||||
$nodes = [];
|
||||
|
||||
// Collect attributes from <embed/> elements
|
||||
foreach (self::getAttributesByRegexp($dom, $regexp) as $attribute)
|
||||
{
|
||||
if ($attribute->nodeType === XML_ATTRIBUTE_NODE)
|
||||
{
|
||||
if (strtolower($attribute->parentNode->localName) === 'embed')
|
||||
{
|
||||
$nodes[] = $attribute;
|
||||
}
|
||||
}
|
||||
elseif ($xpath->evaluate('count(ancestor::embed)', $attribute))
|
||||
{
|
||||
// Assuming <xsl:attribute/> or <xsl:copy-of/>
|
||||
$nodes[] = $attribute;
|
||||
}
|
||||
}
|
||||
|
||||
// Collect <param/> descendants of <object/> elements
|
||||
foreach ($xpath->query('//object//param') as $param)
|
||||
{
|
||||
if (preg_match($regexp, $param->getAttribute('name')))
|
||||
{
|
||||
$nodes[] = $param;
|
||||
}
|
||||
}
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all DOMNodes whose content is an URL
|
||||
*
|
||||
* NOTE: it will also return HTML4 nodes whose content is an URI
|
||||
*
|
||||
* @param DOMDocument $dom Document
|
||||
* @return DOMNode[] List of DOMNode instances
|
||||
*/
|
||||
public static function getURLNodes(DOMDocument $dom)
|
||||
{
|
||||
$regexp = '/(?:^(?:action|background|c(?:ite|lassid|odebase)|data|formaction|href|i(?:con|tem(?:id|prop|type))|longdesc|manifest|p(?:ing|luginspage|oster|rofile)|usemap)|src)$/i';
|
||||
$nodes = self::getAttributesByRegexp($dom, $regexp);
|
||||
|
||||
/**
|
||||
* @link http://helpx.adobe.com/flash/kb/object-tag-syntax-flash-professional.html
|
||||
* @link http://www.sitepoint.com/control-internet-explorer/
|
||||
*/
|
||||
foreach (self::getObjectParamsByRegexp($dom, '/^(?:dataurl|movie)$/i') as $param)
|
||||
{
|
||||
$node = $param->getAttributeNode('value');
|
||||
if ($node)
|
||||
{
|
||||
$nodes[] = $node;
|
||||
}
|
||||
}
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all nodes of given type
|
||||
*
|
||||
* @param DOMDocument $dom Owner document
|
||||
* @param string $type Node type ('element' or 'attribute')
|
||||
* @return DOMNode[] List of DOMNode instances
|
||||
*/
|
||||
protected static function getNodes(DOMDocument $dom, $type)
|
||||
{
|
||||
$nodes = [];
|
||||
$prefix = ($type === 'attribute') ? '@' : '';
|
||||
$xpath = new DOMXPath($dom);
|
||||
|
||||
// Get natural nodes
|
||||
foreach ($xpath->query('//' . $prefix . '*') as $node)
|
||||
{
|
||||
$nodes[] = [$node, $node->nodeName];
|
||||
}
|
||||
|
||||
// Get XSL-generated nodes
|
||||
foreach ($xpath->query('//xsl:' . $type) as $node)
|
||||
{
|
||||
$nodes[] = [$node, $node->getAttribute('name')];
|
||||
}
|
||||
|
||||
// Get xsl:copy-of nodes
|
||||
foreach ($xpath->query('//xsl:copy-of') as $node)
|
||||
{
|
||||
if (preg_match('/^' . $prefix . '(\\w+)$/', $node->getAttribute('select'), $m))
|
||||
{
|
||||
$nodes[] = [$node, $m[1]];
|
||||
}
|
||||
}
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all nodes (literal or generated) that match given regexp
|
||||
*
|
||||
* @param DOMDocument $dom Owner document
|
||||
* @param string $regexp Regexp
|
||||
* @param string $type Node type ('element' or 'attribute')
|
||||
* @return DOMNode[] List of DOMNode instances
|
||||
*/
|
||||
protected static function getNodesByRegexp(DOMDocument $dom, $regexp, $type)
|
||||
{
|
||||
$nodes = [];
|
||||
foreach (self::getNodes($dom, $type) as list($node, $name))
|
||||
{
|
||||
if (preg_match($regexp, $name))
|
||||
{
|
||||
$nodes[] = $node;
|
||||
}
|
||||
}
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
}
|
||||
49
vendor/s9e/text-formatter/src/Configurator/Helpers/RegexpBuilder.php
vendored
Normal file
49
vendor/s9e/text-formatter/src/Configurator/Helpers/RegexpBuilder.php
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
<?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 s9e\RegexpBuilder\Builder;
|
||||
|
||||
abstract class RegexpBuilder
|
||||
{
|
||||
/**
|
||||
* Create a regexp pattern that matches a list of words
|
||||
*
|
||||
* @param array $words Words to sort (must be UTF-8)
|
||||
* @param array $options
|
||||
* @return string
|
||||
*/
|
||||
public static function fromList(array $words, array $options = [])
|
||||
{
|
||||
$options += [
|
||||
'delimiter' => '/',
|
||||
'caseInsensitive' => false,
|
||||
'specialChars' => [],
|
||||
'unicode' => true
|
||||
];
|
||||
|
||||
// Normalize ASCII if the regexp is meant to be case-insensitive
|
||||
if ($options['caseInsensitive'])
|
||||
{
|
||||
foreach ($words as &$word)
|
||||
{
|
||||
$word = strtr($word, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
|
||||
}
|
||||
unset($word);
|
||||
}
|
||||
|
||||
$builder = new Builder([
|
||||
'delimiter' => $options['delimiter'],
|
||||
'meta' => $options['specialChars'],
|
||||
'input' => $options['unicode'] ? 'Utf8' : 'Bytes',
|
||||
'output' => $options['unicode'] ? 'Utf8' : 'Bytes'
|
||||
]);
|
||||
|
||||
return $builder->build($words);
|
||||
}
|
||||
}
|
||||
@@ -1,232 +1,393 @@
|
||||
<?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
|
||||
{
|
||||
/**
|
||||
* Generate a regexp that matches any single character allowed in a regexp
|
||||
*
|
||||
* This method will generate a regexp that can be used to determine whether a given character
|
||||
* could in theory be allowed in a string that matches the source regexp. For example, the source
|
||||
* regexp /^a+$/D would generate /a/ while /^foo\d+$/D would generate /[fo\d]/ whereas the regexp
|
||||
* /foo/ would generate // because it's not anchored so any characters could be found before or
|
||||
* after the literal "foo".
|
||||
*
|
||||
* @param string $regexp Source regexp
|
||||
* @return string Regexp that matches any single character allowed in the source regexp
|
||||
*/
|
||||
public static function getAllowedCharacterRegexp($regexp)
|
||||
{
|
||||
$def = self::parse($regexp);
|
||||
if (\strpos($def['modifiers'], 'm') !== \false)
|
||||
|
||||
// If the regexp is uses the multiline modifier, this regexp can't match the whole string if
|
||||
// it contains newlines, so in effect it could allow any content
|
||||
if (strpos($def['modifiers'], 'm') !== false)
|
||||
{
|
||||
return '//';
|
||||
if (\substr($def['regexp'], 0, 1) !== '^'
|
||||
|| \substr($def['regexp'], -1) !== '$')
|
||||
}
|
||||
|
||||
if (substr($def['regexp'], 0, 1) !== '^'
|
||||
|| substr($def['regexp'], -1) !== '$')
|
||||
{
|
||||
return '//';
|
||||
}
|
||||
|
||||
// Append a token to mark the end of the regexp
|
||||
$def['tokens'][] = [
|
||||
'pos' => \strlen($def['regexp']),
|
||||
'pos' => strlen($def['regexp']),
|
||||
'len' => 0,
|
||||
'type' => 'end'
|
||||
];
|
||||
|
||||
$patterns = [];
|
||||
|
||||
// Collect the literal portions of the source regexp while testing for alternations
|
||||
$literal = '';
|
||||
$pos = 0;
|
||||
$skipPos = 0;
|
||||
$depth = 0;
|
||||
foreach ($def['tokens'] as $token)
|
||||
{
|
||||
// Skip options
|
||||
if ($token['type'] === 'option')
|
||||
$skipPos = \max($skipPos, $token['pos'] + $token['len']);
|
||||
if (\strpos($token['type'], 'AssertionStart') !== \false)
|
||||
{
|
||||
$skipPos = max($skipPos, $token['pos'] + $token['len']);
|
||||
}
|
||||
|
||||
// Skip assertions
|
||||
if (strpos($token['type'], 'AssertionStart') !== false)
|
||||
{
|
||||
$endToken = $def['tokens'][$token['endToken']];
|
||||
$skipPos = \max($skipPos, $endToken['pos'] + $endToken['len']);
|
||||
$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);
|
||||
// Capture the content between last position and current position
|
||||
$tmp = substr($def['regexp'], $pos, $token['pos'] - $pos);
|
||||
|
||||
// Append the content to the literal portion
|
||||
$literal .= $tmp;
|
||||
|
||||
// Test for alternations if it's the root of the regexp
|
||||
if (!$depth)
|
||||
{
|
||||
$tmp = \str_replace('\\\\', '', $tmp);
|
||||
if (\preg_match('/(?<!\\\\)\\|(?!\\^)/', $tmp))
|
||||
// Remove literal backslashes for convenience
|
||||
$tmp = str_replace('\\\\', '', $tmp);
|
||||
|
||||
// Look for an unescaped | that is not followed by ^
|
||||
if (preg_match('/(?<!\\\\)\\|(?!\\^)/', $tmp))
|
||||
{
|
||||
return '//';
|
||||
if (\preg_match('/(?<![$\\\\])\\|/', $tmp))
|
||||
}
|
||||
|
||||
// Look for an unescaped | that is not preceded by $
|
||||
if (preg_match('/(?<![$\\\\])\\|/', $tmp))
|
||||
{
|
||||
return '//';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (\substr($token['type'], -5) === 'Start')
|
||||
|
||||
if (substr($token['type'], -5) === 'Start')
|
||||
{
|
||||
++$depth;
|
||||
elseif (\substr($token['type'], -3) === 'End')
|
||||
}
|
||||
elseif (substr($token['type'], -3) === 'End')
|
||||
{
|
||||
--$depth;
|
||||
$pos = \max($skipPos, $token['pos'] + $token['len']);
|
||||
}
|
||||
|
||||
$pos = max($skipPos, $token['pos'] + $token['len']);
|
||||
}
|
||||
if (\preg_match('#(?<!\\\\)(?:\\\\\\\\)*\\.#', $literal))
|
||||
|
||||
// Test for the presence of an unescaped dot
|
||||
if (preg_match('#(?<!\\\\)(?:\\\\\\\\)*\\.#', $literal))
|
||||
{
|
||||
if (\strpos($def['modifiers'], 's') !== \false
|
||||
|| \strpos($literal, "\n") !== \false)
|
||||
if (strpos($def['modifiers'], 's') !== false
|
||||
|| strpos($literal, "\n") !== false)
|
||||
{
|
||||
return '//';
|
||||
}
|
||||
|
||||
$patterns[] = '.';
|
||||
$literal = \preg_replace('#(?<!\\\\)((?:\\\\\\\\)*)\\.#', '$1', $literal);
|
||||
|
||||
// Remove unescaped dots
|
||||
$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)
|
||||
|
||||
// Remove unescaped quantifiers *, + and ?
|
||||
$literal = preg_replace('#(?<!\\\\)((?:\\\\\\\\)*)[*+?]#', '$1', $literal);
|
||||
|
||||
// Remove unescaped quantifiers {}
|
||||
$literal = preg_replace('#(?<!\\\\)((?:\\\\\\\\)*)\\{[^}]+\\}#', '$1', $literal);
|
||||
|
||||
// Remove backslash assertions \b, \B, \A, \Z, \z and \G, as well as back references
|
||||
$literal = preg_replace('#(?<!\\\\)((?:\\\\\\\\)*)\\\\[bBAZzG1-9]#', '$1', $literal);
|
||||
|
||||
// Remove unescaped ^, | and $
|
||||
$literal = preg_replace('#(?<!\\\\)((?:\\\\\\\\)*)[$^|]#', '$1', $literal);
|
||||
|
||||
// Escape unescaped - and ] so they are safe to use in a character class
|
||||
$literal = preg_replace('#(?<!\\\\)((?:\\\\\\\\)*)([-^\\]])#', '$1\\\\$2', $literal);
|
||||
|
||||
// If the regexp doesn't use PCRE_DOLLAR_ENDONLY, it could end with a \n
|
||||
if (strpos($def['modifiers'], 'D') === false)
|
||||
{
|
||||
$literal .= "\n";
|
||||
}
|
||||
|
||||
// Add the literal portion of the regexp to the patterns, as a character class
|
||||
if ($literal !== '')
|
||||
{
|
||||
$patterns[] = '[' . $literal . ']';
|
||||
}
|
||||
|
||||
// Test whether this regexp actually matches anything
|
||||
if (empty($patterns))
|
||||
{
|
||||
return '/^$/D';
|
||||
$regexp = $def['delimiter'] . \implode('|', $patterns) . $def['delimiter'];
|
||||
if (\strpos($def['modifiers'], 'i') !== \false)
|
||||
}
|
||||
|
||||
// Build the allowed characters regexp
|
||||
$regexp = $def['delimiter'] . implode('|', $patterns) . $def['delimiter'];
|
||||
|
||||
// Add the modifiers
|
||||
if (strpos($def['modifiers'], 'i') !== false)
|
||||
{
|
||||
$regexp .= 'i';
|
||||
if (\strpos($def['modifiers'], 'u') !== \false)
|
||||
}
|
||||
if (strpos($def['modifiers'], 'u') !== false)
|
||||
{
|
||||
$regexp .= 'u';
|
||||
}
|
||||
|
||||
return $regexp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of each capture in given regexp
|
||||
*
|
||||
* Will return an empty string for unnamed captures
|
||||
*
|
||||
* @param string $regexp
|
||||
* @return string[]
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $regexp
|
||||
* @return array
|
||||
*/
|
||||
public static function parse($regexp)
|
||||
{
|
||||
if (!\preg_match('#^(.)(.*?)\\1([a-zA-Z]*)$#Ds', $regexp, $m))
|
||||
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);
|
||||
$regexpLen = strlen($regexp);
|
||||
|
||||
while ($pos < $regexpLen)
|
||||
{
|
||||
switch ($regexp[$pos])
|
||||
{
|
||||
case '\\':
|
||||
// skip next character
|
||||
$pos += 2;
|
||||
break;
|
||||
|
||||
case '[':
|
||||
if (!\preg_match('#\\[(.*?(?<!\\\\)(?:\\\\\\\\)*+)\\]((?:[+*][+?]?|\\?)?)#A', $regexp, $m, 0, $pos))
|
||||
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]),
|
||||
'len' => strlen($m[0]),
|
||||
'type' => 'characterClass',
|
||||
'content' => $m[1],
|
||||
'quantifiers' => $m[2]
|
||||
];
|
||||
$pos += \strlen($m[0]);
|
||||
|
||||
$pos += strlen($m[0]);
|
||||
break;
|
||||
|
||||
case '(':
|
||||
if (\preg_match('#\\(\\?([a-z]*)\\)#iA', $regexp, $m, 0, $pos))
|
||||
if (preg_match('#\\(\\?([a-z]*)\\)#iA', $regexp, $m, 0, $pos))
|
||||
{
|
||||
// This is an option (?i) so we skip past the right parenthesis
|
||||
$ret['tokens'][] = [
|
||||
'pos' => $pos,
|
||||
'len' => \strlen($m[0]),
|
||||
'len' => strlen($m[0]),
|
||||
'type' => 'option',
|
||||
'options' => $m[1]
|
||||
];
|
||||
$pos += \strlen($m[0]);
|
||||
|
||||
$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))
|
||||
|
||||
// This should be a subpattern, we just have to sniff which kind
|
||||
if (preg_match("#(?J)\\(\\?(?:P?<(?<name>[a-z_0-9]+)>|'(?<name>[a-z_0-9]+)')#A", $regexp, $m, \PREG_OFFSET_CAPTURE, $pos))
|
||||
{
|
||||
// This is a named capture
|
||||
$tok = [
|
||||
'pos' => $pos,
|
||||
'len' => \strlen($m[0][0]),
|
||||
'len' => strlen($m[0][0]),
|
||||
'type' => 'capturingSubpatternStart',
|
||||
'name' => $m['name'][0]
|
||||
];
|
||||
$pos += \strlen($m[0][0]);
|
||||
|
||||
$pos += strlen($m[0][0]);
|
||||
}
|
||||
elseif (\preg_match('#\\(\\?([a-z]*):#iA', $regexp, $m, 0, $pos))
|
||||
elseif (preg_match('#\\(\\?([a-z]*):#iA', $regexp, $m, 0, $pos))
|
||||
{
|
||||
// This is a non-capturing subpattern (?:xxx)
|
||||
$tok = [
|
||||
'pos' => $pos,
|
||||
'len' => \strlen($m[0]),
|
||||
'len' => strlen($m[0]),
|
||||
'type' => 'nonCapturingSubpatternStart',
|
||||
'options' => $m[1]
|
||||
];
|
||||
$pos += \strlen($m[0]);
|
||||
|
||||
$pos += strlen($m[0]);
|
||||
}
|
||||
elseif (\preg_match('#\\(\\?>#iA', $regexp, $m, 0, $pos))
|
||||
elseif (preg_match('#\\(\\?>#iA', $regexp, $m, 0, $pos))
|
||||
{
|
||||
/* This is a non-capturing subpattern with atomic grouping "(?>x+)" */
|
||||
$tok = [
|
||||
'pos' => $pos,
|
||||
'len' => \strlen($m[0]),
|
||||
'len' => strlen($m[0]),
|
||||
'type' => 'nonCapturingSubpatternStart',
|
||||
'subtype' => 'atomic'
|
||||
];
|
||||
$pos += \strlen($m[0]);
|
||||
|
||||
$pos += strlen($m[0]);
|
||||
}
|
||||
elseif (\preg_match('#\\(\\?(<?[!=])#A', $regexp, $m, 0, $pos))
|
||||
elseif (preg_match('#\\(\\?(<?[!=])#A', $regexp, $m, 0, $pos))
|
||||
{
|
||||
// This is an assertion
|
||||
$assertions = [
|
||||
'=' => 'lookahead',
|
||||
'<=' => 'lookbehind',
|
||||
'!' => 'negativeLookahead',
|
||||
'<!' => 'negativeLookbehind'
|
||||
];
|
||||
|
||||
$tok = [
|
||||
'pos' => $pos,
|
||||
'len' => \strlen($m[0]),
|
||||
'len' => strlen($m[0]),
|
||||
'type' => $assertions[$m[1]] . 'AssertionStart'
|
||||
];
|
||||
$pos += \strlen($m[0]);
|
||||
|
||||
$pos += strlen($m[0]);
|
||||
}
|
||||
elseif (\preg_match('#\\(\\?#A', $regexp, $m, 0, $pos))
|
||||
elseif (preg_match('#\\(\\?#A', $regexp, $m, 0, $pos))
|
||||
{
|
||||
throw new RuntimeException('Unsupported subpattern type at pos ' . $pos);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This should be a normal capture
|
||||
$tok = [
|
||||
'pos' => $pos,
|
||||
'len' => 1,
|
||||
'type' => 'capturingSubpatternStart'
|
||||
];
|
||||
|
||||
++$pos;
|
||||
}
|
||||
$openSubpatterns[] = \count($ret['tokens']);
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
// Add the key to this token to its matching token and capture this subpattern's
|
||||
// content
|
||||
$k = array_pop($openSubpatterns);
|
||||
$startToken =& $ret['tokens'][$k];
|
||||
$startToken['endToken'] = \count($ret['tokens']);
|
||||
$startToken['content'] = \substr(
|
||||
$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);
|
||||
|
||||
// Look for quantifiers after the subpattern, e.g. (?:ab)++
|
||||
$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',
|
||||
'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;
|
||||
}
|
||||
}
|
||||
257
vendor/s9e/text-formatter/src/Configurator/Helpers/RulesHelper.php
vendored
Normal file
257
vendor/s9e/text-formatter/src/Configurator/Helpers/RulesHelper.php
vendored
Normal file
@@ -0,0 +1,257 @@
|
||||
<?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 s9e\TextFormatter\Configurator\Collections\Ruleset;
|
||||
use s9e\TextFormatter\Configurator\Collections\TagCollection;
|
||||
|
||||
abstract class RulesHelper
|
||||
{
|
||||
/**
|
||||
* Generate the allowedChildren and allowedDescendants bitfields for every tag and for the root context
|
||||
*
|
||||
* @param TagCollection $tags
|
||||
* @param Ruleset $rootRules
|
||||
* @return array
|
||||
*/
|
||||
public static function getBitfields(TagCollection $tags, Ruleset $rootRules)
|
||||
{
|
||||
$rules = ['*root*' => iterator_to_array($rootRules)];
|
||||
foreach ($tags as $tagName => $tag)
|
||||
{
|
||||
$rules[$tagName] = iterator_to_array($tag->rules);
|
||||
}
|
||||
|
||||
// Create a matrix that contains all of the tags and whether every other tag is allowed as
|
||||
// a child and as a descendant
|
||||
$matrix = self::unrollRules($rules);
|
||||
|
||||
// Remove unusable tags from the matrix
|
||||
self::pruneMatrix($matrix);
|
||||
|
||||
// Group together tags are allowed in the exact same contexts
|
||||
$groupedTags = [];
|
||||
foreach (array_keys($matrix) as $tagName)
|
||||
{
|
||||
if ($tagName === '*root*')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$k = '';
|
||||
foreach ($matrix as $tagMatrix)
|
||||
{
|
||||
$k .= $tagMatrix['allowedChildren'][$tagName];
|
||||
$k .= $tagMatrix['allowedDescendants'][$tagName];
|
||||
}
|
||||
|
||||
$groupedTags[$k][] = $tagName;
|
||||
}
|
||||
|
||||
// Record the bit number of each tag, and the name of a tag for each bit
|
||||
$bitTag = [];
|
||||
$bitNumber = 0;
|
||||
$tagsConfig = [];
|
||||
foreach ($groupedTags as $tagNames)
|
||||
{
|
||||
foreach ($tagNames as $tagName)
|
||||
{
|
||||
$tagsConfig[$tagName]['bitNumber'] = $bitNumber;
|
||||
$bitTag[$bitNumber] = $tagName;
|
||||
}
|
||||
|
||||
++$bitNumber;
|
||||
}
|
||||
|
||||
// Build the bitfields of each tag, including the *root* pseudo-tag
|
||||
foreach ($matrix as $tagName => $tagMatrix)
|
||||
{
|
||||
$allowedChildren = '';
|
||||
$allowedDescendants = '';
|
||||
foreach ($bitTag as $targetName)
|
||||
{
|
||||
$allowedChildren .= $tagMatrix['allowedChildren'][$targetName];
|
||||
$allowedDescendants .= $tagMatrix['allowedDescendants'][$targetName];
|
||||
}
|
||||
|
||||
$tagsConfig[$tagName]['allowed'] = self::pack($allowedChildren, $allowedDescendants);
|
||||
}
|
||||
|
||||
// Prepare the return value
|
||||
$return = [
|
||||
'root' => $tagsConfig['*root*'],
|
||||
'tags' => $tagsConfig
|
||||
];
|
||||
unset($return['tags']['*root*']);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a matrix of settings
|
||||
*
|
||||
* @param array $rules Rules for each tag
|
||||
* @return array Multidimensional array of [tagName => [scope => [targetName => setting]]]
|
||||
*/
|
||||
protected static function initMatrix(array $rules)
|
||||
{
|
||||
$matrix = [];
|
||||
$tagNames = array_keys($rules);
|
||||
|
||||
foreach ($rules as $tagName => $tagRules)
|
||||
{
|
||||
$matrix[$tagName]['allowedChildren'] = array_fill_keys($tagNames, 0);
|
||||
$matrix[$tagName]['allowedDescendants'] = array_fill_keys($tagNames, 0);
|
||||
}
|
||||
|
||||
return $matrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply given rule from each applicable tag
|
||||
*
|
||||
* For each tag, if the rule has any target we set the corresponding value for each target in the
|
||||
* matrix
|
||||
*
|
||||
* @param array &$matrix Settings matrix
|
||||
* @param array $rules Rules for each tag
|
||||
* @param string $ruleName Rule name
|
||||
* @param string $key Key in the matrix
|
||||
* @param integer $value Value to be set
|
||||
* @return void
|
||||
*/
|
||||
protected static function applyTargetedRule(array &$matrix, $rules, $ruleName, $key, $value)
|
||||
{
|
||||
foreach ($rules as $tagName => $tagRules)
|
||||
{
|
||||
if (!isset($tagRules[$ruleName]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($tagRules[$ruleName] as $targetName)
|
||||
{
|
||||
$matrix[$tagName][$key][$targetName] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $rules
|
||||
* @return array
|
||||
*/
|
||||
protected static function unrollRules(array $rules)
|
||||
{
|
||||
// Initialize the matrix with default values
|
||||
$matrix = self::initMatrix($rules);
|
||||
|
||||
// Convert ignoreTags and requireParent to denyDescendant and denyChild rules
|
||||
$tagNames = array_keys($rules);
|
||||
foreach ($rules as $tagName => $tagRules)
|
||||
{
|
||||
if (!empty($tagRules['ignoreTags']))
|
||||
{
|
||||
$rules[$tagName]['denyChild'] = $tagNames;
|
||||
$rules[$tagName]['denyDescendant'] = $tagNames;
|
||||
}
|
||||
|
||||
if (!empty($tagRules['requireParent']))
|
||||
{
|
||||
$denyParents = array_diff($tagNames, $tagRules['requireParent']);
|
||||
foreach ($denyParents as $parentName)
|
||||
{
|
||||
$rules[$parentName]['denyChild'][] = $tagName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply "allow" rules to grant usage, overwriting the default settings
|
||||
self::applyTargetedRule($matrix, $rules, 'allowChild', 'allowedChildren', 1);
|
||||
self::applyTargetedRule($matrix, $rules, 'allowDescendant', 'allowedDescendants', 1);
|
||||
|
||||
// Apply "deny" rules to remove usage
|
||||
self::applyTargetedRule($matrix, $rules, 'denyChild', 'allowedChildren', 0);
|
||||
self::applyTargetedRule($matrix, $rules, 'denyDescendant', 'allowedDescendants', 0);
|
||||
|
||||
return $matrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove unusable tags from the matrix
|
||||
*
|
||||
* @param array &$matrix
|
||||
* @return void
|
||||
*/
|
||||
protected static function pruneMatrix(array &$matrix)
|
||||
{
|
||||
$usableTags = ['*root*' => 1];
|
||||
|
||||
// Start from the root and keep digging
|
||||
$parentTags = $usableTags;
|
||||
do
|
||||
{
|
||||
$nextTags = [];
|
||||
foreach (array_keys($parentTags) as $tagName)
|
||||
{
|
||||
// Accumulate the names of tags that are allowed as children of our parent tags
|
||||
$nextTags += array_filter($matrix[$tagName]['allowedChildren']);
|
||||
}
|
||||
|
||||
// Keep only the tags that are in the matrix but aren't in the usable array yet, then
|
||||
// add them to the array
|
||||
$parentTags = array_diff_key($nextTags, $usableTags);
|
||||
$parentTags = array_intersect_key($parentTags, $matrix);
|
||||
$usableTags += $parentTags;
|
||||
}
|
||||
while (!empty($parentTags));
|
||||
|
||||
// Remove unusable tags from the matrix
|
||||
$matrix = array_intersect_key($matrix, $usableTags);
|
||||
unset($usableTags['*root*']);
|
||||
|
||||
// Remove unusable tags from the targets
|
||||
foreach ($matrix as $tagName => &$tagMatrix)
|
||||
{
|
||||
$tagMatrix['allowedChildren']
|
||||
= array_intersect_key($tagMatrix['allowedChildren'], $usableTags);
|
||||
|
||||
$tagMatrix['allowedDescendants']
|
||||
= array_intersect_key($tagMatrix['allowedDescendants'], $usableTags);
|
||||
}
|
||||
unset($tagMatrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a binary representation such as "101011" to an array of integer
|
||||
*
|
||||
* Each bitfield is split in groups of 8 bits, then converted to a 16-bit integer where the
|
||||
* allowedChildren bitfield occupies the least significant bits and the allowedDescendants
|
||||
* bitfield occupies the most significant bits
|
||||
*
|
||||
* @param string $allowedChildren
|
||||
* @param string $allowedDescendants
|
||||
* @return integer[]
|
||||
*/
|
||||
protected static function pack($allowedChildren, $allowedDescendants)
|
||||
{
|
||||
$allowedChildren = str_split($allowedChildren, 8);
|
||||
$allowedDescendants = str_split($allowedDescendants, 8);
|
||||
|
||||
$allowed = [];
|
||||
foreach (array_keys($allowedChildren) as $k)
|
||||
{
|
||||
$allowed[] = bindec(sprintf(
|
||||
'%1$08s%2$08s',
|
||||
strrev($allowedDescendants[$k]),
|
||||
strrev($allowedChildren[$k])
|
||||
));
|
||||
}
|
||||
|
||||
return $allowed;
|
||||
}
|
||||
}
|
||||
200
vendor/s9e/text-formatter/src/Configurator/Helpers/TemplateHelper.php
vendored
Normal file
200
vendor/s9e/text-formatter/src/Configurator/Helpers/TemplateHelper.php
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
<?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 DOMCharacterData;
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use DOMNode;
|
||||
use DOMProcessingInstruction;
|
||||
use DOMText;
|
||||
use DOMXPath;
|
||||
|
||||
abstract class TemplateHelper
|
||||
{
|
||||
/**
|
||||
* XSL namespace
|
||||
*/
|
||||
const XMLNS_XSL = 'http://www.w3.org/1999/XSL/Transform';
|
||||
|
||||
/**
|
||||
* Return a list of parameters in use in given XSL
|
||||
*
|
||||
* @param string $xsl XSL source
|
||||
* @return array Alphabetically sorted list of unique parameter names
|
||||
*/
|
||||
public static function getParametersFromXSL($xsl)
|
||||
{
|
||||
$paramNames = [];
|
||||
$xpath = new DOMXPath(TemplateLoader::load($xsl));
|
||||
|
||||
// Start by collecting XPath expressions in XSL elements
|
||||
$query = '//xsl:*/@match | //xsl:*/@select | //xsl:*/@test';
|
||||
foreach ($xpath->query($query) as $attribute)
|
||||
{
|
||||
$expr = $attribute->value;
|
||||
$paramNames += array_flip(self::getParametersFromExpression($attribute, $expr));
|
||||
}
|
||||
|
||||
// Collect XPath expressions in attribute value templates
|
||||
$query = '//*[namespace-uri() != "' . self::XMLNS_XSL . '"]/@*[contains(., "{")]';
|
||||
foreach ($xpath->query($query) as $attribute)
|
||||
{
|
||||
foreach (AVTHelper::parse($attribute->value) as $token)
|
||||
{
|
||||
if ($token[0] === 'expression')
|
||||
{
|
||||
$expr = $token[1];
|
||||
$paramNames += array_flip(self::getParametersFromExpression($attribute, $expr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the parameter names and return them in a list
|
||||
ksort($paramNames);
|
||||
|
||||
return array_keys($paramNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight the source of a node inside of a template
|
||||
*
|
||||
* @param DOMNode $node Node to highlight
|
||||
* @param string $prepend HTML to prepend
|
||||
* @param string $append HTML to append
|
||||
* @return string Template's source, as HTML
|
||||
*/
|
||||
public static function highlightNode(DOMNode $node, $prepend, $append)
|
||||
{
|
||||
// Create a copy of the document that we can modify without side effects
|
||||
$dom = $node->ownerDocument->cloneNode(true);
|
||||
$dom->formatOutput = true;
|
||||
|
||||
$xpath = new DOMXPath($dom);
|
||||
$node = $xpath->query($node->getNodePath())->item(0);
|
||||
|
||||
// Add a unique token to the node
|
||||
$uniqid = uniqid('_');
|
||||
if ($node instanceof DOMAttr)
|
||||
{
|
||||
$node->value .= $uniqid;
|
||||
}
|
||||
elseif ($node instanceof DOMElement)
|
||||
{
|
||||
$node->setAttribute($uniqid, '');
|
||||
}
|
||||
elseif ($node instanceof DOMCharacterData || $node instanceof DOMProcessingInstruction)
|
||||
{
|
||||
$node->data .= $uniqid;
|
||||
}
|
||||
|
||||
$docXml = TemplateLoader::innerXML($dom->documentElement);
|
||||
$docXml = trim(str_replace("\n ", "\n", $docXml));
|
||||
|
||||
$nodeHtml = htmlspecialchars(trim($dom->saveXML($node)));
|
||||
$docHtml = htmlspecialchars($docXml);
|
||||
|
||||
// Enclose the node's representation in our highlighting HTML
|
||||
$html = str_replace($nodeHtml, $prepend . $nodeHtml . $append, $docHtml);
|
||||
|
||||
// Remove the unique token from HTML
|
||||
$html = str_replace(' ' . $uniqid . '=""', '', $html);
|
||||
$html = str_replace($uniqid, '', $html);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace simple templates (in an array, in-place) with a common template
|
||||
*
|
||||
* In some situations, renderers can take advantage of multiple tags having the same template. In
|
||||
* any configuration, there's almost always a number of "simple" tags that are rendered as an
|
||||
* HTML element of the same name with no HTML attributes. For instance, the system tag "p" used
|
||||
* for paragraphs, "B" tags used for "b" HTML elements, etc... This method replaces those
|
||||
* templates with a common template that uses a dynamic element name based on the tag's name,
|
||||
* either its nodeName or localName depending on whether the tag is namespaced, and normalized to
|
||||
* lowercase using XPath's translate() function
|
||||
*
|
||||
* @param array<string> &$templates Associative array of [tagName => template]
|
||||
* @param integer $minCount
|
||||
* @return void
|
||||
*/
|
||||
public static function replaceHomogeneousTemplates(array &$templates, $minCount = 3)
|
||||
{
|
||||
// Prepare the XPath expression used for the element's name
|
||||
$expr = 'name()';
|
||||
|
||||
// Identify "simple" tags, whose template is one element of the same name. Their template
|
||||
// can be replaced with a dynamic template shared by all the simple tags
|
||||
$tagNames = [];
|
||||
foreach ($templates as $tagName => $template)
|
||||
{
|
||||
// Generate the element name based on the tag's localName, lowercased
|
||||
$elName = strtolower(preg_replace('/^[^:]+:/', '', $tagName));
|
||||
if ($template === '<' . $elName . '><xsl:apply-templates/></' . $elName . '>')
|
||||
{
|
||||
$tagNames[] = $tagName;
|
||||
|
||||
// Use local-name() if any of the tags are namespaced
|
||||
if (strpos($tagName, ':') !== false)
|
||||
{
|
||||
$expr = 'local-name()';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We only bother replacing their template if there are at least $minCount simple tags.
|
||||
// Otherwise it only makes the stylesheet bigger
|
||||
if (count($tagNames) < $minCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate a list of uppercase characters from the tags' names
|
||||
$chars = preg_replace('/[^A-Z]+/', '', count_chars(implode('', $tagNames), 3));
|
||||
if ($chars > '')
|
||||
{
|
||||
$expr = 'translate(' . $expr . ",'" . $chars . "','" . strtolower($chars) . "')";
|
||||
}
|
||||
|
||||
// Prepare the common template
|
||||
$template = '<xsl:element name="{' . $expr . '}"><xsl:apply-templates/></xsl:element>';
|
||||
|
||||
// Replace the templates
|
||||
foreach ($tagNames as $tagName)
|
||||
{
|
||||
$templates[$tagName] = $template;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of parameters from given XPath expression
|
||||
*
|
||||
* @param DOMNode $node Context node
|
||||
* @param string $expr XPath expression
|
||||
* @return string[]
|
||||
*/
|
||||
protected static function getParametersFromExpression(DOMNode $node, $expr)
|
||||
{
|
||||
$varNames = XPathHelper::getVariables($expr);
|
||||
$paramNames = [];
|
||||
$xpath = new DOMXPath($node->ownerDocument);
|
||||
foreach ($varNames as $name)
|
||||
{
|
||||
// Test whether this is the name of a local variable
|
||||
$query = 'ancestor-or-self::*/preceding-sibling::xsl:variable[@name="' . $name . '"]';
|
||||
if (!$xpath->query($query, $node)->length)
|
||||
{
|
||||
$paramNames[] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
return $paramNames;
|
||||
}
|
||||
}
|
||||
713
vendor/s9e/text-formatter/src/Configurator/Helpers/TemplateInspector.php
vendored
Normal file
713
vendor/s9e/text-formatter/src/Configurator/Helpers/TemplateInspector.php
vendored
Normal file
@@ -0,0 +1,713 @@
|
||||
<?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 DOMElement;
|
||||
use DOMXPath;
|
||||
|
||||
/**
|
||||
* This class helps the RulesGenerator by analyzing a given template in order to answer questions
|
||||
* such as "can this tag be a child/descendant of that other tag?" and others related to the HTML5
|
||||
* content model.
|
||||
*
|
||||
* We use the HTML5 specs to determine which children or descendants should be allowed or denied
|
||||
* based on HTML5 content models. While it does not exactly match HTML5 content models, it gets
|
||||
* pretty close. We also use HTML5 "optional end tag" rules to create closeParent rules.
|
||||
*
|
||||
* Currently, this method does not evaluate elements created with <xsl:element> correctly, or
|
||||
* attributes created with <xsl:attribute> and may never will due to the increased complexity it
|
||||
* would entail. Additionally, it does not evaluate the scope of <xsl:apply-templates/>. For
|
||||
* instance, it will treat <xsl:apply-templates select="LI"/> as if it was <xsl:apply-templates/>
|
||||
*
|
||||
* @link http://dev.w3.org/html5/spec/content-models.html#content-models
|
||||
* @link http://dev.w3.org/html5/spec/syntax.html#optional-tags
|
||||
*/
|
||||
class TemplateInspector
|
||||
{
|
||||
/**
|
||||
* XSL namespace
|
||||
*/
|
||||
const XMLNS_XSL = 'http://www.w3.org/1999/XSL/Transform';
|
||||
|
||||
/**
|
||||
* @var string[] allowChild bitfield for each branch
|
||||
*/
|
||||
protected $allowChildBitfields = [];
|
||||
|
||||
/**
|
||||
* @var bool Whether elements are allowed as children
|
||||
*/
|
||||
protected $allowsChildElements;
|
||||
|
||||
/**
|
||||
* @var bool Whether text nodes are allowed as children
|
||||
*/
|
||||
protected $allowsText;
|
||||
|
||||
/**
|
||||
* @var array[] Array of array of DOMElement instances
|
||||
*/
|
||||
protected $branches;
|
||||
|
||||
/**
|
||||
* @var string OR-ed bitfield representing all of the categories used by this template
|
||||
*/
|
||||
protected $contentBitfield = "\0";
|
||||
|
||||
/**
|
||||
* @var string Default bitfield used at the root of a branch
|
||||
*/
|
||||
protected $defaultBranchBitfield;
|
||||
|
||||
/**
|
||||
* @var string denyDescendant bitfield
|
||||
*/
|
||||
protected $denyDescendantBitfield = "\0";
|
||||
|
||||
/**
|
||||
* @var \DOMDocument Document containing the template
|
||||
*/
|
||||
protected $dom;
|
||||
|
||||
/**
|
||||
* @var bool Whether this template contains any HTML elements
|
||||
*/
|
||||
protected $hasElements = false;
|
||||
|
||||
/**
|
||||
* @var bool Whether this template renders non-whitespace text nodes at its root
|
||||
*/
|
||||
protected $hasRootText;
|
||||
|
||||
/**
|
||||
* @var bool Whether this template should be considered a block-level element
|
||||
*/
|
||||
protected $isBlock = false;
|
||||
|
||||
/**
|
||||
* @var bool Whether the template uses the "empty" content model
|
||||
*/
|
||||
protected $isEmpty;
|
||||
|
||||
/**
|
||||
* @var bool Whether this template adds to the list of active formatting elements
|
||||
*/
|
||||
protected $isFormattingElement;
|
||||
|
||||
/**
|
||||
* @var bool Whether this template lets content through via an xsl:apply-templates element
|
||||
*/
|
||||
protected $isPassthrough = false;
|
||||
|
||||
/**
|
||||
* @var bool Whether all branches use the transparent content model
|
||||
*/
|
||||
protected $isTransparent = false;
|
||||
|
||||
/**
|
||||
* @var bool Whether all branches have an ancestor that is a void element
|
||||
*/
|
||||
protected $isVoid;
|
||||
|
||||
/**
|
||||
* @var array Last HTML element that precedes an <xsl:apply-templates/> node
|
||||
*/
|
||||
protected $leafNodes = [];
|
||||
|
||||
/**
|
||||
* @var bool Whether any branch has an element that preserves new lines by default (e.g. <pre>)
|
||||
*/
|
||||
protected $preservesNewLines = false;
|
||||
|
||||
/**
|
||||
* @var array Bitfield of the first HTML element of every branch
|
||||
*/
|
||||
protected $rootBitfields = [];
|
||||
|
||||
/**
|
||||
* @var array Every HTML element that has no HTML parent
|
||||
*/
|
||||
protected $rootNodes = [];
|
||||
|
||||
/**
|
||||
* @var DOMXPath XPath engine associated with $this->dom
|
||||
*/
|
||||
protected $xpath;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $template Template content
|
||||
*/
|
||||
public function __construct($template)
|
||||
{
|
||||
$this->dom = TemplateLoader::load($template);
|
||||
$this->xpath = new DOMXPath($this->dom);
|
||||
|
||||
$this->defaultBranchBitfield = ElementInspector::getAllowChildBitfield($this->dom->createElement('div'));
|
||||
|
||||
$this->analyseRootNodes();
|
||||
$this->analyseBranches();
|
||||
$this->analyseContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this template allows a given child
|
||||
*
|
||||
* @param TemplateInspector $child
|
||||
* @return bool
|
||||
*/
|
||||
public function allowsChild(TemplateInspector $child)
|
||||
{
|
||||
// Sometimes, a template can technically be allowed as a child but denied as a descendant
|
||||
if (!$this->allowsDescendant($child))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($child->rootBitfields as $rootBitfield)
|
||||
{
|
||||
foreach ($this->allowChildBitfields as $allowChildBitfield)
|
||||
{
|
||||
if (!self::match($rootBitfield, $allowChildBitfield))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ($this->allowsText || !$child->hasRootText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this template allows a given descendant
|
||||
*
|
||||
* @param TemplateInspector $descendant
|
||||
* @return bool
|
||||
*/
|
||||
public function allowsDescendant(TemplateInspector $descendant)
|
||||
{
|
||||
// Test whether the descendant is explicitly disallowed
|
||||
if (self::match($descendant->contentBitfield, $this->denyDescendantBitfield))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Test whether the descendant contains any elements and we disallow elements
|
||||
return ($this->allowsChildElements || !$descendant->hasElements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this template allows elements as children
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function allowsChildElements()
|
||||
{
|
||||
return $this->allowsChildElements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this template allows text nodes as children
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function allowsText()
|
||||
{
|
||||
return $this->allowsText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this template automatically closes given parent template
|
||||
*
|
||||
* @param TemplateInspector $parent
|
||||
* @return bool
|
||||
*/
|
||||
public function closesParent(TemplateInspector $parent)
|
||||
{
|
||||
// Test whether any of this template's root nodes closes any of given template's leaf nodes
|
||||
foreach ($this->rootNodes as $rootNode)
|
||||
{
|
||||
foreach ($parent->leafNodes as $leafNode)
|
||||
{
|
||||
if (ElementInspector::closesParent($rootNode, $leafNode))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate an XPath expression
|
||||
*
|
||||
* @param string $expr XPath expression
|
||||
* @param DOMElement $node Context node
|
||||
* @return mixed
|
||||
*/
|
||||
public function evaluate($expr, DOMElement $node = null)
|
||||
{
|
||||
return $this->xpath->evaluate($expr, $node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this template should be considered a block-level element
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isBlock()
|
||||
{
|
||||
return $this->isBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this template adds to the list of active formatting elements
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isFormattingElement()
|
||||
{
|
||||
return $this->isFormattingElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this template uses the "empty" content model
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty()
|
||||
{
|
||||
return $this->isEmpty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this template lets content through via an xsl:apply-templates element
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isPassthrough()
|
||||
{
|
||||
return $this->isPassthrough;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this template uses the "transparent" content model
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isTransparent()
|
||||
{
|
||||
return $this->isTransparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether all branches have an ancestor that is a void element
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isVoid()
|
||||
{
|
||||
return $this->isVoid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this template preserves the whitespace in its descendants
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function preservesNewLines()
|
||||
{
|
||||
return $this->preservesNewLines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyses the content of the whole template and set $this->contentBitfield accordingly
|
||||
*/
|
||||
protected function analyseContent()
|
||||
{
|
||||
// Get all non-XSL elements
|
||||
$query = '//*[namespace-uri() != "' . self::XMLNS_XSL . '"]';
|
||||
foreach ($this->xpath->query($query) as $node)
|
||||
{
|
||||
$this->contentBitfield |= ElementInspector::getCategoryBitfield($node);
|
||||
$this->hasElements = true;
|
||||
}
|
||||
|
||||
// Test whether this template is passthrough
|
||||
$this->isPassthrough = (bool) $this->evaluate('count(//xsl:apply-templates)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the HTML elements (and their bitfield) rendered at the root of the template
|
||||
*/
|
||||
protected function analyseRootNodes()
|
||||
{
|
||||
// Get every non-XSL element with no non-XSL ancestor. This should return us the first
|
||||
// HTML element of every branch
|
||||
$query = '//*[namespace-uri() != "' . self::XMLNS_XSL . '"]'
|
||||
. '[not(ancestor::*[namespace-uri() != "' . self::XMLNS_XSL . '"])]';
|
||||
foreach ($this->xpath->query($query) as $node)
|
||||
{
|
||||
// Store the root node of this branch
|
||||
$this->rootNodes[] = $node;
|
||||
|
||||
// If any root node is a block-level element, we'll mark the template as such
|
||||
if ($this->elementIsBlock($node))
|
||||
{
|
||||
$this->isBlock = true;
|
||||
}
|
||||
|
||||
$this->rootBitfields[] = ElementInspector::getCategoryBitfield($node);
|
||||
}
|
||||
|
||||
// Test for non-whitespace text nodes at the root. For that we need a predicate that filters
|
||||
// out: nodes with a non-XSL ancestor,
|
||||
$predicate = '[not(ancestor::*[namespace-uri() != "' . self::XMLNS_XSL . '"])]';
|
||||
|
||||
// ..and nodes with an <xsl:attribute/>, <xsl:comment/> or <xsl:variable/> ancestor
|
||||
$predicate .= '[not(ancestor::xsl:attribute | ancestor::xsl:comment | ancestor::xsl:variable)]';
|
||||
|
||||
$query = '//text()[normalize-space() != ""]' . $predicate
|
||||
. '|'
|
||||
. '//xsl:text[normalize-space() != ""]' . $predicate
|
||||
. '|'
|
||||
. '//xsl:value-of' . $predicate;
|
||||
|
||||
$this->hasRootText = (bool) $this->evaluate('count(' . $query . ')');
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyses each branch that leads to an <xsl:apply-templates/> tag
|
||||
*/
|
||||
protected function analyseBranches()
|
||||
{
|
||||
$this->branches = [];
|
||||
foreach ($this->xpath->query('//xsl:apply-templates') as $applyTemplates)
|
||||
{
|
||||
$query = 'ancestor::*[namespace-uri() != "' . self::XMLNS_XSL . '"]';
|
||||
$this->branches[] = iterator_to_array($this->xpath->query($query, $applyTemplates));
|
||||
}
|
||||
|
||||
$this->computeAllowsChildElements();
|
||||
$this->computeAllowsText();
|
||||
$this->computeBitfields();
|
||||
$this->computeFormattingElement();
|
||||
$this->computeIsEmpty();
|
||||
$this->computeIsTransparent();
|
||||
$this->computeIsVoid();
|
||||
$this->computePreservesNewLines();
|
||||
$this->storeLeafNodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether any branch of this template has an element that has given property
|
||||
*
|
||||
* @param string $methodName
|
||||
* @return bool
|
||||
*/
|
||||
protected function anyBranchHasProperty($methodName)
|
||||
{
|
||||
foreach ($this->branches as $branch)
|
||||
{
|
||||
foreach ($branch as $element)
|
||||
{
|
||||
if (ElementInspector::$methodName($element))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the allowChildBitfields and denyDescendantBitfield properties
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function computeBitfields()
|
||||
{
|
||||
if (empty($this->branches))
|
||||
{
|
||||
$this->allowChildBitfields = ["\0"];
|
||||
|
||||
return;
|
||||
}
|
||||
foreach ($this->branches as $branch)
|
||||
{
|
||||
/**
|
||||
* @var string allowChild bitfield for current branch. Starts with the value associated
|
||||
* with <div> in order to approximate a value if the whole branch uses the
|
||||
* transparent content model
|
||||
*/
|
||||
$branchBitfield = $this->defaultBranchBitfield;
|
||||
|
||||
foreach ($branch as $element)
|
||||
{
|
||||
if (!ElementInspector::isTransparent($element))
|
||||
{
|
||||
// If the element isn't transparent, we reset its bitfield
|
||||
$branchBitfield = "\0";
|
||||
}
|
||||
|
||||
// allowChild rules are cumulative if transparent, and reset above otherwise
|
||||
$branchBitfield |= ElementInspector::getAllowChildBitfield($element);
|
||||
|
||||
// denyDescendant rules are cumulative
|
||||
$this->denyDescendantBitfield |= ElementInspector::getDenyDescendantBitfield($element);
|
||||
}
|
||||
|
||||
// Add this branch's bitfield to the list
|
||||
$this->allowChildBitfields[] = $branchBitfield;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the allowsChildElements property
|
||||
*
|
||||
* A template allows child Elements if it has at least one xsl:apply-templates and none of its
|
||||
* ancestors have the text-only ("to") property
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function computeAllowsChildElements()
|
||||
{
|
||||
$this->allowsChildElements = ($this->anyBranchHasProperty('isTextOnly')) ? false : !empty($this->branches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the allowsText property
|
||||
*
|
||||
* A template is said to allow text if none of the leaf elements disallow text
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function computeAllowsText()
|
||||
{
|
||||
foreach (array_filter($this->branches) as $branch)
|
||||
{
|
||||
if (ElementInspector::disallowsText(end($branch)))
|
||||
{
|
||||
$this->allowsText = false;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->allowsText = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the isFormattingElement property
|
||||
*
|
||||
* A template is said to be a formatting element if all (non-zero) of its branches are entirely
|
||||
* composed of formatting elements
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function computeFormattingElement()
|
||||
{
|
||||
foreach ($this->branches as $branch)
|
||||
{
|
||||
foreach ($branch as $element)
|
||||
{
|
||||
if (!ElementInspector::isFormattingElement($element) && !$this->isFormattingSpan($element))
|
||||
{
|
||||
$this->isFormattingElement = false;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->isFormattingElement = (bool) count(array_filter($this->branches));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the isEmpty property
|
||||
*
|
||||
* A template is said to be empty if it has no xsl:apply-templates elements or any there is a empty
|
||||
* element ancestor to an xsl:apply-templates element
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function computeIsEmpty()
|
||||
{
|
||||
$this->isEmpty = ($this->anyBranchHasProperty('isEmpty')) || empty($this->branches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the isTransparent property
|
||||
*
|
||||
* A template is said to be transparent if it has at least one branch and no non-transparent
|
||||
* elements in its path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function computeIsTransparent()
|
||||
{
|
||||
foreach ($this->branches as $branch)
|
||||
{
|
||||
foreach ($branch as $element)
|
||||
{
|
||||
if (!ElementInspector::isTransparent($element))
|
||||
{
|
||||
$this->isTransparent = false;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->isTransparent = !empty($this->branches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the isVoid property
|
||||
*
|
||||
* A template is said to be void if it has no xsl:apply-templates elements or any there is a void
|
||||
* element ancestor to an xsl:apply-templates element
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function computeIsVoid()
|
||||
{
|
||||
$this->isVoid = ($this->anyBranchHasProperty('isVoid')) || empty($this->branches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the preservesNewLines property
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function computePreservesNewLines()
|
||||
{
|
||||
foreach ($this->branches as $branch)
|
||||
{
|
||||
$style = '';
|
||||
foreach ($branch as $element)
|
||||
{
|
||||
$style .= $this->getStyle($element, true);
|
||||
}
|
||||
|
||||
if (preg_match('(.*white-space\\s*:\\s*(no|pre))is', $style, $m) && strtolower($m[1]) === 'pre')
|
||||
{
|
||||
$this->preservesNewLines = true;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->preservesNewLines = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether given element is a block-level element
|
||||
*
|
||||
* @param DOMElement $element
|
||||
* @return bool
|
||||
*/
|
||||
protected function elementIsBlock(DOMElement $element)
|
||||
{
|
||||
$style = $this->getStyle($element);
|
||||
if (preg_match('(\\bdisplay\\s*:\\s*block)i', $style))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (preg_match('(\\bdisplay\\s*:\\s*(?:inli|no)ne)i', $style))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return ElementInspector::isBlock($element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve and return the inline style assigned to given element
|
||||
*
|
||||
* @param DOMElement $node Context node
|
||||
* @param bool $deep Whether to retrieve the content of all xsl:attribute descendants
|
||||
* @return string
|
||||
*/
|
||||
protected function getStyle(DOMElement $node, $deep = false)
|
||||
{
|
||||
$style = '';
|
||||
if (ElementInspector::preservesWhitespace($node))
|
||||
{
|
||||
$style .= 'white-space:pre;';
|
||||
}
|
||||
$style .= $node->getAttribute('style');
|
||||
|
||||
// Add the content of any descendant/child xsl:attribute named "style"
|
||||
$query = (($deep) ? './/' : './') . 'xsl:attribute[@name="style"]';
|
||||
foreach ($this->xpath->query($query, $node) as $attribute)
|
||||
{
|
||||
$style .= ';' . $attribute->textContent;
|
||||
}
|
||||
|
||||
return $style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether given node is a span element used for formatting
|
||||
*
|
||||
* Will return TRUE if the node is a span element with a class attribute and/or a style attribute
|
||||
* and no other attributes
|
||||
*
|
||||
* @param DOMElement $node
|
||||
* @return boolean
|
||||
*/
|
||||
protected function isFormattingSpan(DOMElement $node)
|
||||
{
|
||||
if ($node->nodeName !== 'span')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($node->getAttribute('class') === '' && $node->getAttribute('style') === '')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($node->attributes as $attrName => $attribute)
|
||||
{
|
||||
if ($attrName !== 'class' && $attrName !== 'style')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the names of every leaf node
|
||||
*
|
||||
* A leaf node is defined as the closest non-XSL ancestor to an xsl:apply-templates element
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function storeLeafNodes()
|
||||
{
|
||||
foreach (array_filter($this->branches) as $branch)
|
||||
{
|
||||
$this->leafNodes[] = end($branch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether two bitfields have any bits in common
|
||||
*
|
||||
* @param string $bitfield1
|
||||
* @param string $bitfield2
|
||||
* @return bool
|
||||
*/
|
||||
protected static function match($bitfield1, $bitfield2)
|
||||
{
|
||||
return (trim($bitfield1 & $bitfield2, "\0") !== '');
|
||||
}
|
||||
}
|
||||
188
vendor/s9e/text-formatter/src/Configurator/Helpers/TemplateLoader.php
vendored
Normal file
188
vendor/s9e/text-formatter/src/Configurator/Helpers/TemplateLoader.php
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
<?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 DOMDocument;
|
||||
use DOMElement;
|
||||
use DOMXPath;
|
||||
use RuntimeException;
|
||||
|
||||
abstract class TemplateLoader
|
||||
{
|
||||
/**
|
||||
* XSL namespace
|
||||
*/
|
||||
const XMLNS_XSL = 'http://www.w3.org/1999/XSL/Transform';
|
||||
|
||||
/**
|
||||
* Get the XML content of an element
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param DOMElement $element
|
||||
* @return string
|
||||
*/
|
||||
public static function innerXML(DOMElement $element)
|
||||
{
|
||||
// Serialize the XML then remove the outer element
|
||||
$xml = $element->ownerDocument->saveXML($element);
|
||||
$pos = 1 + strpos($xml, '>');
|
||||
$len = strrpos($xml, '<') - $pos;
|
||||
|
||||
// If the template is empty, return an empty string
|
||||
return ($len < 1) ? '' : substr($xml, $pos, $len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a template as an xsl:template node
|
||||
*
|
||||
* Will attempt to load it as XML first, then as HTML as a fallback. Either way, an xsl:template
|
||||
* node is returned
|
||||
*
|
||||
* @param string $template
|
||||
* @return DOMDocument
|
||||
*/
|
||||
public static function load($template)
|
||||
{
|
||||
$dom = self::loadAsXML($template) ?: self::loadAsXML(self::fixEntities($template));
|
||||
if ($dom)
|
||||
{
|
||||
return $dom;
|
||||
}
|
||||
|
||||
// If the template contains an XSL element, abort now. Otherwise, try reparsing it as HTML
|
||||
if (strpos($template, '<xsl:') !== false)
|
||||
{
|
||||
$error = libxml_get_last_error();
|
||||
|
||||
throw new RuntimeException('Invalid XSL: ' . $error->message);
|
||||
}
|
||||
|
||||
return self::loadAsHTML($template);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a loaded template back into a string
|
||||
*
|
||||
* NOTE: removes the root node created by load()
|
||||
*
|
||||
* @param DOMDocument $dom
|
||||
* @return string
|
||||
*/
|
||||
public static function save(DOMDocument $dom)
|
||||
{
|
||||
$xml = self::innerXML($dom->documentElement);
|
||||
if (strpos($xml, 'xmlns:xsl') !== false)
|
||||
{
|
||||
$xml = preg_replace('((<[^>]+?) xmlns:xsl="' . self::XMLNS_XSL . '")', '$1', $xml);
|
||||
}
|
||||
|
||||
return $xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace HTML entities and unescaped ampersands in given template
|
||||
*
|
||||
* @param string $template
|
||||
* @return string
|
||||
*/
|
||||
protected static function fixEntities($template)
|
||||
{
|
||||
return preg_replace_callback(
|
||||
'(&(?!quot;|amp;|apos;|lt;|gt;)\\w+;)',
|
||||
function ($m)
|
||||
{
|
||||
return html_entity_decode($m[0], ENT_NOQUOTES, 'UTF-8');
|
||||
},
|
||||
preg_replace('(&(?![A-Za-z0-9]+;|#\\d+;|#x[A-Fa-f0-9]+;))', '&', $template)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load given HTML template in a DOM document
|
||||
*
|
||||
* @param string $template Original template
|
||||
* @return DOMDocument
|
||||
*/
|
||||
protected static function loadAsHTML($template)
|
||||
{
|
||||
$template = self::replaceCDATA($template);
|
||||
|
||||
$dom = new DOMDocument;
|
||||
$html = '<?xml version="1.0" encoding="utf-8" ?><html><body><div>' . $template . '</div></body></html>';
|
||||
|
||||
$useErrors = libxml_use_internal_errors(true);
|
||||
$dom->loadHTML($html, LIBXML_NSCLEAN);
|
||||
self::removeInvalidAttributes($dom);
|
||||
libxml_use_internal_errors($useErrors);
|
||||
|
||||
// Now dump the thing as XML then reload it with the proper root element
|
||||
$xml = '<?xml version="1.0" encoding="utf-8" ?><xsl:template xmlns:xsl="' . self::XMLNS_XSL . '">' . self::innerXML($dom->documentElement->firstChild->firstChild) . '</xsl:template>';
|
||||
|
||||
$useErrors = libxml_use_internal_errors(true);
|
||||
$dom->loadXML($xml, LIBXML_NSCLEAN);
|
||||
libxml_use_internal_errors($useErrors);
|
||||
|
||||
return $dom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load given XSL template in a DOM document
|
||||
*
|
||||
* @param string $template Original template
|
||||
* @return bool|DOMDocument DOMDocument on success, FALSE otherwise
|
||||
*/
|
||||
protected static function loadAsXML($template)
|
||||
{
|
||||
$xml = '<?xml version="1.0" encoding="utf-8" ?><xsl:template xmlns:xsl="' . self::XMLNS_XSL . '">' . $template . '</xsl:template>';
|
||||
|
||||
$useErrors = libxml_use_internal_errors(true);
|
||||
$dom = new DOMDocument;
|
||||
$success = $dom->loadXML($xml, LIBXML_NOCDATA | LIBXML_NSCLEAN);
|
||||
self::removeInvalidAttributes($dom);
|
||||
libxml_use_internal_errors($useErrors);
|
||||
|
||||
return ($success) ? $dom : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove attributes with an invalid name from given DOM document
|
||||
*
|
||||
* @param DOMDocument $dom
|
||||
* @return void
|
||||
*/
|
||||
protected static function removeInvalidAttributes(DOMDocument $dom)
|
||||
{
|
||||
$xpath = new DOMXPath($dom);
|
||||
foreach ($xpath->query('//@*') as $attribute)
|
||||
{
|
||||
if (!preg_match('(^(?:[-\\w]+:)?(?!\\d)[-\\w]+$)D', $attribute->nodeName))
|
||||
{
|
||||
$attribute->parentNode->removeAttributeNode($attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace CDATA sections in given template
|
||||
*
|
||||
* @param string $template Original template
|
||||
* @return string Modified template
|
||||
*/
|
||||
protected static function replaceCDATA($template)
|
||||
{
|
||||
return preg_replace_callback(
|
||||
'(<!\\[CDATA\\[(.*?)\\]\\]>)',
|
||||
function ($m)
|
||||
{
|
||||
return htmlspecialchars($m[1]);
|
||||
},
|
||||
$template
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,70 @@
|
||||
<?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
|
||||
{
|
||||
/**
|
||||
* XSL namespace
|
||||
*/
|
||||
const XMLNS_XSL = 'http://www.w3.org/1999/XSL/Transform';
|
||||
|
||||
/**
|
||||
* Replace parts of a template that match given regexp
|
||||
*
|
||||
* Treats attribute values as plain text. Replacements within XPath expression is unsupported.
|
||||
* The callback must return an array with two elements. The first must be either of 'expression',
|
||||
* 'literal' or 'passthrough', and the second element depends on the first.
|
||||
*
|
||||
* - 'expression' indicates that the replacement must be treated as an XPath expression such as
|
||||
* '@foo', which must be passed as the second element.
|
||||
*
|
||||
* - 'literal' indicates a literal (plain text) replacement, passed as its second element.
|
||||
*
|
||||
* - 'passthrough' indicates that the replacement should the tag's content. It works differently
|
||||
* whether it is inside an attribute's value or a text node. Within an attribute's value, the
|
||||
* replacement will be the text content of the tag. Within a text node, the replacement
|
||||
* becomes an <xsl:apply-templates/> node. A second optional argument can be passed to be used
|
||||
* as its @select node-set.
|
||||
*
|
||||
* @param string $template Original template
|
||||
* @param string $regexp Regexp for matching parts that need replacement
|
||||
* @param callback $fn Callback used to get the replacement
|
||||
* @return string Processed template
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a node that implements given replacement strategy
|
||||
*
|
||||
* @param DOMDocument $dom
|
||||
* @param array $replacement
|
||||
* @return DOMNode
|
||||
*/
|
||||
protected static function createReplacementNode(DOMDocument $dom, array $replacement)
|
||||
{
|
||||
if ($replacement[0] === 'expression')
|
||||
@@ -34,49 +76,89 @@ abstract class TemplateModifier
|
||||
{
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace parts of an attribute that match given regexp
|
||||
*
|
||||
* @param DOMAttr $attribute Attribute
|
||||
* @param string $regexp Regexp for matching parts that need replacement
|
||||
* @param callback $fn Callback used to get the replacement
|
||||
* @return void
|
||||
*/
|
||||
protected static function replaceTokensInAttribute(DOMAttr $attribute, $regexp, $fn)
|
||||
{
|
||||
$attrValue = \preg_replace_callback(
|
||||
$attrValue = preg_replace_callback(
|
||||
$regexp,
|
||||
function ($m) use ($fn, $attribute)
|
||||
{
|
||||
$replacement = $fn($m, $attribute);
|
||||
if ($replacement[0] === 'expression' || $replacement[0] === 'passthrough')
|
||||
{
|
||||
// Use the node's text content as the default expression
|
||||
$replacement[] = '.';
|
||||
|
||||
return '{' . $replacement[1] . '}';
|
||||
}
|
||||
else
|
||||
{
|
||||
return $replacement[1];
|
||||
}
|
||||
},
|
||||
$attribute->value
|
||||
);
|
||||
$attribute->value = \htmlspecialchars($attrValue, \ENT_COMPAT, 'UTF-8');
|
||||
$attribute->value = htmlspecialchars($attrValue, ENT_COMPAT, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace parts of a text node that match given regexp
|
||||
*
|
||||
* @param DOMText $node Text node
|
||||
* @param string $regexp Regexp for matching parts that need replacement
|
||||
* @param callback $fn Callback used to get the replacement
|
||||
* @return void
|
||||
*/
|
||||
protected static function replaceTokensInText(DOMText $node, $regexp, $fn)
|
||||
{
|
||||
// Grab the node's parent so that we can rebuild the text with added variables right
|
||||
// before the node, using DOM's insertBefore(). Technically, it would make more sense
|
||||
// to create a document fragment, append nodes then replace the node with the fragment
|
||||
// but it leads to namespace redeclarations, which looks ugly
|
||||
$parentNode = $node->parentNode;
|
||||
$dom = $node->ownerDocument;
|
||||
\preg_match_all($regexp, $node->textContent, $matches, \PREG_SET_ORDER | \PREG_OFFSET_CAPTURE);
|
||||
|
||||
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);
|
||||
|
||||
// Catch-up to current position
|
||||
$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);
|
||||
$lastPos = $pos + strlen($m[0][0]);
|
||||
|
||||
// Get the replacement for this token
|
||||
$replacement = $fn(array_column($m, 0), $node);
|
||||
$newNode = self::createReplacementNode($dom, $replacement);
|
||||
$parentNode->insertBefore($newNode, $node);
|
||||
}
|
||||
$text = \substr($node->textContent, $lastPos);
|
||||
|
||||
// Append the rest of the text
|
||||
$text = substr($node->textContent, $lastPos);
|
||||
$parentNode->insertBefore($dom->createTextNode($text), $node);
|
||||
|
||||
// Now remove the old text node
|
||||
$parentNode->removeChild($node);
|
||||
}
|
||||
}
|
||||
39
vendor/s9e/text-formatter/src/Configurator/Helpers/TemplateParser.php
vendored
Normal file
39
vendor/s9e/text-formatter/src/Configurator/Helpers/TemplateParser.php
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
<?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 s9e\TextFormatter\Configurator\Helpers\TemplateParser\Normalizer;
|
||||
use s9e\TextFormatter\Configurator\Helpers\TemplateParser\Optimizer;
|
||||
use s9e\TextFormatter\Configurator\Helpers\TemplateParser\Parser;
|
||||
|
||||
class TemplateParser
|
||||
{
|
||||
/**
|
||||
* XSL namespace
|
||||
*/
|
||||
const XMLNS_XSL = 'http://www.w3.org/1999/XSL/Transform';
|
||||
|
||||
/**
|
||||
* @var string Regexp that matches the names of all void elements
|
||||
* @link http://www.w3.org/TR/html-markup/syntax.html#void-elements
|
||||
*/
|
||||
public static $voidRegexp = '/^(?:area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/Di';
|
||||
|
||||
/**
|
||||
* Parse a template into an internal representation
|
||||
*
|
||||
* @param string $template Source template
|
||||
* @return DOMDocument Internal representation
|
||||
*/
|
||||
public static function parse($template)
|
||||
{
|
||||
$parser = new Parser(new Normalizer(new Optimizer));
|
||||
|
||||
return $parser->parse($template);
|
||||
}
|
||||
}
|
||||
74
vendor/s9e/text-formatter/src/Configurator/Helpers/TemplateParser/IRProcessor.php
vendored
Normal file
74
vendor/s9e/text-formatter/src/Configurator/Helpers/TemplateParser/IRProcessor.php
vendored
Normal 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\Helpers\TemplateParser;
|
||||
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use DOMNode;
|
||||
use DOMXPath;
|
||||
|
||||
abstract class IRProcessor
|
||||
{
|
||||
/**
|
||||
* XSL namespace
|
||||
*/
|
||||
const XMLNS_XSL = 'http://www.w3.org/1999/XSL/Transform';
|
||||
|
||||
/**
|
||||
* @var DOMXPath
|
||||
*/
|
||||
protected $xpath;
|
||||
|
||||
/**
|
||||
* Create and append an element to given node in the IR
|
||||
*
|
||||
* @param DOMElement $parentNode Parent node of the element
|
||||
* @param string $name Tag name of the element
|
||||
* @param string $value Value of the element
|
||||
* @return DOMElement The created element
|
||||
*/
|
||||
protected function appendElement(DOMElement $parentNode, $name, $value = '')
|
||||
{
|
||||
return $parentNode->appendChild($parentNode->ownerDocument->createElement($name, $value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and store an instance of DOMXPath for given document
|
||||
*
|
||||
* @param DOMDocument $dom
|
||||
* @return void
|
||||
*/
|
||||
protected function createXPath(DOMDocument $dom)
|
||||
{
|
||||
$this->xpath = new DOMXPath($dom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate an XPath expression and return its result
|
||||
*
|
||||
* @param string $expr XPath expression
|
||||
* @param DOMNode $node Context node
|
||||
* @return mixed
|
||||
*/
|
||||
protected function evaluate($expr, DOMNode $node = null)
|
||||
{
|
||||
return (isset($node)) ? $this->xpath->evaluate($expr, $node) : $this->xpath->evaluate($expr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an XPath query and return its result
|
||||
*
|
||||
* @param string $query XPath query
|
||||
* @param DOMNode $node Context node
|
||||
* @return \DOMNodeList
|
||||
*/
|
||||
protected function query($query, DOMNode $node = null)
|
||||
{
|
||||
return (isset($node)) ? $this->xpath->query($query, $node) : $this->xpath->query($query);
|
||||
}
|
||||
}
|
||||
282
vendor/s9e/text-formatter/src/Configurator/Helpers/TemplateParser/Normalizer.php
vendored
Normal file
282
vendor/s9e/text-formatter/src/Configurator/Helpers/TemplateParser/Normalizer.php
vendored
Normal file
@@ -0,0 +1,282 @@
|
||||
<?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\TemplateParser;
|
||||
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use DOMNode;
|
||||
use s9e\TextFormatter\Configurator\Helpers\XPathHelper;
|
||||
|
||||
class Normalizer extends IRProcessor
|
||||
{
|
||||
/**
|
||||
* @var Optimizer
|
||||
*/
|
||||
protected $optimizer;
|
||||
|
||||
/**
|
||||
* @var string Regexp that matches the names of all void elements
|
||||
* @link http://www.w3.org/TR/html-markup/syntax.html#void-elements
|
||||
*/
|
||||
public $voidRegexp = '/^(?:area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/Di';
|
||||
|
||||
/**
|
||||
* @param Optimizer $optimizer
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Optimizer $optimizer)
|
||||
{
|
||||
$this->optimizer = $optimizer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize an IR
|
||||
*
|
||||
* @param DOMDocument $ir
|
||||
* @return void
|
||||
*/
|
||||
public function normalize(DOMDocument $ir)
|
||||
{
|
||||
$this->createXPath($ir);
|
||||
$this->addDefaultCase($ir);
|
||||
$this->addElementIds($ir);
|
||||
$this->addCloseTagElements($ir);
|
||||
$this->markVoidElements($ir);
|
||||
$this->optimizer->optimize($ir);
|
||||
$this->markConditionalCloseTagElements($ir);
|
||||
$this->setOutputContext($ir);
|
||||
$this->markBranchTables($ir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add <closeTag/> elements everywhere an open start tag should be closed
|
||||
*
|
||||
* @param DOMDocument $ir
|
||||
* @return void
|
||||
*/
|
||||
protected function addCloseTagElements(DOMDocument $ir)
|
||||
{
|
||||
$exprs = [
|
||||
'//applyTemplates[not(ancestor::attribute)]',
|
||||
'//comment',
|
||||
'//element',
|
||||
'//output[not(ancestor::attribute)]'
|
||||
];
|
||||
foreach ($this->query(implode('|', $exprs)) as $node)
|
||||
{
|
||||
$parentElementId = $this->getParentElementId($node);
|
||||
if (isset($parentElementId))
|
||||
{
|
||||
$node->parentNode
|
||||
->insertBefore($ir->createElement('closeTag'), $node)
|
||||
->setAttribute('id', $parentElementId);
|
||||
}
|
||||
|
||||
// Append a <closeTag/> to <element/> nodes to ensure that empty elements get closed
|
||||
if ($node->nodeName === 'element')
|
||||
{
|
||||
$id = $node->getAttribute('id');
|
||||
$this->appendElement($node, 'closeTag')->setAttribute('id', $id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an empty default <case/> to <switch/> nodes that don't have one
|
||||
*
|
||||
* @param DOMDocument $ir
|
||||
* @return void
|
||||
*/
|
||||
protected function addDefaultCase(DOMDocument $ir)
|
||||
{
|
||||
foreach ($this->query('//switch[not(case[not(@test)])]') as $switch)
|
||||
{
|
||||
$this->appendElement($switch, 'case');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an id attribute to <element/> nodes
|
||||
*
|
||||
* @param DOMDocument $ir
|
||||
* @return void
|
||||
*/
|
||||
protected function addElementIds(DOMDocument $ir)
|
||||
{
|
||||
$id = 0;
|
||||
foreach ($this->query('//element') as $element)
|
||||
{
|
||||
$element->setAttribute('id', ++$id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the context type for given output element
|
||||
*
|
||||
* @param DOMNode $output
|
||||
* @return string
|
||||
*/
|
||||
protected function getOutputContext(DOMNode $output)
|
||||
{
|
||||
$contexts = [
|
||||
'boolean(ancestor::attribute)' => 'attribute',
|
||||
'@disable-output-escaping="yes"' => 'raw',
|
||||
'count(ancestor::element[@name="script"])' => 'raw'
|
||||
];
|
||||
foreach ($contexts as $expr => $context)
|
||||
{
|
||||
if ($this->evaluate($expr, $output))
|
||||
{
|
||||
return $context;
|
||||
}
|
||||
}
|
||||
|
||||
return 'text';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID of the closest "element" ancestor
|
||||
*
|
||||
* @param DOMNode $node Context node
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getParentElementId(DOMNode $node)
|
||||
{
|
||||
$parentNode = $node->parentNode;
|
||||
while (isset($parentNode))
|
||||
{
|
||||
if ($parentNode->nodeName === 'element')
|
||||
{
|
||||
return $parentNode->getAttribute('id');
|
||||
}
|
||||
$parentNode = $parentNode->parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark switch elements that are used as branch tables
|
||||
*
|
||||
* If a switch is used for a series of equality tests against the same attribute or variable, the
|
||||
* attribute/variable is stored within the switch as "branch-key" and the values it is compared
|
||||
* against are stored JSON-encoded in the case as "branch-values". It can be used to create
|
||||
* optimized branch tables
|
||||
*
|
||||
* @param DOMDocument $ir
|
||||
* @return void
|
||||
*/
|
||||
protected function markBranchTables(DOMDocument $ir)
|
||||
{
|
||||
// Iterate over switch elements that have at least two case children with a test attribute
|
||||
foreach ($this->query('//switch[case[2][@test]]') as $switch)
|
||||
{
|
||||
$this->markSwitchTable($switch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark given switch element if it's used as a branch table
|
||||
*
|
||||
* @param DOMElement $switch
|
||||
* @return void
|
||||
*/
|
||||
protected function markSwitchTable(DOMElement $switch)
|
||||
{
|
||||
$cases = [];
|
||||
$maps = [];
|
||||
foreach ($this->query('./case[@test]', $switch) as $i => $case)
|
||||
{
|
||||
$map = XPathHelper::parseEqualityExpr($case->getAttribute('test'));
|
||||
if ($map === false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
$maps += $map;
|
||||
$cases[$i] = [$case, end($map)];
|
||||
}
|
||||
if (count($maps) !== 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$switch->setAttribute('branch-key', key($maps));
|
||||
foreach ($cases as list($case, $values))
|
||||
{
|
||||
sort($values);
|
||||
$case->setAttribute('branch-values', serialize($values));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark conditional <closeTag/> nodes
|
||||
*
|
||||
* @param DOMDocument $ir
|
||||
* @return void
|
||||
*/
|
||||
protected function markConditionalCloseTagElements(DOMDocument $ir)
|
||||
{
|
||||
foreach ($this->query('//closeTag') as $closeTag)
|
||||
{
|
||||
$id = $closeTag->getAttribute('id');
|
||||
|
||||
// For each <switch/> ancestor, look for a <closeTag/> and that is either a sibling or
|
||||
// the descendant of a sibling, and that matches the id
|
||||
$query = 'ancestor::switch/'
|
||||
. 'following-sibling::*/'
|
||||
. 'descendant-or-self::closeTag[@id = "' . $id . '"]';
|
||||
foreach ($this->query($query, $closeTag) as $following)
|
||||
{
|
||||
// Mark following <closeTag/> nodes to indicate that the status of this tag must
|
||||
// be checked before it is closed
|
||||
$following->setAttribute('check', '');
|
||||
|
||||
// Mark the current <closeTag/> to indicate that it must set a flag to indicate
|
||||
// that its tag has been closed
|
||||
$closeTag->setAttribute('set', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark void elements
|
||||
*
|
||||
* @param DOMDocument $ir
|
||||
* @return void
|
||||
*/
|
||||
protected function markVoidElements(DOMDocument $ir)
|
||||
{
|
||||
foreach ($this->query('//element') as $element)
|
||||
{
|
||||
// Test whether this element is (maybe) void
|
||||
$elName = $element->getAttribute('name');
|
||||
if (strpos($elName, '{') !== false)
|
||||
{
|
||||
// Dynamic element names must be checked at runtime
|
||||
$element->setAttribute('void', 'maybe');
|
||||
}
|
||||
elseif (preg_match($this->voidRegexp, $elName))
|
||||
{
|
||||
// Static element names can be checked right now
|
||||
$element->setAttribute('void', 'yes');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill in output context
|
||||
*
|
||||
* @param DOMDocument $ir
|
||||
* @return void
|
||||
*/
|
||||
protected function setOutputContext(DOMDocument $ir)
|
||||
{
|
||||
foreach ($this->query('//output') as $output)
|
||||
{
|
||||
$output->setAttribute('escape', $this->getOutputContext($output));
|
||||
}
|
||||
}
|
||||
}
|
||||
244
vendor/s9e/text-formatter/src/Configurator/Helpers/TemplateParser/Optimizer.php
vendored
Normal file
244
vendor/s9e/text-formatter/src/Configurator/Helpers/TemplateParser/Optimizer.php
vendored
Normal file
@@ -0,0 +1,244 @@
|
||||
<?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\TemplateParser;
|
||||
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use DOMNode;
|
||||
|
||||
class Optimizer extends IRProcessor
|
||||
{
|
||||
/**
|
||||
* Optimize an IR
|
||||
*
|
||||
* @param DOMDocument $ir
|
||||
* @return void
|
||||
*/
|
||||
public function optimize(DOMDocument $ir)
|
||||
{
|
||||
$this->createXPath($ir);
|
||||
|
||||
// Get a snapshot of current internal representation
|
||||
$xml = $ir->saveXML();
|
||||
|
||||
// Set a maximum number of loops to ward against infinite loops
|
||||
$remainingLoops = 10;
|
||||
|
||||
// From now on, keep looping until no further modifications are applied
|
||||
do
|
||||
{
|
||||
$old = $xml;
|
||||
$this->optimizeCloseTagElements($ir);
|
||||
$xml = $ir->saveXML();
|
||||
}
|
||||
while (--$remainingLoops > 0 && $xml !== $old);
|
||||
|
||||
$this->removeCloseTagSiblings($ir);
|
||||
$this->removeContentFromVoidElements($ir);
|
||||
$this->mergeConsecutiveLiteralOutputElements($ir);
|
||||
$this->removeEmptyDefaultCases($ir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone closeTag elements that follow a switch into said switch
|
||||
*
|
||||
* If there's a <closeTag/> right after a <switch/>, clone the <closeTag/> at the end of
|
||||
* the every <case/> that does not end with a <closeTag/>
|
||||
*
|
||||
* @param DOMDocument $ir
|
||||
* @return void
|
||||
*/
|
||||
protected function cloneCloseTagElementsIntoSwitch(DOMDocument $ir)
|
||||
{
|
||||
$query = '//switch[name(following-sibling::*[1]) = "closeTag"]';
|
||||
foreach ($this->query($query) as $switch)
|
||||
{
|
||||
$closeTag = $switch->nextSibling;
|
||||
foreach ($this->query('case', $switch) as $case)
|
||||
{
|
||||
if (!$case->lastChild || $case->lastChild->nodeName !== 'closeTag')
|
||||
{
|
||||
$case->appendChild($closeTag->cloneNode());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone closeTag elements from the head of a switch's cases before said switch
|
||||
*
|
||||
* If there's a <closeTag/> at the beginning of every <case/>, clone it and insert it
|
||||
* right before the <switch/> unless there's already one
|
||||
*
|
||||
* @param DOMDocument $ir
|
||||
* @return void
|
||||
*/
|
||||
protected function cloneCloseTagElementsOutOfSwitch(DOMDocument $ir)
|
||||
{
|
||||
$query = '//switch[case/closeTag][not(case[name(*[1]) != "closeTag"])]';
|
||||
foreach ($this->query($query) as $switch)
|
||||
{
|
||||
$case = $this->query('case/closeTag', $switch)->item(0);
|
||||
$switch->parentNode->insertBefore($case->cloneNode(), $switch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge consecutive literal outputs
|
||||
*
|
||||
* @param DOMDocument $ir
|
||||
* @return void
|
||||
*/
|
||||
protected function mergeConsecutiveLiteralOutputElements(DOMDocument $ir)
|
||||
{
|
||||
foreach ($this->query('//output[@type="literal"]') as $output)
|
||||
{
|
||||
$disableOutputEscaping = $output->getAttribute('disable-output-escaping');
|
||||
while ($this->nextSiblingIsLiteralOutput($output, $disableOutputEscaping))
|
||||
{
|
||||
$output->nodeValue = htmlspecialchars($output->nodeValue . $output->nextSibling->nodeValue);
|
||||
$output->parentNode->removeChild($output->nextSibling);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the next sibling of an element is a literal output element with matching escaping
|
||||
*
|
||||
* @param DOMElement $node
|
||||
* @param string $disableOutputEscaping
|
||||
* @return bool
|
||||
*/
|
||||
protected function nextSiblingIsLiteralOutput(DOMElement $node, $disableOutputEscaping)
|
||||
{
|
||||
return isset($node->nextSibling) && $node->nextSibling->nodeName === 'output' && $node->nextSibling->getAttribute('type') === 'literal' && $node->nextSibling->getAttribute('disable-output-escaping') === $disableOutputEscaping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimize closeTags elements
|
||||
*
|
||||
* @param DOMDocument $ir
|
||||
* @return void
|
||||
*/
|
||||
protected function optimizeCloseTagElements(DOMDocument $ir)
|
||||
{
|
||||
$this->cloneCloseTagElementsIntoSwitch($ir);
|
||||
$this->cloneCloseTagElementsOutOfSwitch($ir);
|
||||
$this->removeRedundantCloseTagElementsInSwitch($ir);
|
||||
$this->removeRedundantCloseTagElements($ir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove redundant closeTag siblings after a switch
|
||||
*
|
||||
* If all branches of a switch have a closeTag we can remove any closeTag siblings of the switch
|
||||
*
|
||||
* @param DOMDocument $ir
|
||||
* @return void
|
||||
*/
|
||||
protected function removeCloseTagSiblings(DOMDocument $ir)
|
||||
{
|
||||
$query = '//switch[not(case[not(closeTag)])]/following-sibling::closeTag';
|
||||
$this->removeNodes($ir, $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove content from void elements
|
||||
*
|
||||
* For each void element, we find whichever <closeTag/> elements close it and remove everything
|
||||
* after
|
||||
*
|
||||
* @param DOMDocument $ir
|
||||
* @return void
|
||||
*/
|
||||
protected function removeContentFromVoidElements(DOMDocument $ir)
|
||||
{
|
||||
foreach ($this->query('//element[@void="yes"]') as $element)
|
||||
{
|
||||
$id = $element->getAttribute('id');
|
||||
$query = './/closeTag[@id="' . $id . '"]/following-sibling::*';
|
||||
|
||||
$this->removeNodes($ir, $query, $element);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove empty default cases (no test and no descendants)
|
||||
*
|
||||
* @param DOMDocument $ir
|
||||
* @return void
|
||||
*/
|
||||
protected function removeEmptyDefaultCases(DOMDocument $ir)
|
||||
{
|
||||
$query = '//case[not(@test)][not(*)][. = ""]';
|
||||
$this->removeNodes($ir, $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all nodes that match given XPath query
|
||||
*
|
||||
* @param DOMDocument $ir
|
||||
* @param string $query
|
||||
* @param DOMNode $contextNode
|
||||
* @return void
|
||||
*/
|
||||
protected function removeNodes(DOMDocument $ir, $query, DOMNode $contextNode = null)
|
||||
{
|
||||
foreach ($this->query($query, $contextNode) as $node)
|
||||
{
|
||||
if ($node->parentNode instanceof DOMElement)
|
||||
{
|
||||
$node->parentNode->removeChild($node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove redundant closeTag elements from the tail of a switch's cases
|
||||
*
|
||||
* For each <closeTag/> remove duplicate <closeTag/> nodes that are either siblings or
|
||||
* descendants of a sibling
|
||||
*
|
||||
* @param DOMDocument $ir
|
||||
* @return void
|
||||
*/
|
||||
protected function removeRedundantCloseTagElements(DOMDocument $ir)
|
||||
{
|
||||
foreach ($this->query('//closeTag') as $closeTag)
|
||||
{
|
||||
$id = $closeTag->getAttribute('id');
|
||||
$query = 'following-sibling::*/descendant-or-self::closeTag[@id="' . $id . '"]';
|
||||
|
||||
$this->removeNodes($ir, $query, $closeTag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove redundant closeTag elements from the tail of a switch's cases
|
||||
*
|
||||
* If there's a <closeTag/> right after a <switch/>, remove all <closeTag/> nodes at the
|
||||
* end of every <case/>
|
||||
*
|
||||
* @param DOMDocument $ir
|
||||
* @return void
|
||||
*/
|
||||
protected function removeRedundantCloseTagElementsInSwitch(DOMDocument $ir)
|
||||
{
|
||||
$query = '//switch[name(following-sibling::*[1]) = "closeTag"]';
|
||||
foreach ($this->query($query) as $switch)
|
||||
{
|
||||
foreach ($this->query('case', $switch) as $case)
|
||||
{
|
||||
while ($case->lastChild && $case->lastChild->nodeName === 'closeTag')
|
||||
{
|
||||
$case->removeChild($case->lastChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
381
vendor/s9e/text-formatter/src/Configurator/Helpers/TemplateParser/Parser.php
vendored
Normal file
381
vendor/s9e/text-formatter/src/Configurator/Helpers/TemplateParser/Parser.php
vendored
Normal file
@@ -0,0 +1,381 @@
|
||||
<?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\TemplateParser;
|
||||
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use DOMXPath;
|
||||
use RuntimeException;
|
||||
use s9e\TextFormatter\Configurator\Helpers\AVTHelper;
|
||||
use s9e\TextFormatter\Configurator\Helpers\TemplateLoader;
|
||||
|
||||
class Parser extends IRProcessor
|
||||
{
|
||||
/**
|
||||
* @var Normalizer
|
||||
*/
|
||||
protected $normalizer;
|
||||
|
||||
/**
|
||||
* @param Normalizer $normalizer
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Normalizer $normalizer)
|
||||
{
|
||||
$this->normalizer = $normalizer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a template into an internal representation
|
||||
*
|
||||
* @param string $template Source template
|
||||
* @return DOMDocument Internal representation
|
||||
*/
|
||||
public function parse($template)
|
||||
{
|
||||
$dom = TemplateLoader::load($template);
|
||||
|
||||
$ir = new DOMDocument;
|
||||
$ir->loadXML('<template/>');
|
||||
|
||||
$this->createXPath($dom);
|
||||
$this->parseChildren($ir->documentElement, $dom->documentElement);
|
||||
$this->normalizer->normalize($ir);
|
||||
|
||||
return $ir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append <output/> elements corresponding to given AVT
|
||||
*
|
||||
* @param DOMElement $parentNode Parent node
|
||||
* @param string $avt Attribute value template
|
||||
* @return void
|
||||
*/
|
||||
protected function appendAVT(DOMElement $parentNode, $avt)
|
||||
{
|
||||
foreach (AVTHelper::parse($avt) as $token)
|
||||
{
|
||||
if ($token[0] === 'expression')
|
||||
{
|
||||
$this->appendXPathOutput($parentNode, $token[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->appendLiteralOutput($parentNode, $token[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Append an <output/> element with literal content to given node
|
||||
*
|
||||
* @param DOMElement $parentNode Parent node
|
||||
* @param string $content Content to output
|
||||
* @return void
|
||||
*/
|
||||
protected function appendLiteralOutput(DOMElement $parentNode, $content)
|
||||
{
|
||||
if ($content === '')
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->appendElement($parentNode, 'output', htmlspecialchars($content))
|
||||
->setAttribute('type', 'literal');
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the structure for a <xsl:copy-of/> element to given node
|
||||
*
|
||||
* @param DOMElement $parentNode Parent node
|
||||
* @param string $expr Select expression, which is should only contain attributes
|
||||
* @return void
|
||||
*/
|
||||
protected function appendConditionalAttributes(DOMElement $parentNode, $expr)
|
||||
{
|
||||
preg_match_all('(@([-\\w]+))', $expr, $matches);
|
||||
foreach ($matches[1] as $attrName)
|
||||
{
|
||||
// Create a switch element in the IR
|
||||
$switch = $this->appendElement($parentNode, 'switch');
|
||||
$case = $this->appendElement($switch, 'case');
|
||||
$case->setAttribute('test', '@' . $attrName);
|
||||
|
||||
// Append an attribute element
|
||||
$attribute = $this->appendElement($case, 'attribute');
|
||||
$attribute->setAttribute('name', $attrName);
|
||||
|
||||
// Set the attribute's content, which is simply the copied attribute's value
|
||||
$this->appendXPathOutput($attribute, '@' . $attrName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Append an <output/> element for given XPath expression to given node
|
||||
*
|
||||
* @param DOMElement $parentNode Parent node
|
||||
* @param string $expr XPath expression
|
||||
* @return void
|
||||
*/
|
||||
protected function appendXPathOutput(DOMElement $parentNode, $expr)
|
||||
{
|
||||
$this->appendElement($parentNode, 'output', htmlspecialchars(trim($expr)))
|
||||
->setAttribute('type', 'xpath');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse all the children of a given element
|
||||
*
|
||||
* @param DOMElement $ir Node in the internal representation that represents the parent node
|
||||
* @param DOMElement $parent Parent node
|
||||
* @return void
|
||||
*/
|
||||
protected function parseChildren(DOMElement $ir, DOMElement $parent)
|
||||
{
|
||||
foreach ($parent->childNodes as $child)
|
||||
{
|
||||
switch ($child->nodeType)
|
||||
{
|
||||
case XML_COMMENT_NODE:
|
||||
// Do nothing
|
||||
break;
|
||||
|
||||
case XML_TEXT_NODE:
|
||||
if (trim($child->textContent) !== '')
|
||||
{
|
||||
$this->appendLiteralOutput($ir, $child->textContent);
|
||||
}
|
||||
break;
|
||||
|
||||
case XML_ELEMENT_NODE:
|
||||
$this->parseNode($ir, $child);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Cannot parse node '" . $child->nodeName . "''");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a given node into the internal representation
|
||||
*
|
||||
* @param DOMElement $ir Node in the internal representation that represents the node's parent
|
||||
* @param DOMElement $node Node to parse
|
||||
* @return void
|
||||
*/
|
||||
protected function parseNode(DOMElement $ir, DOMElement $node)
|
||||
{
|
||||
// XSL elements are parsed by the corresponding parseXsl* method
|
||||
if ($node->namespaceURI === self::XMLNS_XSL)
|
||||
{
|
||||
$methodName = 'parseXsl' . str_replace(' ', '', ucwords(str_replace('-', ' ', $node->localName)));
|
||||
if (!method_exists($this, $methodName))
|
||||
{
|
||||
throw new RuntimeException("Element '" . $node->nodeName . "' is not supported");
|
||||
}
|
||||
|
||||
return $this->$methodName($ir, $node);
|
||||
}
|
||||
|
||||
// Create an <element/> with a name attribute equal to given node's name
|
||||
$element = $this->appendElement($ir, 'element');
|
||||
$element->setAttribute('name', $node->nodeName);
|
||||
|
||||
// Append an <attribute/> element for each namespace declaration
|
||||
$xpath = new DOMXPath($node->ownerDocument);
|
||||
foreach ($xpath->query('namespace::*', $node) as $ns)
|
||||
{
|
||||
if ($node->hasAttribute($ns->nodeName))
|
||||
{
|
||||
$irAttribute = $this->appendElement($element, 'attribute');
|
||||
$irAttribute->setAttribute('name', $ns->nodeName);
|
||||
$this->appendLiteralOutput($irAttribute, $ns->nodeValue);
|
||||
}
|
||||
}
|
||||
|
||||
// Append an <attribute/> element for each of this node's attribute
|
||||
foreach ($node->attributes as $attribute)
|
||||
{
|
||||
$irAttribute = $this->appendElement($element, 'attribute');
|
||||
$irAttribute->setAttribute('name', $attribute->nodeName);
|
||||
|
||||
// Append an <output/> element to represent the attribute's value
|
||||
$this->appendAVT($irAttribute, $attribute->value);
|
||||
}
|
||||
|
||||
// Parse the content of this node
|
||||
$this->parseChildren($element, $node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an <xsl:apply-templates/> node into the internal representation
|
||||
*
|
||||
* @param DOMElement $ir Node in the internal representation that represents the node's parent
|
||||
* @param DOMElement $node <xsl:apply-templates/> node
|
||||
* @return void
|
||||
*/
|
||||
protected function parseXslApplyTemplates(DOMElement $ir, DOMElement $node)
|
||||
{
|
||||
$applyTemplates = $this->appendElement($ir, 'applyTemplates');
|
||||
if ($node->hasAttribute('select'))
|
||||
{
|
||||
$applyTemplates->setAttribute('select', $node->getAttribute('select'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an <xsl:attribute/> node into the internal representation
|
||||
*
|
||||
* @param DOMElement $ir Node in the internal representation that represents the node's parent
|
||||
* @param DOMElement $node <xsl:attribute/> node
|
||||
* @return void
|
||||
*/
|
||||
protected function parseXslAttribute(DOMElement $ir, DOMElement $node)
|
||||
{
|
||||
$attribute = $this->appendElement($ir, 'attribute');
|
||||
$attribute->setAttribute('name', $node->getAttribute('name'));
|
||||
$this->parseChildren($attribute, $node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an <xsl:choose/> node and its <xsl:when/> and <xsl:otherwise/> children into the
|
||||
* internal representation
|
||||
*
|
||||
* @param DOMElement $ir Node in the internal representation that represents the node's parent
|
||||
* @param DOMElement $node <xsl:choose/> node
|
||||
* @return void
|
||||
*/
|
||||
protected function parseXslChoose(DOMElement $ir, DOMElement $node)
|
||||
{
|
||||
$switch = $this->appendElement($ir, 'switch');
|
||||
foreach ($this->query('./xsl:when', $node) as $when)
|
||||
{
|
||||
// Create a <case/> element with the original test condition in @test
|
||||
$case = $this->appendElement($switch, 'case');
|
||||
$case->setAttribute('test', $when->getAttribute('test'));
|
||||
$this->parseChildren($case, $when);
|
||||
}
|
||||
|
||||
// Add the default branch, which is presumed to be last
|
||||
foreach ($this->query('./xsl:otherwise', $node) as $otherwise)
|
||||
{
|
||||
$case = $this->appendElement($switch, 'case');
|
||||
$this->parseChildren($case, $otherwise);
|
||||
|
||||
// There should be only one <xsl:otherwise/> but we'll break anyway
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an <xsl:comment/> node into the internal representation
|
||||
*
|
||||
* @param DOMElement $ir Node in the internal representation that represents the node's parent
|
||||
* @param DOMElement $node <xsl:comment/> node
|
||||
* @return void
|
||||
*/
|
||||
protected function parseXslComment(DOMElement $ir, DOMElement $node)
|
||||
{
|
||||
$comment = $this->appendElement($ir, 'comment');
|
||||
$this->parseChildren($comment, $node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an <xsl:copy-of/> node into the internal representation
|
||||
*
|
||||
* NOTE: only attributes are supported
|
||||
*
|
||||
* @param DOMElement $ir Node in the internal representation that represents the node's parent
|
||||
* @param DOMElement $node <xsl:copy-of/> node
|
||||
* @return void
|
||||
*/
|
||||
protected function parseXslCopyOf(DOMElement $ir, DOMElement $node)
|
||||
{
|
||||
$expr = $node->getAttribute('select');
|
||||
if (preg_match('#^@[-\\w]+(?:\\s*\\|\\s*@[-\\w]+)*$#', $expr, $m))
|
||||
{
|
||||
// <xsl:copy-of select="@foo"/>
|
||||
$this->appendConditionalAttributes($ir, $expr);
|
||||
}
|
||||
elseif ($expr === '@*')
|
||||
{
|
||||
// <xsl:copy-of select="@*"/>
|
||||
$this->appendElement($ir, 'copyOfAttributes');
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RuntimeException("Unsupported <xsl:copy-of/> expression '" . $expr . "'");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an <xsl:element/> node into the internal representation
|
||||
*
|
||||
* @param DOMElement $ir Node in the internal representation that represents the node's parent
|
||||
* @param DOMElement $node <xsl:element/> node
|
||||
* @return void
|
||||
*/
|
||||
protected function parseXslElement(DOMElement $ir, DOMElement $node)
|
||||
{
|
||||
$element = $this->appendElement($ir, 'element');
|
||||
$element->setAttribute('name', $node->getAttribute('name'));
|
||||
$this->parseChildren($element, $node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an <xsl:if/> node into the internal representation
|
||||
*
|
||||
* @param DOMElement $ir Node in the internal representation that represents the node's parent
|
||||
* @param DOMElement $node <xsl:if/> node
|
||||
* @return void
|
||||
*/
|
||||
protected function parseXslIf(DOMElement $ir, DOMElement $node)
|
||||
{
|
||||
// An <xsl:if/> is represented by a <switch/> with only one <case/>
|
||||
$switch = $this->appendElement($ir, 'switch');
|
||||
$case = $this->appendElement($switch, 'case');
|
||||
$case->setAttribute('test', $node->getAttribute('test'));
|
||||
|
||||
// Parse this branch's content
|
||||
$this->parseChildren($case, $node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an <xsl:text/> node into the internal representation
|
||||
*
|
||||
* @param DOMElement $ir Node in the internal representation that represents the node's parent
|
||||
* @param DOMElement $node <xsl:text/> node
|
||||
* @return void
|
||||
*/
|
||||
protected function parseXslText(DOMElement $ir, DOMElement $node)
|
||||
{
|
||||
$this->appendLiteralOutput($ir, $node->textContent);
|
||||
if ($node->getAttribute('disable-output-escaping') === 'yes')
|
||||
{
|
||||
$ir->lastChild->setAttribute('disable-output-escaping', 'yes');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an <xsl:value-of/> node into the internal representation
|
||||
*
|
||||
* @param DOMElement $ir Node in the internal representation that represents the node's parent
|
||||
* @param DOMElement $node <xsl:value-of/> node
|
||||
* @return void
|
||||
*/
|
||||
protected function parseXslValueOf(DOMElement $ir, DOMElement $node)
|
||||
{
|
||||
$this->appendXPathOutput($ir, $node->getAttribute('select'));
|
||||
if ($node->getAttribute('disable-output-escaping') === 'yes')
|
||||
{
|
||||
$ir->lastChild->setAttribute('disable-output-escaping', 'yes');
|
||||
}
|
||||
}
|
||||
}
|
||||
264
vendor/s9e/text-formatter/src/Configurator/Helpers/XPathHelper.php
vendored
Normal file
264
vendor/s9e/text-formatter/src/Configurator/Helpers/XPathHelper.php
vendored
Normal file
@@ -0,0 +1,264 @@
|
||||
<?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;
|
||||
use s9e\TextFormatter\Configurator\RecursiveParser;
|
||||
use s9e\TextFormatter\Configurator\RendererGenerators\PHP\XPathConvertor\Convertors\BooleanFunctions;
|
||||
use s9e\TextFormatter\Configurator\RendererGenerators\PHP\XPathConvertor\Convertors\BooleanOperators;
|
||||
use s9e\TextFormatter\Configurator\RendererGenerators\PHP\XPathConvertor\Convertors\Comparisons;
|
||||
use s9e\TextFormatter\Configurator\RendererGenerators\PHP\XPathConvertor\Convertors\Core;
|
||||
use s9e\TextFormatter\Configurator\RendererGenerators\PHP\XPathConvertor\Convertors\Math;
|
||||
use s9e\TextFormatter\Configurator\RendererGenerators\PHP\XPathConvertor\Convertors\SingleByteStringFunctions;
|
||||
use s9e\TextFormatter\Utils\XPath;
|
||||
|
||||
abstract class XPathHelper
|
||||
{
|
||||
/**
|
||||
* Decode strings inside of an XPath expression
|
||||
*
|
||||
* @param string $expr
|
||||
* @return string
|
||||
*/
|
||||
public static function decodeStrings($expr)
|
||||
{
|
||||
return preg_replace_callback(
|
||||
'(([\'"])(.*?)\\1)s',
|
||||
function ($m)
|
||||
{
|
||||
return $m[1] . hex2bin($m[2]) . $m[1];
|
||||
},
|
||||
$expr
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode strings inside of an XPath expression
|
||||
*
|
||||
* @param string $expr
|
||||
* @return string
|
||||
*/
|
||||
public static function encodeStrings($expr)
|
||||
{
|
||||
return preg_replace_callback(
|
||||
'(([\'"])(.*?)\\1)s',
|
||||
function ($m)
|
||||
{
|
||||
return $m[1] . bin2hex($m[2]) . $m[1];
|
||||
},
|
||||
$expr
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of variables used in a given XPath expression
|
||||
*
|
||||
* @param string $expr XPath expression
|
||||
* @return array Alphabetically sorted list of unique variable names
|
||||
*/
|
||||
public static function getVariables($expr)
|
||||
{
|
||||
// First, remove strings' contents to prevent false-positives
|
||||
$expr = preg_replace('/(["\']).*?\\1/s', '$1$1', $expr);
|
||||
|
||||
// Capture all the variable names
|
||||
preg_match_all('/\\$(\\w+)/', $expr, $matches);
|
||||
|
||||
// Dedupe and sort names
|
||||
$varNames = array_unique($matches[1]);
|
||||
sort($varNames);
|
||||
|
||||
return $varNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether given XPath expression definitely evaluates to a number
|
||||
*
|
||||
* @param string $expr XPath expression
|
||||
* @return bool Whether given XPath expression definitely evaluates to a number
|
||||
*/
|
||||
public static function isExpressionNumeric($expr)
|
||||
{
|
||||
// Detect simple arithmetic operations
|
||||
if (preg_match('(^([$@][-\\w]++|-?[.\\d]++)(?: *(?:[-*+]|div) *(?1))+$)', $expr))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Try parsing the expression as a math expression
|
||||
try
|
||||
{
|
||||
return (bool) self::getXPathParser()->parse($expr, 'Math');
|
||||
}
|
||||
catch (RuntimeException $e)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove extraneous space in a given XPath expression
|
||||
*
|
||||
* @param string $expr Original XPath expression
|
||||
* @return string Minified XPath expression
|
||||
*/
|
||||
public static function minify($expr)
|
||||
{
|
||||
$old = $expr;
|
||||
$strings = [];
|
||||
|
||||
// Trim the surrounding whitespace then temporarily remove literal strings
|
||||
$expr = preg_replace_callback(
|
||||
'/"[^"]*"|\'[^\']*\'/',
|
||||
function ($m) use (&$strings)
|
||||
{
|
||||
$uniqid = '(' . sha1(uniqid()) . ')';
|
||||
$strings[$uniqid] = $m[0];
|
||||
|
||||
return $uniqid;
|
||||
},
|
||||
trim($expr)
|
||||
);
|
||||
|
||||
if (preg_match('/[\'"]/', $expr))
|
||||
{
|
||||
throw new RuntimeException("Cannot parse XPath expression '" . $old . "'");
|
||||
}
|
||||
|
||||
// Normalize whitespace to a single space
|
||||
$expr = preg_replace('/\\s+/', ' ', $expr);
|
||||
|
||||
// Remove the space between a non-word character and a word character
|
||||
$expr = preg_replace('/([-a-z_0-9]) ([^-a-z_0-9])/i', '$1$2', $expr);
|
||||
$expr = preg_replace('/([^-a-z_0-9]) ([-a-z_0-9])/i', '$1$2', $expr);
|
||||
|
||||
// Remove the space between two non-word characters as long as they're not two -
|
||||
$expr = preg_replace('/(?!- -)([^-a-z_0-9]) ([^-a-z_0-9])/i', '$1$2', $expr);
|
||||
|
||||
// Remove the space between a - and a word character, as long as there's a space before -
|
||||
$expr = preg_replace('/ - ([a-z_0-9])/i', ' -$1', $expr);
|
||||
|
||||
// Remove the spaces between a number and a div or "-" operator and the next token
|
||||
$expr = preg_replace('/(?:^|[ \\(])\\d+\\K (div|-) ?/', '$1', $expr);
|
||||
|
||||
// Remove the space between the div operator the next token
|
||||
$expr = preg_replace('/([^-a-z_0-9]div) (?=[$0-9@])/', '$1', $expr);
|
||||
|
||||
// Restore the literals
|
||||
$expr = strtr($expr, $strings);
|
||||
|
||||
return $expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an XPath expression that is composed entirely of equality tests between a variable part
|
||||
* and a constant part
|
||||
*
|
||||
* @param string $expr
|
||||
* @return array|false
|
||||
*/
|
||||
public static function parseEqualityExpr($expr)
|
||||
{
|
||||
// Match an equality between a variable and a literal or the concatenation of strings
|
||||
$eq = '(?<equality>'
|
||||
. '(?<key>@[-\\w]+|\\$\\w+|\\.)'
|
||||
. '(?<operator>\\s*=\\s*)'
|
||||
. '(?:'
|
||||
. '(?<literal>(?<string>"[^"]*"|\'[^\']*\')|0|[1-9][0-9]*)'
|
||||
. '|'
|
||||
. '(?<concat>concat\\(\\s*(?&string)\\s*(?:,\\s*(?&string)\\s*)+\\))'
|
||||
. ')'
|
||||
. '|'
|
||||
. '(?:(?<literal>(?&literal))|(?<concat>(?&concat)))(?&operator)(?<key>(?&key))'
|
||||
. ')';
|
||||
|
||||
// Match a string that is entirely composed of equality checks separated with "or"
|
||||
$regexp = '(^(?J)\\s*' . $eq . '\\s*(?:or\\s*(?&equality)\\s*)*$)';
|
||||
if (!preg_match($regexp, $expr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
preg_match_all("((?J)$eq)", $expr, $matches, PREG_SET_ORDER);
|
||||
|
||||
$map = [];
|
||||
foreach ($matches as $m)
|
||||
{
|
||||
$key = $m['key'];
|
||||
$value = (!empty($m['concat']))
|
||||
? self::evaluateConcat($m['concat'])
|
||||
: self::evaluateLiteral($m['literal']);
|
||||
|
||||
$map[$key][] = $value;
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a concat() expression where all arguments are string literals
|
||||
*
|
||||
* @param string $expr concat() expression
|
||||
* @return string Expression's value
|
||||
*/
|
||||
protected static function evaluateConcat($expr)
|
||||
{
|
||||
preg_match_all('(\'[^\']*\'|"[^"]*")', $expr, $strings);
|
||||
|
||||
$value = '';
|
||||
foreach ($strings[0] as $string)
|
||||
{
|
||||
$value .= substr($string, 1, -1);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate an XPath literal
|
||||
*
|
||||
* @param string $expr XPath literal
|
||||
* @return string Literal's string value
|
||||
*/
|
||||
protected static function evaluateLiteral($expr)
|
||||
{
|
||||
if ($expr[0] === '"' || $expr[0] === "'")
|
||||
{
|
||||
$expr = substr($expr, 1, -1);
|
||||
}
|
||||
|
||||
return $expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate and return a cached XPath parser with a default set of matchers
|
||||
*
|
||||
* @return RecursiveParser
|
||||
*/
|
||||
protected static function getXPathParser()
|
||||
{
|
||||
static $parser;
|
||||
if (!isset($parser))
|
||||
{
|
||||
$parser = new RecursiveParser;
|
||||
$matchers = [];
|
||||
$matchers[] = new BooleanFunctions($parser);
|
||||
$matchers[] = new BooleanOperators($parser);
|
||||
$matchers[] = new Comparisons($parser);
|
||||
$matchers[] = new Core($parser);
|
||||
$matchers[] = new Math($parser);
|
||||
$matchers[] = new SingleByteStringFunctions($parser);
|
||||
|
||||
$parser->setMatchers($matchers);
|
||||
}
|
||||
|
||||
return $parser;
|
||||
}
|
||||
}
|
||||
92
vendor/s9e/text-formatter/src/Configurator/Items/Attribute.php
vendored
Normal file
92
vendor/s9e/text-formatter/src/Configurator/Items/Attribute.php
vendored
Normal 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;
|
||||
|
||||
use s9e\TextFormatter\Configurator\Collections\AttributeFilterChain;
|
||||
use s9e\TextFormatter\Configurator\ConfigProvider;
|
||||
use s9e\TextFormatter\Configurator\Helpers\ConfigHelper;
|
||||
use s9e\TextFormatter\Configurator\Items\ProgrammableCallback;
|
||||
use s9e\TextFormatter\Configurator\Traits\Configurable;
|
||||
use s9e\TextFormatter\Configurator\Traits\TemplateSafeness;
|
||||
|
||||
/**
|
||||
* @property mixed $defaultValue Default value used for this attribute
|
||||
* @property AttributeFilterChain $filterChain This attribute's filter chain
|
||||
* @property bool $required Whether this attribute is required for the tag to be valid
|
||||
*/
|
||||
class Attribute implements ConfigProvider
|
||||
{
|
||||
use Configurable;
|
||||
use TemplateSafeness;
|
||||
|
||||
/**
|
||||
* @var mixed Default value used for this attribute
|
||||
*/
|
||||
protected $defaultValue;
|
||||
|
||||
/**
|
||||
* @var AttributeFilterChain This attribute's filter chain
|
||||
*/
|
||||
protected $filterChain;
|
||||
|
||||
/**
|
||||
* @var bool Whether this attribute is required for the tag to be valid
|
||||
*/
|
||||
protected $required = true;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $options This attribute's options
|
||||
*/
|
||||
public function __construct(array $options = null)
|
||||
{
|
||||
$this->filterChain = new AttributeFilterChain;
|
||||
|
||||
if (isset($options))
|
||||
{
|
||||
foreach ($options as $optionName => $optionValue)
|
||||
{
|
||||
$this->__set($optionName, $optionValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this attribute is safe to be used in given context
|
||||
*
|
||||
* @param string $context Either 'AsURL', 'InCSS' or 'InJS'
|
||||
* @return bool
|
||||
*/
|
||||
protected function isSafe($context)
|
||||
{
|
||||
// Test this attribute's filters
|
||||
$methodName = 'isSafe' . $context;
|
||||
foreach ($this->filterChain as $filter)
|
||||
{
|
||||
if ($filter->$methodName())
|
||||
{
|
||||
// If any filter makes it safe, we consider it safe
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return !empty($this->markedSafe[$context]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function asConfig()
|
||||
{
|
||||
$vars = get_object_vars($this);
|
||||
unset($vars['markedSafe']);
|
||||
|
||||
return ConfigHelper::toArray($vars) + ['filterChain' => []];
|
||||
}
|
||||
}
|
||||
54
vendor/s9e/text-formatter/src/Configurator/Items/AttributeFilter.php
vendored
Normal file
54
vendor/s9e/text-formatter/src/Configurator/Items/AttributeFilter.php
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
<?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 s9e\TextFormatter\Configurator\Traits\TemplateSafeness;
|
||||
|
||||
class AttributeFilter extends Filter
|
||||
{
|
||||
use TemplateSafeness;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param callable $callback
|
||||
*/
|
||||
public function __construct($callback)
|
||||
{
|
||||
parent::__construct($callback);
|
||||
|
||||
// Set the default signature
|
||||
$this->resetParameters();
|
||||
$this->addParameterByName('attrValue');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this filter makes a value safe to be used in JavaScript
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSafeInJS()
|
||||
{
|
||||
// List of callbacks that make a value safe to be used in a script, hardcoded for
|
||||
// convenience. Technically, there are numerous built-in PHP functions that would make an
|
||||
// arbitrary value safe in JS, but only a handful have the potential to be used as an
|
||||
// attribute filter
|
||||
$safeCallbacks = [
|
||||
'urlencode',
|
||||
'strtotime',
|
||||
'rawurlencode'
|
||||
];
|
||||
|
||||
if (in_array($this->callback, $safeCallbacks, true))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->isSafe('InJS');
|
||||
}
|
||||
}
|
||||
@@ -1,13 +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
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('/^[0-9A-Za-z]+$/D');
|
||||
|
||||
@@ -1,31 +1,64 @@
|
||||
<?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)
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $values List of allowed values
|
||||
* @param bool $caseSensitive Whether the choice is case-sensitive
|
||||
*/
|
||||
public function __construct(array $values = null, $caseSensitive = false)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
if (isset($values))
|
||||
{
|
||||
$this->setValues($values, $caseSensitive);
|
||||
}
|
||||
}
|
||||
public function setValues(array $values, $caseSensitive = \false)
|
||||
|
||||
/**
|
||||
* Set the list of allowed values
|
||||
*
|
||||
* @param array $values List of allowed values
|
||||
* @param bool $caseSensitive Whether the choice is case-sensitive
|
||||
* @return void
|
||||
*/
|
||||
public function setValues(array $values, $caseSensitive = false)
|
||||
{
|
||||
if (!\is_bool($caseSensitive))
|
||||
if (!is_bool($caseSensitive))
|
||||
{
|
||||
throw new InvalidArgumentException('Argument 2 passed to ' . __METHOD__ . ' must be a boolean');
|
||||
}
|
||||
|
||||
// Create a regexp based on the list of allowed values
|
||||
$regexp = RegexpBuilder::fromList($values, ['delimiter' => '/']);
|
||||
$regexp = '/^' . $regexp . '$/D';
|
||||
|
||||
// Add the case-insensitive flag if applicable
|
||||
if (!$caseSensitive)
|
||||
{
|
||||
$regexp .= 'i';
|
||||
if (!\preg_match('#^[[:ascii:]]*$#D', $regexp))
|
||||
}
|
||||
|
||||
// Add the Unicode flag if the regexp isn't purely ASCII
|
||||
if (!preg_match('#^[[:ascii:]]*$#D', $regexp))
|
||||
{
|
||||
$regexp .= 'u';
|
||||
}
|
||||
|
||||
// Set the regexp associated with this list of values
|
||||
$this->setRegexp($regexp);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +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 ColorFilter extends RegexpFilter
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
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');
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
<?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
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('s9e\\TextFormatter\\Parser\\AttributeFilters\\EmailFilter::filter');
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
<?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
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('s9e\\TextFormatter\\Parser\\AttributeFilters\\FalseFilter::filter');
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
<?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
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('s9e\\TextFormatter\\Parser\\AttributeFilters\\NumericFilter::filterFloat');
|
||||
|
||||
@@ -1,20 +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\Items\AttributeFilters;
|
||||
|
||||
class FontfamilyFilter extends RegexpFilter
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
// This is more restrictive than the specs but safer
|
||||
$namechars = '[- \\w]+';
|
||||
$double = '"' . $namechars . '"';
|
||||
$single = "'" . $namechars . "'";
|
||||
$name = '(?:' . $single . '|' . $double . '|' . $namechars . ')';
|
||||
$regexp = '/^' . $name . '(?:, *' . $name . ')*$/';
|
||||
|
||||
parent::__construct($regexp);
|
||||
$this->markAsSafeInCSS();
|
||||
}
|
||||
|
||||
@@ -1,44 +1,83 @@
|
||||
<?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)
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $map Associative array in the form [key => value]
|
||||
* @param bool $strict Whether this map is strict (values with no match are invalid)
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
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)
|
||||
|
||||
/**
|
||||
* Set the content of this map
|
||||
*
|
||||
* @param array $map Associative array in the form [word => replacement]
|
||||
* @param bool $strict Whether this map is strict (values with no match are invalid)
|
||||
* @return void
|
||||
*/
|
||||
public function setMap(array $map, $strict = false)
|
||||
{
|
||||
if (!\is_bool($strict))
|
||||
if (!is_bool($strict))
|
||||
{
|
||||
throw new InvalidArgumentException('Argument 2 passed to ' . __METHOD__ . ' must be a boolean');
|
||||
}
|
||||
|
||||
// If the map is not strict, we can optimize away the values that are identical to their key
|
||||
if (!$strict)
|
||||
{
|
||||
$map = $this->optimizeLooseMap($map);
|
||||
\ksort($map);
|
||||
}
|
||||
|
||||
// Sort the map so it looks tidy
|
||||
ksort($map);
|
||||
|
||||
// Record this filter's variables
|
||||
$this->vars['map'] = new Dictionary($map);
|
||||
$this->vars['strict'] = $strict;
|
||||
|
||||
// Evaluate safeness
|
||||
$this->resetSafeness();
|
||||
if (!empty($this->vars['strict']))
|
||||
{
|
||||
@@ -46,29 +85,69 @@ class HashmapFilter extends AttributeFilter
|
||||
$this->evaluateSafenessInJS();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark whether this filter makes a value safe to be used in CSS
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function evaluateSafenessInCSS()
|
||||
{
|
||||
// Test each value against the list of disallowed characters
|
||||
$disallowedChars = ContextSafeness::getDisallowedCharactersInCSS();
|
||||
foreach ($this->vars['map'] as $value)
|
||||
{
|
||||
foreach ($disallowedChars as $char)
|
||||
if (\strpos($value, $char) !== \false)
|
||||
{
|
||||
if (strpos($value, $char) !== false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->markAsSafeInCSS();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark whether this filter makes a value safe to be used in JS
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function evaluateSafenessInJS()
|
||||
{
|
||||
// Test each value against the list of disallowed characters
|
||||
$disallowedChars = ContextSafeness::getDisallowedCharactersInJS();
|
||||
foreach ($this->vars['map'] as $value)
|
||||
{
|
||||
foreach ($disallowedChars as $char)
|
||||
if (\strpos($value, $char) !== \false)
|
||||
{
|
||||
if (strpos($value, $char) !== false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->markAsSafeInJS();
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimize a non-strict map by removing values that are identical to their key
|
||||
*
|
||||
* @param array $map Original map
|
||||
* @return array Optimized map
|
||||
*/
|
||||
protected function optimizeLooseMap(array $map)
|
||||
{
|
||||
foreach ($map as $k => $v)
|
||||
{
|
||||
if ($k === $v)
|
||||
{
|
||||
unset($map[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +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
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('/^[-0-9A-Za-z_]+$/D');
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
<?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
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('s9e\\TextFormatter\\Parser\\AttributeFilters\\NumericFilter::filterInt');
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
<?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
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('s9e\\TextFormatter\\Parser\\AttributeFilters\\NetworkFilter::filterIp');
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
<?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
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('s9e\\TextFormatter\\Parser\\AttributeFilters\\NetworkFilter::filterIpport');
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
<?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
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('s9e\\TextFormatter\\Parser\\AttributeFilters\\NetworkFilter::filterIpv4');
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user