update lock clucknut
All checks were successful
All checks were successful
This commit is contained in:
65
vendor/sabberworm/php-css-parser/CHANGELOG.md
vendored
65
vendor/sabberworm/php-css-parser/CHANGELOG.md
vendored
@@ -20,6 +20,71 @@ ### Fixed
|
||||
|
||||
### Documentation
|
||||
|
||||
## 9.3.0: Support for modern CSS at-rules and autoloading bugfix
|
||||
|
||||
### Added
|
||||
|
||||
- Add support for modern CSS at-rules: `@layer`, `@scope`, and `@starting-style` (#1549)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Avoid double autoloading of class aliases (#1552)
|
||||
|
||||
### Documentation
|
||||
|
||||
## 9.2.0: New features and deprecations
|
||||
|
||||
### Added
|
||||
|
||||
- Add `OutputFormat::setSpaceAroundSelectorCombinator()` (#1504)
|
||||
- Add support for escaped quotes in the selectors (#1485, #1489)
|
||||
- Provide line number in exception message for mismatched parentheses in
|
||||
selector (#1435)
|
||||
- Add support for CSS container queries (#1400)
|
||||
|
||||
### Changed
|
||||
|
||||
- `RuleSet\RuleContainer` is renamed to `RuleSet\DeclarationList` (#1530, #1539)
|
||||
- Methods like `setRule()` in `RuleSet` and `DeclarationBlock` have been renamed
|
||||
to `setDeclaration()`, etc. (#1521)
|
||||
- `Rule\Rule` class is renamed to `Property\Declaration`
|
||||
(#1508, #1512, #1513, #1522)
|
||||
- `Rule::setRule()` and `getRule()` are replaced with `setPropertyName()` and
|
||||
`getPropertyName()` (#1506)
|
||||
- `Selector` is now represented as a sequence of `Selector\Component` objects
|
||||
which can be accessed via `getComponents()`, manipulated individually, or set
|
||||
via `setComponents()` (#1478, #1486, #1487, #1488, #1494, #1496, #1536, #1537)
|
||||
- `Selector::setSelector()` and `Selector` constructor will now throw exception
|
||||
upon provision of an invalid selectior (#1498, #1502)
|
||||
- Clean up extra whitespace in CSS selector (#1398)
|
||||
- The array keys passed to `DeclarationBlock::setSelectors()` are no longer
|
||||
preserved (#1407)
|
||||
|
||||
### Deprecated
|
||||
|
||||
- `RuleSet\RuleContainer` is deprecated; use `RuleSet\DeclarationList` instead
|
||||
(#1530)
|
||||
- Methods like `setRule()` in `RuleSet` and `DeclarationBlock` are deprecated;
|
||||
there are direct replacements such as `setDeclaration()` (#1521)
|
||||
- `Rule\Rule` class is deprecated; `Property\Declaration` is a direct
|
||||
replacement (#1508)
|
||||
- `Rule::setRule()` and `getRule()` are deprecated and replaced with
|
||||
`setPropertyName()` and `getPropertyName()` (#1506, #1519)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Do not escape characters that do not need escaping in CSS string (#1444)
|
||||
- Reject selector comprising only whitespace (#1433)
|
||||
- Improve recovery parsing when a rogue `}` is encountered (#1425, #1426)
|
||||
- Parse comment(s) immediately preceding a selector (#1421, #1424)
|
||||
- Parse consecutive comments (#1421)
|
||||
- Support attribute selectors with values containing commas in
|
||||
`DeclarationBlock::setSelectors()` (#1419)
|
||||
- Allow `removeDeclarationBlockBySelector()` to be order-insensitve (#1406)
|
||||
- Fix parsing of `calc` expressions when a newline immediately precedes or
|
||||
follows a `+` or `-` operator (#1399)
|
||||
- Use typesafe versions of PHP functions (#1379, #1380, #1382, #1383, #1384)
|
||||
|
||||
## 9.1.0: Add support for PHP 8.5
|
||||
|
||||
### Added
|
||||
|
||||
106
vendor/sabberworm/php-css-parser/composer.json
vendored
106
vendor/sabberworm/php-css-parser/composer.json
vendored
@@ -25,18 +25,20 @@
|
||||
"require": {
|
||||
"php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
|
||||
"ext-iconv": "*",
|
||||
"thecodingmachine/safe": "^1.3 || ^2.5 || ^3.3"
|
||||
"thecodingmachine/safe": "^1.3 || ^2.5 || ^3.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-parallel-lint/php-parallel-lint": "1.4.0",
|
||||
"phpstan/extension-installer": "1.4.3",
|
||||
"phpstan/phpstan": "1.12.28 || 2.1.25",
|
||||
"phpstan/phpstan-phpunit": "1.4.2 || 2.0.7",
|
||||
"phpstan/phpstan-strict-rules": "1.6.2 || 2.0.6",
|
||||
"phpunit/phpunit": "8.5.46",
|
||||
"phpstan/phpstan": "1.12.32 || 2.1.32",
|
||||
"phpstan/phpstan-phpunit": "1.4.2 || 2.0.8",
|
||||
"phpstan/phpstan-strict-rules": "1.6.2 || 2.0.7",
|
||||
"phpunit/phpunit": "8.5.52",
|
||||
"rawr/phpunit-data-provider": "3.3.1",
|
||||
"rector/rector": "1.2.10 || 2.1.7",
|
||||
"rector/type-perfect": "1.0.0 || 2.1.0"
|
||||
"rector/rector": "1.2.10 || 2.2.8",
|
||||
"rector/type-perfect": "1.0.0 || 2.1.0",
|
||||
"squizlabs/php_codesniffer": "4.0.1",
|
||||
"thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "for parsing UTF-8 CSS"
|
||||
@@ -44,7 +46,11 @@
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Sabberworm\\CSS\\": "src/"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"src/Rule/Rule.php",
|
||||
"src/RuleSet/RuleContainer.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
@@ -62,35 +68,37 @@
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "9.2.x-dev"
|
||||
"dev-main": "9.4.x-dev"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"ci": [
|
||||
"@ci:static",
|
||||
"@ci:dynamic"
|
||||
"check": [
|
||||
"@check:static",
|
||||
"@check:dynamic"
|
||||
],
|
||||
"ci:composer:normalize": "\"./.phive/composer-normalize\" --dry-run",
|
||||
"ci:dynamic": [
|
||||
"@ci:tests"
|
||||
"check:composer:normalize": "\"./.phive/composer-normalize\" --dry-run",
|
||||
"check:dynamic": [
|
||||
"@check:tests"
|
||||
],
|
||||
"ci:php:fixer": "\"./.phive/php-cs-fixer\" --config=config/php-cs-fixer.php fix --dry-run -v --show-progress=dots --diff bin src tests config",
|
||||
"ci:php:lint": "parallel-lint src tests config bin",
|
||||
"ci:php:rector": "rector --no-progress-bar --dry-run --config=config/rector.php",
|
||||
"ci:php:stan": "phpstan --no-progress --configuration=config/phpstan.neon",
|
||||
"ci:static": [
|
||||
"@ci:composer:normalize",
|
||||
"@ci:php:fixer",
|
||||
"@ci:php:lint",
|
||||
"@ci:php:rector",
|
||||
"@ci:php:stan"
|
||||
"check:php:codesniffer": "phpcs --standard=config/phpcs.xml bin config src tests",
|
||||
"check:php:fixer": "\"./.phive/php-cs-fixer\" --config=config/php-cs-fixer.php fix --dry-run -v --show-progress=dots --diff bin config src tests",
|
||||
"check:php:lint": "parallel-lint bin config src tests",
|
||||
"check:php:rector": "rector process --no-progress-bar --dry-run --config=config/rector.php",
|
||||
"check:php:stan": "phpstan --no-progress --configuration=config/phpstan.neon",
|
||||
"check:static": [
|
||||
"@check:composer:normalize",
|
||||
"@check:php:fixer",
|
||||
"@check:php:codesniffer",
|
||||
"@check:php:lint",
|
||||
"@check:php:rector",
|
||||
"@check:php:stan"
|
||||
],
|
||||
"ci:tests": [
|
||||
"@ci:tests:unit"
|
||||
"check:tests": [
|
||||
"@check:tests:unit"
|
||||
],
|
||||
"ci:tests:coverage": "phpunit --do-not-cache-result --coverage-clover=coverage.xml",
|
||||
"ci:tests:sof": "phpunit --stop-on-failure --do-not-cache-result",
|
||||
"ci:tests:unit": "phpunit --do-not-cache-result",
|
||||
"check:tests:coverage": "phpunit --do-not-cache-result --coverage-clover=coverage.xml",
|
||||
"check:tests:sof": "phpunit --stop-on-failure --do-not-cache-result",
|
||||
"check:tests:unit": "phpunit --do-not-cache-result",
|
||||
"fix": [
|
||||
"@fix:php"
|
||||
],
|
||||
@@ -98,30 +106,36 @@
|
||||
"fix:php": [
|
||||
"@fix:composer:normalize",
|
||||
"@fix:php:rector",
|
||||
"@fix:php:codesniffer",
|
||||
"@fix:php:fixer"
|
||||
],
|
||||
"fix:php:fixer": "\"./.phive/php-cs-fixer\" --config=config/php-cs-fixer.php fix bin src tests",
|
||||
"fix:php:rector": "rector --config=config/rector.php",
|
||||
"phpstan:baseline": "phpstan --configuration=config/phpstan.neon --generate-baseline=config/phpstan-baseline.neon --allow-empty-baseline"
|
||||
"fix:php:codesniffer": "phpcbf --standard=config/phpcs.xml bin config src tests",
|
||||
"fix:php:fixer": "\"./.phive/php-cs-fixer\" --config=config/php-cs-fixer.php fix bin config src tests",
|
||||
"fix:php:rector": "rector process --config=config/rector.php",
|
||||
"phpstan:baseline": "phpstan --configuration=config/phpstan.neon --generate-baseline=config/phpstan-baseline.neon --allow-empty-baseline",
|
||||
"phpstan:clearcache": "phpstan clear-result-cache"
|
||||
},
|
||||
"scripts-descriptions": {
|
||||
"ci": "Runs all dynamic and static code checks.",
|
||||
"ci:composer:normalize": "Checks the formatting and structure of the composer.json.",
|
||||
"ci:dynamic": "Runs all dynamic code checks (i.e., currently, the unit tests).",
|
||||
"ci:php:fixer": "Checks the code style with PHP CS Fixer.",
|
||||
"ci:php:lint": "Checks the syntax of the PHP code.",
|
||||
"ci:php:rector": "Checks the code for possible code updates and refactoring.",
|
||||
"ci:php:stan": "Checks the types with PHPStan.",
|
||||
"ci:static": "Runs all static code analysis checks for the code.",
|
||||
"ci:tests": "Runs all dynamic tests (i.e., currently, the unit tests).",
|
||||
"ci:tests:coverage": "Runs the unit tests with code coverage.",
|
||||
"ci:tests:sof": "Runs the unit tests and stops at the first failure.",
|
||||
"ci:tests:unit": "Runs all unit tests.",
|
||||
"check": "Runs all dynamic and static code checks.",
|
||||
"check:composer:normalize": "Checks the formatting and structure of the composer.json.",
|
||||
"check:dynamic": "Runs all dynamic code checks (i.e., currently, the unit tests).",
|
||||
"check:php:codesniffer": "Checks the code style with PHP_CodeSniffer.",
|
||||
"check:php:fixer": "Checks the code style with PHP CS Fixer.",
|
||||
"check:php:lint": "Checks the syntax of the PHP code.",
|
||||
"check:php:rector": "Checks the code for possible code updates and refactoring.",
|
||||
"check:php:stan": "Checks the types with PHPStan.",
|
||||
"check:static": "Runs all static code analysis checks for the code.",
|
||||
"check:tests": "Runs all dynamic tests (i.e., currently, the unit tests).",
|
||||
"check:tests:coverage": "Runs the unit tests with code coverage.",
|
||||
"check:tests:sof": "Runs the unit tests and stops at the first failure.",
|
||||
"check:tests:unit": "Runs all unit tests.",
|
||||
"fix": "Runs all fixers",
|
||||
"fix:composer:normalize": "Reformats and sorts the composer.json file.",
|
||||
"fix:php": "Autofixes all autofixable issues in the PHP code.",
|
||||
"fix:php:codesniffer": "Reformats the code with PHP_CodeSniffer.",
|
||||
"fix:php:fixer": "Fixes autofixable issues found by PHP CS Fixer.",
|
||||
"fix:php:rector": "Fixes autofixable issues found by Rector.",
|
||||
"phpstan:baseline": "Updates the PHPStan baseline file to match the code."
|
||||
"phpstan:baseline": "Updates the PHPStan baseline file to match the code.",
|
||||
"phpstan:clearcache": "Clears the PHPStan cache."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
namespace Sabberworm\CSS\CSSList;
|
||||
|
||||
use Sabberworm\CSS\CSSElement;
|
||||
use Sabberworm\CSS\Property\Declaration;
|
||||
use Sabberworm\CSS\Property\Selector;
|
||||
use Sabberworm\CSS\Rule\Rule;
|
||||
use Sabberworm\CSS\RuleSet\DeclarationBlock;
|
||||
use Sabberworm\CSS\RuleSet\RuleContainer;
|
||||
use Sabberworm\CSS\RuleSet\DeclarationList;
|
||||
use Sabberworm\CSS\RuleSet\RuleSet;
|
||||
use Sabberworm\CSS\Value\CSSFunction;
|
||||
use Sabberworm\CSS\Value\Value;
|
||||
@@ -65,7 +65,7 @@ public function getAllRuleSets(): array
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all `Value` objects found recursively in `Rule`s in the tree.
|
||||
* Returns all `Value` objects found recursively in `Declaration`s in the tree.
|
||||
*
|
||||
* @param CSSElement|null $element
|
||||
* This is the `CSSList` or `RuleSet` to start the search from (defaults to the whole document).
|
||||
@@ -98,14 +98,14 @@ public function getAllValues(
|
||||
);
|
||||
}
|
||||
}
|
||||
} elseif ($element instanceof RuleContainer) {
|
||||
} elseif ($element instanceof DeclarationList) {
|
||||
foreach ($element->getRules($ruleSearchPattern) as $rule) {
|
||||
$result = \array_merge(
|
||||
$result,
|
||||
$this->getAllValues($rule, $ruleSearchPattern, $searchInFunctionArguments)
|
||||
);
|
||||
}
|
||||
} elseif ($element instanceof Rule) {
|
||||
} elseif ($element instanceof Declaration) {
|
||||
$value = $element->getValue();
|
||||
// `string` values are discarded.
|
||||
if ($value instanceof CSSElement) {
|
||||
|
||||
@@ -68,13 +68,19 @@ public static function parseList(ParserState $parserState, CSSList $list): void
|
||||
$usesLenientParsing = $parserState->getSettings()->usesLenientParsing();
|
||||
$comments = [];
|
||||
while (!$parserState->isEnd()) {
|
||||
$comments = \array_merge($comments, $parserState->consumeWhiteSpace());
|
||||
$parserState->consumeWhiteSpace($comments);
|
||||
$listItem = null;
|
||||
if ($usesLenientParsing) {
|
||||
try {
|
||||
$positionBeforeParse = $parserState->currentColumn();
|
||||
$listItem = self::parseListItem($parserState, $list);
|
||||
} catch (UnexpectedTokenException $e) {
|
||||
$listItem = false;
|
||||
// If the failed parsing did not consume anything that was to come ...
|
||||
if ($parserState->currentColumn() === $positionBeforeParse && !$parserState->isEnd()) {
|
||||
// ... the unexpected token needs to be skipped, otherwise there'll be an infinite loop.
|
||||
$parserState->consume(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$listItem = self::parseListItem($parserState, $list);
|
||||
@@ -87,7 +93,8 @@ public static function parseList(ParserState $parserState, CSSList $list): void
|
||||
$listItem->addComments($comments);
|
||||
$list->append($listItem);
|
||||
}
|
||||
$comments = $parserState->consumeWhiteSpace();
|
||||
$comments = [];
|
||||
$parserState->consumeWhiteSpace($comments);
|
||||
}
|
||||
$list->addComments($comments);
|
||||
if (!$isRoot && !$usesLenientParsing) {
|
||||
@@ -133,7 +140,8 @@ private static function parseListItem(ParserState $parserState, CSSList $list)
|
||||
} elseif ($parserState->comes('}')) {
|
||||
if ($isRoot) {
|
||||
if ($parserState->getSettings()->usesLenientParsing()) {
|
||||
return DeclarationBlock::parse($parserState) ?? false;
|
||||
$parserState->consume(1);
|
||||
return self::parseListItem($parserState, $list);
|
||||
} else {
|
||||
throw new SourceException('Unopened {', $parserState->currentLine());
|
||||
}
|
||||
@@ -214,7 +222,7 @@ private static function parseAtRule(ParserState $parserState): ?CSSListItem
|
||||
}
|
||||
}
|
||||
$useRuleSet = true;
|
||||
foreach (\explode('/', AtRule::BLOCK_RULES) as $blockRuleName) {
|
||||
foreach (AtRule::BLOCK_RULES as $blockRuleName) {
|
||||
if (self::identifierIs($identifier, $blockRuleName)) {
|
||||
$useRuleSet = false;
|
||||
break;
|
||||
@@ -370,7 +378,7 @@ public function removeDeclarationBlockBySelector($selectors, bool $removeAll = f
|
||||
if (!($item instanceof DeclarationBlock)) {
|
||||
continue;
|
||||
}
|
||||
if ($item->getSelectors() == $selectors) {
|
||||
if (self::selectorsMatch($item->getSelectors(), $selectors)) {
|
||||
unset($this->contents[$key]);
|
||||
if (!$removeAll) {
|
||||
return;
|
||||
@@ -427,4 +435,44 @@ public function getContents(): array
|
||||
{
|
||||
return $this->contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|int|float|string|array<mixed>|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getArrayRepresentation(): array
|
||||
{
|
||||
throw new \BadMethodCallException('`getArrayRepresentation` is not yet implemented for `' . self::class . '`');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<Selector> $selectors1
|
||||
* @param list<Selector> $selectors2
|
||||
*/
|
||||
private static function selectorsMatch(array $selectors1, array $selectors2): bool
|
||||
{
|
||||
$selectorStrings1 = self::getSelectorStrings($selectors1);
|
||||
$selectorStrings2 = self::getSelectorStrings($selectors2);
|
||||
|
||||
\sort($selectorStrings1);
|
||||
\sort($selectorStrings2);
|
||||
|
||||
return $selectorStrings1 === $selectorStrings2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<Selector> $selectors
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
private static function getSelectorStrings(array $selectors): array
|
||||
{
|
||||
return \array_map(
|
||||
static function (Selector $selector): string {
|
||||
return $selector->getSelector();
|
||||
},
|
||||
$selectors
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,15 @@
|
||||
namespace Sabberworm\CSS\Comment;
|
||||
|
||||
use Sabberworm\CSS\OutputFormat;
|
||||
use Sabberworm\CSS\Renderable;
|
||||
use Sabberworm\CSS\Position\Position;
|
||||
use Sabberworm\CSS\Position\Positionable;
|
||||
use Sabberworm\CSS\Renderable;
|
||||
use Sabberworm\CSS\ShortClassNameProvider;
|
||||
|
||||
class Comment implements Positionable, Renderable
|
||||
{
|
||||
use Position;
|
||||
use ShortClassNameProvider;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
@@ -46,4 +48,19 @@ public function render(OutputFormat $outputFormat): string
|
||||
{
|
||||
return '/*' . $this->commentText . '*/';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|int|float|string|array<mixed>|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getArrayRepresentation(): array
|
||||
{
|
||||
return [
|
||||
'class' => $this->getShortClassName(),
|
||||
// "contents" is the term used in the W3C specs:
|
||||
// https://www.w3.org/TR/CSS22/syndata.html#comments
|
||||
'contents' => $this->commentText,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
final class OutputFormat
|
||||
{
|
||||
/**
|
||||
* Value format: `"` means double-quote, `'` means single-quote
|
||||
*
|
||||
* @var non-empty-string
|
||||
* @var '"'|"'"
|
||||
*/
|
||||
private $stringQuotingType = '"';
|
||||
|
||||
@@ -94,6 +92,11 @@ final class OutputFormat
|
||||
*/
|
||||
private $spaceAfterSelectorSeparator = ' ';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $spaceAroundSelectorCombinator = ' ';
|
||||
|
||||
/**
|
||||
* This is what’s inserted before the separator in value lists, by default.
|
||||
*
|
||||
@@ -181,7 +184,7 @@ final class OutputFormat
|
||||
private $indentationLevel = 0;
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
* @return '"'|"'"
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
@@ -191,7 +194,7 @@ public function getStringQuotingType(): string
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-string $quotingType
|
||||
* @param '"'|"'" $quotingType
|
||||
*
|
||||
* @return $this fluent interface
|
||||
*/
|
||||
@@ -436,6 +439,27 @@ public function setSpaceAfterSelectorSeparator(string $whitespace): self
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function getSpaceAroundSelectorCombinator(): string
|
||||
{
|
||||
return $this->spaceAroundSelectorCombinator;
|
||||
}
|
||||
|
||||
/**
|
||||
* The spacing set is also used for the descendent combinator, which is whitespace only,
|
||||
* unless an empty string is set, in which case a space will be used.
|
||||
*
|
||||
* @return $this fluent interface
|
||||
*/
|
||||
public function setSpaceAroundSelectorCombinator(string $whitespace): self
|
||||
{
|
||||
$this->spaceAroundSelectorCombinator = $whitespace;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@@ -726,6 +750,7 @@ public static function createCompact(): self
|
||||
->setSpaceAfterRuleName('')
|
||||
->setSpaceBeforeOpeningBrace('')
|
||||
->setSpaceAfterSelectorSeparator('')
|
||||
->setSpaceAroundSelectorCombinator('')
|
||||
->setSemicolonAfterLastRule(false)
|
||||
->setRenderComments(false);
|
||||
|
||||
|
||||
@@ -16,9 +16,6 @@
|
||||
*/
|
||||
class ParserState
|
||||
{
|
||||
/**
|
||||
* @var null
|
||||
*/
|
||||
public const EOF = null;
|
||||
|
||||
/**
|
||||
@@ -191,17 +188,25 @@ public function parseCharacter(bool $isForIdentifier): ?string
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<Comment>
|
||||
* Consumes whitespace and/or comments until the next non-whitespace character that isn't a slash opening a comment.
|
||||
*
|
||||
* @param list<Comment> $comments Any comments consumed will be appended to this array.
|
||||
*
|
||||
* @return string the whitespace consumed, without the comments
|
||||
*
|
||||
* @throws UnexpectedEOFException
|
||||
* @throws UnexpectedTokenException
|
||||
*
|
||||
* @phpstan-impure
|
||||
* This method may change the state of the object by advancing the internal position;
|
||||
* it does not simply 'get' a value.
|
||||
*/
|
||||
public function consumeWhiteSpace(): array
|
||||
public function consumeWhiteSpace(array &$comments = []): string
|
||||
{
|
||||
$comments = [];
|
||||
$consumed = '';
|
||||
do {
|
||||
while (preg_match('/\\s/isSu', $this->peek()) === 1) {
|
||||
$this->consume(1);
|
||||
$consumed .= $this->consume(1);
|
||||
}
|
||||
if ($this->parserSettings->usesLenientParsing()) {
|
||||
try {
|
||||
@@ -218,7 +223,7 @@ public function consumeWhiteSpace(): array
|
||||
}
|
||||
} while ($comment instanceof Comment);
|
||||
|
||||
return $comments;
|
||||
return $consumed;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -282,6 +287,27 @@ public function consume($value = 1): string
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the possibly-expected next content is next, consume it.
|
||||
*
|
||||
* @param non-empty-string $nextContent
|
||||
*
|
||||
* @return bool whether the possibly-expected content was found and consumed
|
||||
*/
|
||||
public function consumeIfComes(string $nextContent): bool
|
||||
{
|
||||
$length = $this->strlen($nextContent);
|
||||
if (!$this->streql($this->substr($this->currentPosition, $length), $nextContent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$numberOfLines = \substr_count($nextContent, "\n");
|
||||
$this->lineNumber += $numberOfLines;
|
||||
$this->currentPosition += $this->strlen($nextContent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $expression
|
||||
* @param int<1, max>|null $maximumLength
|
||||
@@ -331,7 +357,7 @@ public function isEnd(): bool
|
||||
|
||||
/**
|
||||
* @param list<string|self::EOF>|string|self::EOF $stopCharacters
|
||||
* @param array<int, Comment> $comments
|
||||
* @param list<Comment> $comments
|
||||
*
|
||||
* @throws UnexpectedEOFException
|
||||
* @throws UnexpectedTokenException
|
||||
@@ -346,6 +372,7 @@ public function consumeUntil(
|
||||
$consumedCharacters = '';
|
||||
$start = $this->currentPosition;
|
||||
|
||||
$comments = \array_merge($comments, $this->consumeComments());
|
||||
while (!$this->isEnd()) {
|
||||
$character = $this->consume(1);
|
||||
if (\in_array($character, $stopCharacters, true)) {
|
||||
@@ -357,10 +384,7 @@ public function consumeUntil(
|
||||
return $consumedCharacters;
|
||||
}
|
||||
$consumedCharacters .= $character;
|
||||
$comment = $this->consumeComment();
|
||||
if ($comment instanceof Comment) {
|
||||
$comments[] = $comment;
|
||||
}
|
||||
$comments = \array_merge($comments, $this->consumeComments());
|
||||
}
|
||||
|
||||
if (\in_array(self::EOF, $stopCharacters, true)) {
|
||||
@@ -458,4 +482,21 @@ private function strsplit(string $string): array
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<Comment>
|
||||
*/
|
||||
private function consumeComments(): array
|
||||
{
|
||||
$comments = [];
|
||||
|
||||
while (true) {
|
||||
$comment = $this->consumeComment();
|
||||
if ($comment instanceof Comment) {
|
||||
$comments[] = $comment;
|
||||
} else {
|
||||
return $comments;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,19 @@ interface AtRule extends CSSListItem
|
||||
* Since there are more set rules than block rules,
|
||||
* we’re whitelisting the block rules and have anything else be treated as a set rule.
|
||||
*
|
||||
* @var non-empty-string
|
||||
*
|
||||
* @internal since 8.5.2
|
||||
*/
|
||||
public const BLOCK_RULES = 'media/document/supports/region-style/font-feature-values';
|
||||
public const BLOCK_RULES = [
|
||||
'media',
|
||||
'document',
|
||||
'supports',
|
||||
'region-style',
|
||||
'font-feature-values',
|
||||
'container',
|
||||
'layer',
|
||||
'scope',
|
||||
'starting-style',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
use Sabberworm\CSS\OutputFormat;
|
||||
use Sabberworm\CSS\Position\Position;
|
||||
use Sabberworm\CSS\Position\Positionable;
|
||||
use Sabberworm\CSS\ShortClassNameProvider;
|
||||
use Sabberworm\CSS\Value\CSSString;
|
||||
use Sabberworm\CSS\Value\URL;
|
||||
|
||||
@@ -18,6 +19,7 @@ class CSSNamespace implements AtRule, Positionable
|
||||
{
|
||||
use CommentContainer;
|
||||
use Position;
|
||||
use ShortClassNameProvider;
|
||||
|
||||
/**
|
||||
* @var CSSString|URL
|
||||
@@ -94,4 +96,19 @@ public function atRuleArgs(): array
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|int|float|string|array<mixed>|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getArrayRepresentation(): array
|
||||
{
|
||||
return [
|
||||
'class' => $this->getShortClassName(),
|
||||
// We're using `uri` here instead of `url` to better match the spec.
|
||||
'uri' => $this->url->getArrayRepresentation(),
|
||||
'prefix' => $this->prefix,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
use Sabberworm\CSS\OutputFormat;
|
||||
use Sabberworm\CSS\Position\Position;
|
||||
use Sabberworm\CSS\Position\Positionable;
|
||||
use Sabberworm\CSS\ShortClassNameProvider;
|
||||
use Sabberworm\CSS\Value\CSSString;
|
||||
|
||||
/**
|
||||
@@ -22,6 +23,7 @@ class Charset implements AtRule, Positionable
|
||||
{
|
||||
use CommentContainer;
|
||||
use Position;
|
||||
use ShortClassNameProvider;
|
||||
|
||||
/**
|
||||
* @var CSSString
|
||||
@@ -71,4 +73,17 @@ public function atRuleArgs(): CSSString
|
||||
{
|
||||
return $this->charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|int|float|string|array<mixed>|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getArrayRepresentation(): array
|
||||
{
|
||||
return [
|
||||
'class' => $this->getShortClassName(),
|
||||
'charset' => $this->charset->getArrayRepresentation(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
231
vendor/sabberworm/php-css-parser/src/Property/Declaration.php
vendored
Normal file
231
vendor/sabberworm/php-css-parser/src/Property/Declaration.php
vendored
Normal file
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabberworm\CSS\Property;
|
||||
|
||||
use Sabberworm\CSS\Comment\Comment;
|
||||
use Sabberworm\CSS\Comment\Commentable;
|
||||
use Sabberworm\CSS\Comment\CommentContainer;
|
||||
use Sabberworm\CSS\CSSElement;
|
||||
use Sabberworm\CSS\OutputFormat;
|
||||
use Sabberworm\CSS\Parsing\ParserState;
|
||||
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
|
||||
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
|
||||
use Sabberworm\CSS\Position\Position;
|
||||
use Sabberworm\CSS\Position\Positionable;
|
||||
use Sabberworm\CSS\Value\RuleValueList;
|
||||
use Sabberworm\CSS\Value\Value;
|
||||
|
||||
use function Safe\preg_match;
|
||||
|
||||
/**
|
||||
* `Declaration`s just have a string key (the property name) and a 'Value'.
|
||||
*
|
||||
* In CSS, `Declaration`s are expressed as follows: “key: value[0][0] value[0][1], value[1][0] value[1][1];”
|
||||
*/
|
||||
class Declaration implements Commentable, CSSElement, Positionable
|
||||
{
|
||||
use CommentContainer;
|
||||
use Position;
|
||||
|
||||
/**
|
||||
* @var non-empty-string
|
||||
*/
|
||||
private $propertyName;
|
||||
|
||||
/**
|
||||
* @var RuleValueList|string|null
|
||||
*/
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isImportant = false;
|
||||
|
||||
/**
|
||||
* @param non-empty-string $propertyName
|
||||
* @param int<1, max>|null $lineNumber
|
||||
* @param int<0, max>|null $columnNumber
|
||||
*/
|
||||
public function __construct(string $propertyName, ?int $lineNumber = null, ?int $columnNumber = null)
|
||||
{
|
||||
$this->propertyName = $propertyName;
|
||||
$this->setPosition($lineNumber, $columnNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<Comment> $commentsBefore
|
||||
*
|
||||
* @throws UnexpectedEOFException
|
||||
* @throws UnexpectedTokenException
|
||||
*
|
||||
* @internal since V8.8.0
|
||||
*/
|
||||
public static function parse(ParserState $parserState, array $commentsBefore = []): self
|
||||
{
|
||||
$comments = $commentsBefore;
|
||||
$parserState->consumeWhiteSpace($comments);
|
||||
$declaration = new self(
|
||||
$parserState->parseIdentifier(!$parserState->comes('--')),
|
||||
$parserState->currentLine(),
|
||||
$parserState->currentColumn()
|
||||
);
|
||||
$parserState->consumeWhiteSpace($comments);
|
||||
$declaration->setComments($comments);
|
||||
$parserState->consume(':');
|
||||
$value = Value::parseValue($parserState, self::getDelimitersForPropertyValue($declaration->getPropertyName()));
|
||||
$declaration->setValue($value);
|
||||
$parserState->consumeWhiteSpace();
|
||||
if ($parserState->comes('!')) {
|
||||
$parserState->consume('!');
|
||||
$parserState->consumeWhiteSpace();
|
||||
$parserState->consume('important');
|
||||
$declaration->setIsImportant(true);
|
||||
}
|
||||
$parserState->consumeWhiteSpace();
|
||||
while ($parserState->comes(';')) {
|
||||
$parserState->consume(';');
|
||||
}
|
||||
|
||||
return $declaration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of delimiters (or separators).
|
||||
* The first item is the innermost separator (or, put another way, the highest-precedence operator).
|
||||
* The sequence continues to the outermost separator (or lowest-precedence operator).
|
||||
*
|
||||
* @param non-empty-string $propertyName
|
||||
*
|
||||
* @return list<non-empty-string>
|
||||
*/
|
||||
private static function getDelimitersForPropertyValue(string $propertyName): array
|
||||
{
|
||||
if (preg_match('/^font($|-)/', $propertyName) === 1) {
|
||||
return [',', '/', ' '];
|
||||
}
|
||||
|
||||
switch ($propertyName) {
|
||||
case 'src':
|
||||
return [' ', ','];
|
||||
default:
|
||||
return [',', ' ', '/'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-string $propertyName
|
||||
*/
|
||||
public function setPropertyName(string $propertyName): void
|
||||
{
|
||||
$this->propertyName = $propertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function getPropertyName(): string
|
||||
{
|
||||
return $this->propertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-string $propertyName
|
||||
*
|
||||
* @deprecated in v9.2, will be removed in v10.0; use `setPropertyName()` instead.
|
||||
*/
|
||||
public function setRule(string $propertyName): void
|
||||
{
|
||||
$this->propertyName = $propertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*
|
||||
* @deprecated in v9.2, will be removed in v10.0; use `getPropertyName()` instead.
|
||||
*/
|
||||
public function getRule(): string
|
||||
{
|
||||
return $this->propertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RuleValueList|string|null
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RuleValueList|string|null $value
|
||||
*/
|
||||
public function setValue($value): void
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a value to the existing value. Value will be appended if a `RuleValueList` exists of the given type.
|
||||
* Otherwise, the existing value will be wrapped by one.
|
||||
*
|
||||
* @param RuleValueList|array<int, RuleValueList> $value
|
||||
*/
|
||||
public function addValue($value, string $type = ' '): void
|
||||
{
|
||||
if (!\is_array($value)) {
|
||||
$value = [$value];
|
||||
}
|
||||
if (!($this->value instanceof RuleValueList) || $this->value->getListSeparator() !== $type) {
|
||||
$currentValue = $this->value;
|
||||
$this->value = new RuleValueList($type, $this->getLineNumber());
|
||||
if ($currentValue !== null && $currentValue !== '') {
|
||||
$this->value->addListComponent($currentValue);
|
||||
}
|
||||
}
|
||||
foreach ($value as $valueItem) {
|
||||
$this->value->addListComponent($valueItem);
|
||||
}
|
||||
}
|
||||
|
||||
public function setIsImportant(bool $isImportant): void
|
||||
{
|
||||
$this->isImportant = $isImportant;
|
||||
}
|
||||
|
||||
public function getIsImportant(): bool
|
||||
{
|
||||
return $this->isImportant;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function render(OutputFormat $outputFormat): string
|
||||
{
|
||||
$formatter = $outputFormat->getFormatter();
|
||||
$result = "{$formatter->comments($this)}{$this->propertyName}:{$formatter->spaceAfterRuleName()}";
|
||||
if ($this->value instanceof Value) { // Can also be a ValueList
|
||||
$result .= $this->value->render($outputFormat);
|
||||
} else {
|
||||
$result .= $this->value;
|
||||
}
|
||||
if ($this->isImportant) {
|
||||
$result .= ' !important';
|
||||
}
|
||||
$result .= ';';
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|int|float|string|array<mixed>|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getArrayRepresentation(): array
|
||||
{
|
||||
throw new \BadMethodCallException('`getArrayRepresentation` is not yet implemented for `' . self::class . '`');
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
use Sabberworm\CSS\OutputFormat;
|
||||
use Sabberworm\CSS\Position\Position;
|
||||
use Sabberworm\CSS\Position\Positionable;
|
||||
use Sabberworm\CSS\ShortClassNameProvider;
|
||||
use Sabberworm\CSS\Value\URL;
|
||||
|
||||
/**
|
||||
@@ -17,6 +18,7 @@ class Import implements AtRule, Positionable
|
||||
{
|
||||
use CommentContainer;
|
||||
use Position;
|
||||
use ShortClassNameProvider;
|
||||
|
||||
/**
|
||||
* @var URL
|
||||
@@ -82,4 +84,19 @@ public function getMediaQuery(): ?string
|
||||
{
|
||||
return $this->mediaQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|int|float|string|array<mixed>|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getArrayRepresentation(): array
|
||||
{
|
||||
return [
|
||||
'class' => $this->getShortClassName(),
|
||||
// We're using the term "uri" here to match the wording used in the specs:
|
||||
// https://www.w3.org/TR/CSS22/cascade.html#at-import
|
||||
'uri' => $this->location->getArrayRepresentation(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,6 @@ class KeyframeSelector extends Selector
|
||||
* - comma is not allowed unless escaped or quoted;
|
||||
* - percentage value is allowed by itself.
|
||||
*
|
||||
* @var non-empty-string
|
||||
*
|
||||
* @internal since 8.5.2
|
||||
*/
|
||||
public const SELECTOR_VALIDATION_RX = '/
|
||||
|
||||
@@ -4,9 +4,16 @@
|
||||
|
||||
namespace Sabberworm\CSS\Property;
|
||||
|
||||
use Sabberworm\CSS\Comment\Comment;
|
||||
use Sabberworm\CSS\OutputFormat;
|
||||
use Sabberworm\CSS\Property\Selector\SpecificityCalculator;
|
||||
use Sabberworm\CSS\Parsing\ParserState;
|
||||
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
|
||||
use Sabberworm\CSS\Property\Selector\Combinator;
|
||||
use Sabberworm\CSS\Property\Selector\Component;
|
||||
use Sabberworm\CSS\Property\Selector\CompoundSelector;
|
||||
use Sabberworm\CSS\Renderable;
|
||||
use Sabberworm\CSS\Settings;
|
||||
use Sabberworm\CSS\ShortClassNameProvider;
|
||||
|
||||
use function Safe\preg_match;
|
||||
|
||||
@@ -16,13 +23,15 @@
|
||||
*/
|
||||
class Selector implements Renderable
|
||||
{
|
||||
use ShortClassNameProvider;
|
||||
|
||||
/**
|
||||
* @var non-empty-string
|
||||
*
|
||||
* @internal since 8.5.2
|
||||
*/
|
||||
public const SELECTOR_VALIDATION_RX = '/
|
||||
^(
|
||||
# not whitespace only
|
||||
(?!\\s*+$)
|
||||
(?:
|
||||
# any sequence of valid unescaped characters, except quotes
|
||||
[a-zA-Z0-9\\x{00A0}-\\x{FFFF}_^$|*=~\\[\\]()\\-\\s\\.:#+>,]++
|
||||
@@ -49,9 +58,9 @@ class Selector implements Renderable
|
||||
/ux';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @var non-empty-list<Component>
|
||||
*/
|
||||
private $selector;
|
||||
private $components;
|
||||
|
||||
/**
|
||||
* @internal since V8.8.0
|
||||
@@ -64,19 +73,137 @@ public static function isValid(string $selector): bool
|
||||
return $numberOfMatches === 1;
|
||||
}
|
||||
|
||||
public function __construct(string $selector)
|
||||
/**
|
||||
* @param non-empty-string|non-empty-list<Component> $selector
|
||||
* Providing a string is deprecated in version 9.2 and will not work from v10.0
|
||||
*
|
||||
* @throws UnexpectedTokenException if the selector is not valid
|
||||
*/
|
||||
final public function __construct($selector)
|
||||
{
|
||||
$this->setSelector($selector);
|
||||
if (\is_string($selector)) {
|
||||
$this->setSelector($selector);
|
||||
} else {
|
||||
$this->setComponents($selector);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<Comment> $comments
|
||||
*
|
||||
* @return non-empty-list<Component>
|
||||
*
|
||||
* @throws UnexpectedTokenException
|
||||
*/
|
||||
private static function parseComponents(ParserState $parserState, array &$comments = []): array
|
||||
{
|
||||
// Whitespace is a descendent combinator, not allowed around a compound selector.
|
||||
// (It is allowed within, e.g. as part of a string or within a function like `:not()`.)
|
||||
// Gobble any up now to get a clean start.
|
||||
$parserState->consumeWhiteSpace($comments);
|
||||
|
||||
$selectorParts = [];
|
||||
while (true) {
|
||||
try {
|
||||
$selectorParts[] = CompoundSelector::parse($parserState, $comments);
|
||||
} catch (UnexpectedTokenException $e) {
|
||||
if ($selectorParts !== [] && \end($selectorParts)->getValue() === ' ') {
|
||||
// The whitespace was not a descendent combinator, and was, in fact, arbitrary,
|
||||
// after the end of the selector. Discard it.
|
||||
\array_pop($selectorParts);
|
||||
break;
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
try {
|
||||
$selectorParts[] = Combinator::parse($parserState, $comments);
|
||||
} catch (UnexpectedTokenException $e) {
|
||||
// End of selector has been reached.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $selectorParts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<Comment> $comments
|
||||
*
|
||||
* @throws UnexpectedTokenException
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function parse(ParserState $parserState, array &$comments = []): self
|
||||
{
|
||||
$selectorParts = self::parseComponents($parserState, $comments);
|
||||
|
||||
// Check that the selector has been fully parsed:
|
||||
if (!\in_array($parserState->peek(), ['{', '}', ',', ''], true)) {
|
||||
throw new UnexpectedTokenException(
|
||||
'`,`, `{`, `}` or EOF',
|
||||
$parserState->peek(5),
|
||||
'literal',
|
||||
$parserState->currentLine()
|
||||
);
|
||||
}
|
||||
|
||||
return new static($selectorParts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-list<Component>
|
||||
*/
|
||||
public function getComponents(): array
|
||||
{
|
||||
return $this->components;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-list<Component> $components
|
||||
* This should be an alternating sequence of `CompoundSelector` and `Combinator`, starting and ending with a
|
||||
* `CompoundSelector`, and may be a single `CompoundSelector`.
|
||||
*/
|
||||
public function setComponents(array $components): self
|
||||
{
|
||||
$this->components = $components;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*
|
||||
* @deprecated in version 9.2, will be removed in v10.0. Use either `getComponents()` or `render()` instead.
|
||||
*/
|
||||
public function getSelector(): string
|
||||
{
|
||||
return $this->selector;
|
||||
return $this->render(new OutputFormat());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-string $selector
|
||||
*
|
||||
* @throws UnexpectedTokenException if the selector is not valid
|
||||
*
|
||||
* @deprecated in version 9.2, will be removed in v10.0. Use `setComponents()` instead.
|
||||
*/
|
||||
public function setSelector(string $selector): void
|
||||
{
|
||||
$this->selector = \trim($selector);
|
||||
$parserState = new ParserState($selector, Settings::create());
|
||||
|
||||
$components = self::parseComponents($parserState);
|
||||
|
||||
// Check that the selector has been fully parsed:
|
||||
if (!$parserState->isEnd()) {
|
||||
throw new UnexpectedTokenException(
|
||||
'EOF',
|
||||
$parserState->peek(5),
|
||||
'literal'
|
||||
);
|
||||
}
|
||||
|
||||
$this->components = $components;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,11 +211,39 @@ public function setSelector(string $selector): void
|
||||
*/
|
||||
public function getSpecificity(): int
|
||||
{
|
||||
return SpecificityCalculator::calculate($this->selector);
|
||||
return \array_sum(\array_map(
|
||||
static function (Component $component): int {
|
||||
return $component->getSpecificity();
|
||||
},
|
||||
$this->components
|
||||
));
|
||||
}
|
||||
|
||||
public function render(OutputFormat $outputFormat): string
|
||||
{
|
||||
return $this->getSelector();
|
||||
return \implode('', \array_map(
|
||||
static function (Component $component) use ($outputFormat): string {
|
||||
return $component->render($outputFormat);
|
||||
},
|
||||
$this->components
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|int|float|string|array<mixed>|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getArrayRepresentation(): array
|
||||
{
|
||||
return [
|
||||
'class' => $this->getShortClassName(),
|
||||
'components' => \array_map(
|
||||
static function (Component $component): array {
|
||||
return $component->getArrayRepresentation();
|
||||
},
|
||||
$this->components
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
119
vendor/sabberworm/php-css-parser/src/Property/Selector/Combinator.php
vendored
Normal file
119
vendor/sabberworm/php-css-parser/src/Property/Selector/Combinator.php
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabberworm\CSS\Property\Selector;
|
||||
|
||||
use Sabberworm\CSS\Comment\Comment;
|
||||
use Sabberworm\CSS\OutputFormat;
|
||||
use Sabberworm\CSS\Parsing\ParserState;
|
||||
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
|
||||
use Sabberworm\CSS\ShortClassNameProvider;
|
||||
|
||||
/**
|
||||
* Class representing a CSS selector combinator (space, `>`, `+`, or `~`).
|
||||
*
|
||||
* @phpstan-type ValidCombinatorValue ' '|'>'|'+'|'~'
|
||||
*/
|
||||
class Combinator implements Component
|
||||
{
|
||||
use ShortClassNameProvider;
|
||||
|
||||
/**
|
||||
* @var ValidCombinatorValue
|
||||
*/
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* @param ValidCombinatorValue $value
|
||||
*/
|
||||
public function __construct(string $value)
|
||||
{
|
||||
$this->setValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<Comment> $comments
|
||||
*
|
||||
* @throws UnexpectedTokenException
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function parse(ParserState $parserState, array &$comments = []): self
|
||||
{
|
||||
$consumedWhitespace = $parserState->consumeWhiteSpace($comments);
|
||||
|
||||
$nextToken = $parserState->peek();
|
||||
if (\in_array($nextToken, ['>', '+', '~'], true)) {
|
||||
$value = $nextToken;
|
||||
$parserState->consume(1);
|
||||
$parserState->consumeWhiteSpace($comments);
|
||||
} elseif ($consumedWhitespace !== '') {
|
||||
$value = ' ';
|
||||
} else {
|
||||
throw new UnexpectedTokenException(
|
||||
'combinator',
|
||||
$nextToken,
|
||||
'literal',
|
||||
$parserState->currentLine()
|
||||
);
|
||||
}
|
||||
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ValidCombinatorValue
|
||||
*/
|
||||
public function getValue(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-string $value
|
||||
*
|
||||
* @throws \UnexpectedValueException if `$value` is not either space, '>', '+' or '~'
|
||||
*/
|
||||
public function setValue(string $value): void
|
||||
{
|
||||
if (!\in_array($value, [' ', '>', '+', '~'], true)) {
|
||||
throw new \UnexpectedValueException('`' . $value . '` is not a valid selector combinator.');
|
||||
}
|
||||
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int<0, max>
|
||||
*/
|
||||
public function getSpecificity(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function render(OutputFormat $outputFormat): string
|
||||
{
|
||||
$spacing = $outputFormat->getSpaceAroundSelectorCombinator();
|
||||
if ($this->value === ' ') {
|
||||
$rendering = $spacing !== '' ? $spacing : ' ';
|
||||
} else {
|
||||
$rendering = $spacing . $this->value . $spacing;
|
||||
}
|
||||
|
||||
return $rendering;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|int|float|string|array<mixed>|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getArrayRepresentation(): array
|
||||
{
|
||||
return [
|
||||
'class' => $this->getShortClassName(),
|
||||
'value' => $this->value,
|
||||
];
|
||||
}
|
||||
}
|
||||
41
vendor/sabberworm/php-css-parser/src/Property/Selector/Component.php
vendored
Normal file
41
vendor/sabberworm/php-css-parser/src/Property/Selector/Component.php
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabberworm\CSS\Property\Selector;
|
||||
|
||||
use Sabberworm\CSS\Renderable;
|
||||
|
||||
/**
|
||||
* This interface is for a class that represents a part of a selector which is either a compound selector (or a simple
|
||||
* selector, which is effectively a compound selector without any compounding) or a selector combinator.
|
||||
*
|
||||
* It allows a selector to be represented as an array of objects that implement this interface.
|
||||
* This is the formal definition:
|
||||
* selector = compound-selector [combinator, compound-selector]*
|
||||
*
|
||||
* The selector is comprised of an array of alternating types that can't be easily represented in a type-safe manner
|
||||
* without this.
|
||||
*
|
||||
* 'Selector component' is not a known grammar in the spec, but a convenience for the implementation.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Selectors/Selector_structure
|
||||
* @see https://www.w3.org/TR/selectors-4/#structure
|
||||
*/
|
||||
interface Component extends Renderable
|
||||
{
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function getValue(): string;
|
||||
|
||||
/**
|
||||
* @param non-empty-string $value
|
||||
*/
|
||||
public function setValue(string $value): void;
|
||||
|
||||
/**
|
||||
* @return int<0, max>
|
||||
*/
|
||||
public function getSpecificity(): int;
|
||||
}
|
||||
284
vendor/sabberworm/php-css-parser/src/Property/Selector/CompoundSelector.php
vendored
Normal file
284
vendor/sabberworm/php-css-parser/src/Property/Selector/CompoundSelector.php
vendored
Normal file
@@ -0,0 +1,284 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabberworm\CSS\Property\Selector;
|
||||
|
||||
use Sabberworm\CSS\Comment\Comment;
|
||||
use Sabberworm\CSS\OutputFormat;
|
||||
use Sabberworm\CSS\Parsing\ParserState;
|
||||
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
|
||||
use Sabberworm\CSS\ShortClassNameProvider;
|
||||
|
||||
use function Safe\preg_match;
|
||||
|
||||
/**
|
||||
* Class representing a CSS compound selector.
|
||||
* Selectors have to be split at combinators (space, `>`, `+`, `~`) before being passed to this class.
|
||||
*/
|
||||
class CompoundSelector implements Component
|
||||
{
|
||||
use ShortClassNameProvider;
|
||||
|
||||
private const PARSER_STOP_CHARACTERS = [
|
||||
'{',
|
||||
'}',
|
||||
'\'',
|
||||
'"',
|
||||
'(',
|
||||
')',
|
||||
'[',
|
||||
']',
|
||||
',',
|
||||
' ',
|
||||
"\t",
|
||||
"\n",
|
||||
"\r",
|
||||
'>',
|
||||
'+',
|
||||
'~',
|
||||
ParserState::EOF,
|
||||
'', // `ParserState::peek()` returns empty string rather than `ParserState::EOF` when end of string is reached
|
||||
];
|
||||
|
||||
private const SELECTOR_VALIDATION_RX = '/
|
||||
^
|
||||
# not starting with whitespace
|
||||
(?!\\s)
|
||||
(?:
|
||||
(?:
|
||||
# any sequence of valid unescaped characters, except quotes
|
||||
[a-zA-Z0-9\\x{00A0}-\\x{FFFF}_^$|*=~\\[\\]()\\-\\s\\.:#+>,]++
|
||||
|
|
||||
# one or more escaped characters
|
||||
(?:\\\\.)++
|
||||
|
|
||||
# quoted text, like in `[id="example"]`
|
||||
(?:
|
||||
# opening quote
|
||||
([\'"])
|
||||
(?:
|
||||
# sequence of characters except closing quote or backslash
|
||||
(?:(?!\\g{-1}|\\\\).)++
|
||||
|
|
||||
# one or more escaped characters
|
||||
(?:\\\\.)++
|
||||
)*+ # zero or more times
|
||||
# closing quote or end (unmatched quote is currently allowed)
|
||||
(?:\\g{-1}|$)
|
||||
)
|
||||
)++ # one or more times
|
||||
|
|
||||
# keyframe animation progress percentage (e.g. 50%)
|
||||
(?:\\d++%)
|
||||
)
|
||||
# not ending with whitespace
|
||||
(?<!\\s)
|
||||
$
|
||||
/ux';
|
||||
|
||||
/**
|
||||
* @var non-empty-string
|
||||
*/
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* @param non-empty-string $value
|
||||
*/
|
||||
public function __construct(string $value)
|
||||
{
|
||||
$this->setValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<Comment> $comments
|
||||
*
|
||||
* @throws UnexpectedTokenException
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function parse(ParserState $parserState, array &$comments = []): self
|
||||
{
|
||||
$selectorParts = [];
|
||||
$stringWrapperCharacter = null;
|
||||
$functionNestingLevel = 0;
|
||||
$isWithinAttribute = false;
|
||||
|
||||
while (true) {
|
||||
$selectorParts[] = $parserState->consumeUntil(self::PARSER_STOP_CHARACTERS, false, false, $comments);
|
||||
$nextCharacter = $parserState->peek();
|
||||
switch ($nextCharacter) {
|
||||
case '':
|
||||
// EOF
|
||||
break 2;
|
||||
case '\'':
|
||||
// The fallthrough is intentional.
|
||||
case '"':
|
||||
$lastPart = \end($selectorParts);
|
||||
$backslashCount = \strspn(\strrev($lastPart), '\\');
|
||||
$quoteIsEscaped = ($backslashCount % 2 === 1);
|
||||
if (!$quoteIsEscaped) {
|
||||
if (!\is_string($stringWrapperCharacter)) {
|
||||
$stringWrapperCharacter = $nextCharacter;
|
||||
} elseif ($stringWrapperCharacter === $nextCharacter) {
|
||||
$stringWrapperCharacter = null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case '(':
|
||||
if (!\is_string($stringWrapperCharacter)) {
|
||||
++$functionNestingLevel;
|
||||
}
|
||||
break;
|
||||
case ')':
|
||||
if (!\is_string($stringWrapperCharacter)) {
|
||||
if ($functionNestingLevel <= 0) {
|
||||
throw new UnexpectedTokenException(
|
||||
'anything but',
|
||||
')',
|
||||
'literal',
|
||||
$parserState->currentLine()
|
||||
);
|
||||
}
|
||||
--$functionNestingLevel;
|
||||
}
|
||||
break;
|
||||
case '[':
|
||||
if (!\is_string($stringWrapperCharacter)) {
|
||||
if ($isWithinAttribute) {
|
||||
throw new UnexpectedTokenException(
|
||||
'anything but',
|
||||
'[',
|
||||
'literal',
|
||||
$parserState->currentLine()
|
||||
);
|
||||
}
|
||||
$isWithinAttribute = true;
|
||||
}
|
||||
break;
|
||||
case ']':
|
||||
if (!\is_string($stringWrapperCharacter)) {
|
||||
if (!$isWithinAttribute) {
|
||||
throw new UnexpectedTokenException(
|
||||
'anything but',
|
||||
']',
|
||||
'literal',
|
||||
$parserState->currentLine()
|
||||
);
|
||||
}
|
||||
$isWithinAttribute = false;
|
||||
}
|
||||
break;
|
||||
case '{':
|
||||
// The fallthrough is intentional.
|
||||
case '}':
|
||||
if (!\is_string($stringWrapperCharacter)) {
|
||||
break 2;
|
||||
}
|
||||
break;
|
||||
case ',':
|
||||
// The fallthrough is intentional.
|
||||
case ' ':
|
||||
// The fallthrough is intentional.
|
||||
case "\t":
|
||||
// The fallthrough is intentional.
|
||||
case "\n":
|
||||
// The fallthrough is intentional.
|
||||
case "\r":
|
||||
// The fallthrough is intentional.
|
||||
case '>':
|
||||
// The fallthrough is intentional.
|
||||
case '+':
|
||||
// The fallthrough is intentional.
|
||||
case '~':
|
||||
if (!\is_string($stringWrapperCharacter) && $functionNestingLevel === 0 && !$isWithinAttribute) {
|
||||
break 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
$selectorParts[] = $parserState->consume(1);
|
||||
}
|
||||
|
||||
if ($functionNestingLevel !== 0) {
|
||||
throw new UnexpectedTokenException(')', $nextCharacter, 'literal', $parserState->currentLine());
|
||||
}
|
||||
if (\is_string($stringWrapperCharacter)) {
|
||||
throw new UnexpectedTokenException(
|
||||
$stringWrapperCharacter,
|
||||
$nextCharacter,
|
||||
'literal',
|
||||
$parserState->currentLine()
|
||||
);
|
||||
}
|
||||
|
||||
$value = \implode('', $selectorParts);
|
||||
if ($value === '') {
|
||||
throw new UnexpectedTokenException('selector', $nextCharacter, 'literal', $parserState->currentLine());
|
||||
}
|
||||
if (!self::isValid($value)) {
|
||||
throw new UnexpectedTokenException(
|
||||
'Selector component is not valid:',
|
||||
'`' . $value . '`',
|
||||
'custom',
|
||||
$parserState->currentLine()
|
||||
);
|
||||
}
|
||||
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function getValue(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-string $value
|
||||
*
|
||||
* @throws \UnexpectedValueException if `$value` contains invalid characters or has surrounding whitespce
|
||||
*/
|
||||
public function setValue(string $value): void
|
||||
{
|
||||
if (!self::isValid($value)) {
|
||||
throw new \UnexpectedValueException('`' . $value . '` is not a valid compound selector.');
|
||||
}
|
||||
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int<0, max>
|
||||
*/
|
||||
public function getSpecificity(): int
|
||||
{
|
||||
return SpecificityCalculator::calculate($this->value);
|
||||
}
|
||||
|
||||
public function render(OutputFormat $outputFormat): string
|
||||
{
|
||||
return $this->getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|int|float|string|array<mixed>|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getArrayRepresentation(): array
|
||||
{
|
||||
return [
|
||||
'class' => $this->getShortClassName(),
|
||||
'value' => $this->value,
|
||||
];
|
||||
}
|
||||
|
||||
private static function isValid(string $value): bool
|
||||
{
|
||||
$numberOfMatches = preg_match(self::SELECTOR_VALIDATION_RX, $value);
|
||||
|
||||
return $numberOfMatches === 1;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
namespace Sabberworm\CSS\Property\Selector;
|
||||
|
||||
use function Safe\preg_match_all;
|
||||
|
||||
/**
|
||||
* Utility class to calculate the specificity of a CSS selector.
|
||||
*
|
||||
@@ -13,8 +15,6 @@ final class SpecificityCalculator
|
||||
{
|
||||
/**
|
||||
* regexp for specificity calculations
|
||||
*
|
||||
* @var non-empty-string
|
||||
*/
|
||||
private const NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX = '/
|
||||
(\\.[\\w]+) # classes
|
||||
@@ -37,8 +37,6 @@ final class SpecificityCalculator
|
||||
|
||||
/**
|
||||
* regexp for specificity calculations
|
||||
*
|
||||
* @var non-empty-string
|
||||
*/
|
||||
private const ELEMENTS_AND_PSEUDO_ELEMENTS_RX = '/
|
||||
((^|[\\s\\+\\>\\~]+)[\\w]+ # elements
|
||||
@@ -67,8 +65,8 @@ public static function calculate(string $selector): int
|
||||
/// @todo should exclude \# as well as "#"
|
||||
$matches = null;
|
||||
$b = \substr_count($selector, '#');
|
||||
$c = \preg_match_all(self::NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX, $selector, $matches);
|
||||
$d = \preg_match_all(self::ELEMENTS_AND_PSEUDO_ELEMENTS_RX, $selector, $matches);
|
||||
$c = preg_match_all(self::NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX, $selector, $matches);
|
||||
$d = preg_match_all(self::ELEMENTS_AND_PSEUDO_ELEMENTS_RX, $selector, $matches);
|
||||
self::$cache[$selector] = ($a * 1000) + ($b * 100) + ($c * 10) + $d;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,4 +7,11 @@
|
||||
interface Renderable
|
||||
{
|
||||
public function render(OutputFormat $outputFormat): string;
|
||||
|
||||
/**
|
||||
* @return array<string, bool|int|float|string|array<mixed>|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getArrayRepresentation(): array;
|
||||
}
|
||||
|
||||
193
vendor/sabberworm/php-css-parser/src/Rule/Rule.php
vendored
193
vendor/sabberworm/php-css-parser/src/Rule/Rule.php
vendored
@@ -4,195 +4,14 @@
|
||||
|
||||
namespace Sabberworm\CSS\Rule;
|
||||
|
||||
use Sabberworm\CSS\Comment\Comment;
|
||||
use Sabberworm\CSS\Comment\Commentable;
|
||||
use Sabberworm\CSS\Comment\CommentContainer;
|
||||
use Sabberworm\CSS\CSSElement;
|
||||
use Sabberworm\CSS\OutputFormat;
|
||||
use Sabberworm\CSS\Parsing\ParserState;
|
||||
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
|
||||
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
|
||||
use Sabberworm\CSS\Position\Position;
|
||||
use Sabberworm\CSS\Position\Positionable;
|
||||
use Sabberworm\CSS\Value\RuleValueList;
|
||||
use Sabberworm\CSS\Value\Value;
|
||||
use Sabberworm\CSS\Property\Declaration;
|
||||
|
||||
/**
|
||||
* `Rule`s just have a string key (the rule) and a 'Value'.
|
||||
*
|
||||
* In CSS, `Rule`s are expressed as follows: “key: value[0][0] value[0][1], value[1][0] value[1][1];”
|
||||
*/
|
||||
class Rule implements Commentable, CSSElement, Positionable
|
||||
{
|
||||
use CommentContainer;
|
||||
use Position;
|
||||
use function Safe\class_alias;
|
||||
|
||||
if (!\class_exists(Rule::class, false) && !\interface_exists(Rule::class, false)) {
|
||||
/**
|
||||
* @var non-empty-string
|
||||
* @deprecated in v9.2, will be removed in v10.0. Use `Property\Declaration` instead, which is a direct
|
||||
* replacement.
|
||||
*/
|
||||
private $rule;
|
||||
|
||||
/**
|
||||
* @var RuleValueList|string|null
|
||||
*/
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isImportant = false;
|
||||
|
||||
/**
|
||||
* @param non-empty-string $rule
|
||||
* @param int<1, max>|null $lineNumber
|
||||
* @param int<0, max>|null $columnNumber
|
||||
*/
|
||||
public function __construct(string $rule, ?int $lineNumber = null, ?int $columnNumber = null)
|
||||
{
|
||||
$this->rule = $rule;
|
||||
$this->setPosition($lineNumber, $columnNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<Comment> $commentsBeforeRule
|
||||
*
|
||||
* @throws UnexpectedEOFException
|
||||
* @throws UnexpectedTokenException
|
||||
*
|
||||
* @internal since V8.8.0
|
||||
*/
|
||||
public static function parse(ParserState $parserState, array $commentsBeforeRule = []): Rule
|
||||
{
|
||||
$comments = \array_merge($commentsBeforeRule, $parserState->consumeWhiteSpace());
|
||||
$rule = new Rule(
|
||||
$parserState->parseIdentifier(!$parserState->comes('--')),
|
||||
$parserState->currentLine(),
|
||||
$parserState->currentColumn()
|
||||
);
|
||||
$rule->setComments($comments);
|
||||
$rule->addComments($parserState->consumeWhiteSpace());
|
||||
$parserState->consume(':');
|
||||
$value = Value::parseValue($parserState, self::listDelimiterForRule($rule->getRule()));
|
||||
$rule->setValue($value);
|
||||
$parserState->consumeWhiteSpace();
|
||||
if ($parserState->comes('!')) {
|
||||
$parserState->consume('!');
|
||||
$parserState->consumeWhiteSpace();
|
||||
$parserState->consume('important');
|
||||
$rule->setIsImportant(true);
|
||||
}
|
||||
$parserState->consumeWhiteSpace();
|
||||
while ($parserState->comes(';')) {
|
||||
$parserState->consume(';');
|
||||
}
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of delimiters (or separators).
|
||||
* The first item is the innermost separator (or, put another way, the highest-precedence operator).
|
||||
* The sequence continues to the outermost separator (or lowest-precedence operator).
|
||||
*
|
||||
* @param non-empty-string $rule
|
||||
*
|
||||
* @return list<non-empty-string>
|
||||
*/
|
||||
private static function listDelimiterForRule(string $rule): array
|
||||
{
|
||||
if (\preg_match('/^font($|-)/', $rule)) {
|
||||
return [',', '/', ' '];
|
||||
}
|
||||
|
||||
switch ($rule) {
|
||||
case 'src':
|
||||
return [' ', ','];
|
||||
default:
|
||||
return [',', ' ', '/'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-string $rule
|
||||
*/
|
||||
public function setRule(string $rule): void
|
||||
{
|
||||
$this->rule = $rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function getRule(): string
|
||||
{
|
||||
return $this->rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RuleValueList|string|null
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RuleValueList|string|null $value
|
||||
*/
|
||||
public function setValue($value): void
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a value to the existing value. Value will be appended if a `RuleValueList` exists of the given type.
|
||||
* Otherwise, the existing value will be wrapped by one.
|
||||
*
|
||||
* @param RuleValueList|array<int, RuleValueList> $value
|
||||
*/
|
||||
public function addValue($value, string $type = ' '): void
|
||||
{
|
||||
if (!\is_array($value)) {
|
||||
$value = [$value];
|
||||
}
|
||||
if (!($this->value instanceof RuleValueList) || $this->value->getListSeparator() !== $type) {
|
||||
$currentValue = $this->value;
|
||||
$this->value = new RuleValueList($type, $this->getLineNumber());
|
||||
if ($currentValue !== null && $currentValue !== '') {
|
||||
$this->value->addListComponent($currentValue);
|
||||
}
|
||||
}
|
||||
foreach ($value as $valueItem) {
|
||||
$this->value->addListComponent($valueItem);
|
||||
}
|
||||
}
|
||||
|
||||
public function setIsImportant(bool $isImportant): void
|
||||
{
|
||||
$this->isImportant = $isImportant;
|
||||
}
|
||||
|
||||
public function getIsImportant(): bool
|
||||
{
|
||||
return $this->isImportant;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function render(OutputFormat $outputFormat): string
|
||||
{
|
||||
$formatter = $outputFormat->getFormatter();
|
||||
$result = "{$formatter->comments($this)}{$this->rule}:{$formatter->spaceAfterRuleName()}";
|
||||
if ($this->value instanceof Value) { // Can also be a ValueList
|
||||
$result .= $this->value->render($outputFormat);
|
||||
} else {
|
||||
$result .= $this->value;
|
||||
}
|
||||
if ($this->isImportant) {
|
||||
$result .= ' !important';
|
||||
}
|
||||
$result .= ';';
|
||||
return $result;
|
||||
}
|
||||
class_alias(Declaration::class, Rule::class);
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ public function render(OutputFormat $outputFormat): string
|
||||
$arguments = ' ' . $arguments;
|
||||
}
|
||||
$result .= "@{$this->type}$arguments{$formatter->spaceBeforeOpeningBrace()}{";
|
||||
$result .= $this->renderRules($outputFormat);
|
||||
$result .= $this->renderDeclarations($outputFormat);
|
||||
$result .= '}';
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
namespace Sabberworm\CSS\RuleSet;
|
||||
|
||||
use Sabberworm\CSS\Comment\Comment;
|
||||
use Sabberworm\CSS\Comment\CommentContainer;
|
||||
use Sabberworm\CSS\CSSElement;
|
||||
use Sabberworm\CSS\CSSList\CSSList;
|
||||
@@ -16,9 +17,10 @@
|
||||
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
|
||||
use Sabberworm\CSS\Position\Position;
|
||||
use Sabberworm\CSS\Position\Positionable;
|
||||
use Sabberworm\CSS\Property\Declaration;
|
||||
use Sabberworm\CSS\Property\KeyframeSelector;
|
||||
use Sabberworm\CSS\Property\Selector;
|
||||
use Sabberworm\CSS\Rule\Rule;
|
||||
use Sabberworm\CSS\Settings;
|
||||
|
||||
/**
|
||||
* This class represents a `RuleSet` constrained by a `Selector`.
|
||||
@@ -30,13 +32,14 @@
|
||||
*
|
||||
* Note that `CSSListItem` extends both `Commentable` and `Renderable`, so those interfaces must also be implemented.
|
||||
*/
|
||||
class DeclarationBlock implements CSSElement, CSSListItem, Positionable, RuleContainer
|
||||
class DeclarationBlock implements CSSElement, CSSListItem, Positionable, DeclarationList
|
||||
{
|
||||
use CommentContainer;
|
||||
use LegacyDeclarationListMethods;
|
||||
use Position;
|
||||
|
||||
/**
|
||||
* @var array<Selector|string>
|
||||
* @var list<Selector>
|
||||
*/
|
||||
private $selectors = [];
|
||||
|
||||
@@ -65,66 +68,15 @@ public static function parse(ParserState $parserState, ?CSSList $list = null): ?
|
||||
$comments = [];
|
||||
$result = new DeclarationBlock($parserState->currentLine());
|
||||
try {
|
||||
$selectors = [];
|
||||
$selectorParts = [];
|
||||
$stringWrapperCharacter = null;
|
||||
$functionNestingLevel = 0;
|
||||
$consumedNextCharacter = false;
|
||||
static $stopCharacters = ['{', '}', '\'', '"', '(', ')', ','];
|
||||
do {
|
||||
if (!$consumedNextCharacter) {
|
||||
$selectorParts[] = $parserState->consume(1);
|
||||
}
|
||||
$selectorParts[] = $parserState->consumeUntil($stopCharacters, false, false, $comments);
|
||||
$nextCharacter = $parserState->peek();
|
||||
$consumedNextCharacter = false;
|
||||
switch ($nextCharacter) {
|
||||
case '\'':
|
||||
// The fallthrough is intentional.
|
||||
case '"':
|
||||
if (!\is_string($stringWrapperCharacter)) {
|
||||
$stringWrapperCharacter = $nextCharacter;
|
||||
} elseif ($stringWrapperCharacter === $nextCharacter) {
|
||||
if (\substr(\end($selectorParts), -1) !== '\\') {
|
||||
$stringWrapperCharacter = null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case '(':
|
||||
if (!\is_string($stringWrapperCharacter)) {
|
||||
++$functionNestingLevel;
|
||||
}
|
||||
break;
|
||||
case ')':
|
||||
if (!\is_string($stringWrapperCharacter)) {
|
||||
if ($functionNestingLevel <= 0) {
|
||||
throw new UnexpectedTokenException('anything but', ')');
|
||||
}
|
||||
--$functionNestingLevel;
|
||||
}
|
||||
break;
|
||||
case ',':
|
||||
if (!\is_string($stringWrapperCharacter) && $functionNestingLevel === 0) {
|
||||
$selectors[] = \implode('', $selectorParts);
|
||||
$selectorParts = [];
|
||||
$parserState->consume(1);
|
||||
$consumedNextCharacter = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} while (!\in_array($nextCharacter, ['{', '}'], true) || \is_string($stringWrapperCharacter));
|
||||
if ($functionNestingLevel !== 0) {
|
||||
throw new UnexpectedTokenException(')', $nextCharacter);
|
||||
}
|
||||
$selectors[] = \implode('', $selectorParts); // add final or only selector
|
||||
$selectors = self::parseSelectors($parserState, $list, $comments);
|
||||
$result->setSelectors($selectors, $list);
|
||||
if ($parserState->comes('{')) {
|
||||
$parserState->consume(1);
|
||||
}
|
||||
} catch (UnexpectedTokenException $e) {
|
||||
if ($parserState->getSettings()->usesLenientParsing()) {
|
||||
if (!$parserState->comes('}')) {
|
||||
$parserState->consumeUntil('}', false, true);
|
||||
if (!$parserState->consumeIfComes('}')) {
|
||||
$parserState->consumeUntil(['}', ParserState::EOF], false, true);
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
@@ -146,11 +98,29 @@ public static function parse(ParserState $parserState, ?CSSList $list = null): ?
|
||||
public function setSelectors($selectors, ?CSSList $list = null): void
|
||||
{
|
||||
if (\is_array($selectors)) {
|
||||
$this->selectors = $selectors;
|
||||
$selectorsToSet = $selectors;
|
||||
} else {
|
||||
$this->selectors = \explode(',', $selectors);
|
||||
// A string of comma-separated selectors requires parsing.
|
||||
try {
|
||||
$parserState = new ParserState($selectors, Settings::create());
|
||||
$selectorsToSet = self::parseSelectors($parserState, $list);
|
||||
if (!$parserState->isEnd()) {
|
||||
throw new UnexpectedTokenException('EOF', 'more');
|
||||
}
|
||||
} catch (UnexpectedTokenException $exception) {
|
||||
// The exception message from parsing may refer to the faux `{` block start token,
|
||||
// which would be confusing.
|
||||
// Rethrow with a more useful message, that also includes the selector(s) string that was passed.
|
||||
throw new UnexpectedTokenException(
|
||||
'Selector(s) string is not valid.',
|
||||
$selectors,
|
||||
'custom'
|
||||
);
|
||||
}
|
||||
}
|
||||
foreach ($this->selectors as $key => $selector) {
|
||||
|
||||
// Convert all items to a `Selector` if not already
|
||||
foreach ($selectorsToSet as $key => $selector) {
|
||||
if (!($selector instanceof Selector)) {
|
||||
if ($list === null || !($list instanceof KeyFrame)) {
|
||||
if (!Selector::isValid($selector)) {
|
||||
@@ -160,7 +130,7 @@ public function setSelectors($selectors, ?CSSList $list = null): void
|
||||
'custom'
|
||||
);
|
||||
}
|
||||
$this->selectors[$key] = new Selector($selector);
|
||||
$selectorsToSet[$key] = new Selector($selector);
|
||||
} else {
|
||||
if (!KeyframeSelector::isValid($selector)) {
|
||||
throw new UnexpectedTokenException(
|
||||
@@ -169,10 +139,13 @@ public function setSelectors($selectors, ?CSSList $list = null): void
|
||||
'custom'
|
||||
);
|
||||
}
|
||||
$this->selectors[$key] = new KeyframeSelector($selector);
|
||||
$selectorsToSet[$key] = new KeyframeSelector($selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Discard the keys and reindex the array
|
||||
$this->selectors = \array_values($selectorsToSet);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,7 +168,7 @@ public function removeSelector($selectorToRemove): bool
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<Selector>
|
||||
* @return list<Selector>
|
||||
*/
|
||||
public function getSelectors(): array
|
||||
{
|
||||
@@ -208,65 +181,65 @@ public function getRuleSet(): RuleSet
|
||||
}
|
||||
|
||||
/**
|
||||
* @see RuleSet::addRule()
|
||||
* @see RuleSet::addDeclaration()
|
||||
*/
|
||||
public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void
|
||||
public function addDeclaration(Declaration $declarationToAdd, ?Declaration $sibling = null): void
|
||||
{
|
||||
$this->ruleSet->addRule($ruleToAdd, $sibling);
|
||||
$this->ruleSet->addDeclaration($declarationToAdd, $sibling);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see RuleSet::getRules()
|
||||
* @return array<int<0, max>, Declaration>
|
||||
*
|
||||
* @return array<int<0, max>, Rule>
|
||||
* @see RuleSet::getDeclarations()
|
||||
*/
|
||||
public function getRules(?string $searchPattern = null): array
|
||||
public function getDeclarations(?string $searchPattern = null): array
|
||||
{
|
||||
return $this->ruleSet->getRules($searchPattern);
|
||||
return $this->ruleSet->getDeclarations($searchPattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see RuleSet::setRules()
|
||||
* @param array<Declaration> $declarations
|
||||
*
|
||||
* @param array<Rule> $rules
|
||||
* @see RuleSet::setDeclarations()
|
||||
*/
|
||||
public function setRules(array $rules): void
|
||||
public function setDeclarations(array $declarations): void
|
||||
{
|
||||
$this->ruleSet->setRules($rules);
|
||||
$this->ruleSet->setDeclarations($declarations);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see RuleSet::getRulesAssoc()
|
||||
* @return array<string, Declaration>
|
||||
*
|
||||
* @return array<string, Rule>
|
||||
* @see RuleSet::getDeclarationsAssociative()
|
||||
*/
|
||||
public function getRulesAssoc(?string $searchPattern = null): array
|
||||
public function getDeclarationsAssociative(?string $searchPattern = null): array
|
||||
{
|
||||
return $this->ruleSet->getRulesAssoc($searchPattern);
|
||||
return $this->ruleSet->getDeclarationsAssociative($searchPattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see RuleSet::removeRule()
|
||||
* @see RuleSet::removeDeclaration()
|
||||
*/
|
||||
public function removeRule(Rule $ruleToRemove): void
|
||||
public function removeDeclaration(Declaration $declarationToRemove): void
|
||||
{
|
||||
$this->ruleSet->removeRule($ruleToRemove);
|
||||
$this->ruleSet->removeDeclaration($declarationToRemove);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see RuleSet::removeMatchingRules()
|
||||
* @see RuleSet::removeMatchingDeclarations()
|
||||
*/
|
||||
public function removeMatchingRules(string $searchPattern): void
|
||||
public function removeMatchingDeclarations(string $searchPattern): void
|
||||
{
|
||||
$this->ruleSet->removeMatchingRules($searchPattern);
|
||||
$this->ruleSet->removeMatchingDeclarations($searchPattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see RuleSet::removeAllRules()
|
||||
* @see RuleSet::removeAllDeclarations()
|
||||
*/
|
||||
public function removeAllRules(): void
|
||||
public function removeAllDeclarations(): void
|
||||
{
|
||||
$this->ruleSet->removeAllRules();
|
||||
$this->ruleSet->removeAllDeclarations();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -298,4 +271,36 @@ public function render(OutputFormat $outputFormat): string
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|int|float|string|array<mixed>|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getArrayRepresentation(): array
|
||||
{
|
||||
throw new \BadMethodCallException('`getArrayRepresentation` is not yet implemented for `' . self::class . '`');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<Comment> $comments
|
||||
*
|
||||
* @return list<Selector>
|
||||
*
|
||||
* @throws UnexpectedTokenException
|
||||
*/
|
||||
private static function parseSelectors(ParserState $parserState, ?CSSList $list, array &$comments = []): array
|
||||
{
|
||||
$selectorClass = $list instanceof KeyFrame ? KeyFrameSelector::class : Selector::class;
|
||||
$selectors = [];
|
||||
|
||||
while (true) {
|
||||
$selectors[] = $selectorClass::parse($parserState, $comments);
|
||||
if (!$parserState->consumeIfComes(',')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $selectors;
|
||||
}
|
||||
}
|
||||
|
||||
77
vendor/sabberworm/php-css-parser/src/RuleSet/DeclarationList.php
vendored
Normal file
77
vendor/sabberworm/php-css-parser/src/RuleSet/DeclarationList.php
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabberworm\CSS\RuleSet;
|
||||
|
||||
use Sabberworm\CSS\Property\Declaration;
|
||||
|
||||
/**
|
||||
* Represents a CSS item that contains `Declaration`s, defining the methods to manipulate them.
|
||||
*/
|
||||
interface DeclarationList
|
||||
{
|
||||
public function addDeclaration(Declaration $declarationToAdd, ?Declaration $sibling = null): void;
|
||||
|
||||
public function removeDeclaration(Declaration $declarationToRemove): void;
|
||||
|
||||
public function removeMatchingDeclarations(string $searchPattern): void;
|
||||
|
||||
public function removeAllDeclarations(): void;
|
||||
|
||||
/**
|
||||
* @param array<Declaration> $declarations
|
||||
*/
|
||||
public function setDeclarations(array $declarations): void;
|
||||
|
||||
/**
|
||||
* @return array<int<0, max>, Declaration>
|
||||
*/
|
||||
public function getDeclarations(?string $searchPattern = null): array;
|
||||
|
||||
/**
|
||||
* @return array<string, Declaration>
|
||||
*/
|
||||
public function getDeclarationsAssociative(?string $searchPattern = null): array;
|
||||
|
||||
/**
|
||||
* @deprecated in v9.2, will be removed in v10.0; use `addDeclaration()` instead.
|
||||
*/
|
||||
public function addRule(Declaration $declarationToAdd, ?Declaration $sibling = null): void;
|
||||
|
||||
/**
|
||||
* @deprecated in v9.2, will be removed in v10.0; use `removeDeclaration()` instead.
|
||||
*/
|
||||
public function removeRule(Declaration $declarationToRemove): void;
|
||||
|
||||
/**
|
||||
* @deprecated in v9.2, will be removed in v10.0; use `removeMatchingDeclarations()` instead.
|
||||
*/
|
||||
public function removeMatchingRules(string $searchPattern): void;
|
||||
|
||||
/**
|
||||
* @deprecated in v9.2, will be removed in v10.0; use `removeAllDeclarations()` instead.
|
||||
*/
|
||||
public function removeAllRules(): void;
|
||||
|
||||
/**
|
||||
* @param array<Declaration> $declarations
|
||||
*
|
||||
* @deprecated in v9.2, will be removed in v10.0; use `setDeclarations()` instead.
|
||||
*/
|
||||
public function setRules(array $declarations): void;
|
||||
|
||||
/**
|
||||
* @return array<int<0, max>, Declaration>
|
||||
*
|
||||
* @deprecated in v9.2, will be removed in v10.0; use `getDeclarations()` instead.
|
||||
*/
|
||||
public function getRules(?string $searchPattern = null): array;
|
||||
|
||||
/**
|
||||
* @return array<string, Declaration>
|
||||
*
|
||||
* @deprecated in v9.2, will be removed in v10.0; use `getDeclarationsAssociative()` instead.
|
||||
*/
|
||||
public function getRulesAssoc(?string $searchPattern = null): array;
|
||||
}
|
||||
75
vendor/sabberworm/php-css-parser/src/RuleSet/LegacyDeclarationListMethods.php
vendored
Normal file
75
vendor/sabberworm/php-css-parser/src/RuleSet/LegacyDeclarationListMethods.php
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabberworm\CSS\RuleSet;
|
||||
|
||||
use Sabberworm\CSS\Property\Declaration;
|
||||
|
||||
/**
|
||||
* Provides a mapping of the deprecated methods in a `DeclarationList` to their renamed replacements.
|
||||
*/
|
||||
trait LegacyDeclarationListMethods
|
||||
{
|
||||
/**
|
||||
* @deprecated in v9.2, will be removed in v10.0; use `addDeclaration()` instead.
|
||||
*/
|
||||
public function addRule(Declaration $declarationToAdd, ?Declaration $sibling = null): void
|
||||
{
|
||||
$this->addDeclaration($declarationToAdd, $sibling);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated in v9.2, will be removed in v10.0; use `removeDeclaration()` instead.
|
||||
*/
|
||||
public function removeRule(Declaration $declarationToRemove): void
|
||||
{
|
||||
$this->removeDeclaration($declarationToRemove);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated in v9.2, will be removed in v10.0; use `removeMatchingDeclarations()` instead.
|
||||
*/
|
||||
public function removeMatchingRules(string $searchPattern): void
|
||||
{
|
||||
$this->removeMatchingDeclarations($searchPattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated in v9.2, will be removed in v10.0; use `removeAllDeclarations()` instead.
|
||||
*/
|
||||
public function removeAllRules(): void
|
||||
{
|
||||
$this->removeAllDeclarations();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Declaration> $declarations
|
||||
*
|
||||
* @deprecated in v9.2, will be removed in v10.0; use `setDeclarations()` instead.
|
||||
*/
|
||||
public function setRules(array $declarations): void
|
||||
{
|
||||
$this->setDeclarations($declarations);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int<0, max>, Declaration>
|
||||
*
|
||||
* @deprecated in v9.2, will be removed in v10.0; use `getDeclarations()` instead.
|
||||
*/
|
||||
public function getRules(?string $searchPattern = null): array
|
||||
{
|
||||
return $this->getDeclarations($searchPattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, Declaration>
|
||||
*
|
||||
* @deprecated in v9.2, will be removed in v10.0; use `getDeclarationsAssociative()` instead.
|
||||
*/
|
||||
public function getRulesAssoc(?string $searchPattern = null): array
|
||||
{
|
||||
return $this->getDeclarationsAssociative($searchPattern);
|
||||
}
|
||||
}
|
||||
@@ -4,33 +4,11 @@
|
||||
|
||||
namespace Sabberworm\CSS\RuleSet;
|
||||
|
||||
use Sabberworm\CSS\Rule\Rule;
|
||||
|
||||
/**
|
||||
* Represents a CSS item that contains `Rules`, defining the methods to manipulate them.
|
||||
*/
|
||||
interface RuleContainer
|
||||
{
|
||||
public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void;
|
||||
|
||||
public function removeRule(Rule $ruleToRemove): void;
|
||||
|
||||
public function removeMatchingRules(string $searchPattern): void;
|
||||
|
||||
public function removeAllRules(): void;
|
||||
use function Safe\class_alias;
|
||||
|
||||
if (!\class_exists(RuleContainer::class, false) && !\interface_exists(RuleContainer::class, false)) {
|
||||
/**
|
||||
* @param array<Rule> $rules
|
||||
* @deprecated in v9.2, will be removed in v10.0. Use `DeclarationList` instead, which is a direct replacement.
|
||||
*/
|
||||
public function setRules(array $rules): void;
|
||||
|
||||
/**
|
||||
* @return array<int<0, max>, Rule>
|
||||
*/
|
||||
public function getRules(?string $searchPattern = null): array;
|
||||
|
||||
/**
|
||||
* @return array<string, Rule>
|
||||
*/
|
||||
public function getRulesAssoc(?string $searchPattern = null): array;
|
||||
class_alias(DeclarationList::class, RuleContainer::class);
|
||||
}
|
||||
|
||||
@@ -13,31 +13,32 @@
|
||||
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
|
||||
use Sabberworm\CSS\Position\Position;
|
||||
use Sabberworm\CSS\Position\Positionable;
|
||||
use Sabberworm\CSS\Rule\Rule;
|
||||
use Sabberworm\CSS\Property\Declaration;
|
||||
|
||||
/**
|
||||
* This class is a container for individual 'Rule's.
|
||||
* This class is a container for individual `Declaration`s.
|
||||
*
|
||||
* The most common form of a rule set is one constrained by a selector, i.e., a `DeclarationBlock`.
|
||||
* However, unknown `AtRule`s (like `@font-face`) are rule sets as well.
|
||||
*
|
||||
* If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $rule)`, `getRules()` and `removeRule($rule)`
|
||||
* (which accepts either a `Rule` or a rule name; optionally suffixed by a dash to remove all related rules).
|
||||
* If you want to manipulate a `RuleSet`,
|
||||
* use the methods `addDeclaration()`, `getDeclarations()`, `removeDeclaration()`, `removeMatchingDeclarations()`, etc.
|
||||
*
|
||||
* Note that `CSSListItem` extends both `Commentable` and `Renderable`, so those interfaces must also be implemented.
|
||||
*/
|
||||
class RuleSet implements CSSElement, CSSListItem, Positionable, RuleContainer
|
||||
class RuleSet implements CSSElement, CSSListItem, Positionable, DeclarationList
|
||||
{
|
||||
use CommentContainer;
|
||||
use LegacyDeclarationListMethods;
|
||||
use Position;
|
||||
|
||||
/**
|
||||
* the rules in this rule set, using the property name as the key,
|
||||
* with potentially multiple rules per property name.
|
||||
* the declarations in this rule set, using the property name as the key,
|
||||
* with potentially multiple declarations per property name.
|
||||
*
|
||||
* @var array<string, array<int<0, max>, Rule>>
|
||||
* @var array<string, array<int<0, max>, Declaration>>
|
||||
*/
|
||||
private $rules = [];
|
||||
private $declarations = [];
|
||||
|
||||
/**
|
||||
* @param int<1, max>|null $lineNumber
|
||||
@@ -59,14 +60,15 @@ public static function parseRuleSet(ParserState $parserState, RuleSet $ruleSet):
|
||||
$parserState->consume(';');
|
||||
}
|
||||
while (true) {
|
||||
$commentsBeforeRule = $parserState->consumeWhiteSpace();
|
||||
$commentsBeforeDeclaration = [];
|
||||
$parserState->consumeWhiteSpace($commentsBeforeDeclaration);
|
||||
if ($parserState->comes('}')) {
|
||||
break;
|
||||
}
|
||||
$rule = null;
|
||||
$declaration = null;
|
||||
if ($parserState->getSettings()->usesLenientParsing()) {
|
||||
try {
|
||||
$rule = Rule::parse($parserState, $commentsBeforeRule);
|
||||
$declaration = Declaration::parse($parserState, $commentsBeforeDeclaration);
|
||||
} catch (UnexpectedTokenException $e) {
|
||||
try {
|
||||
$consumedText = $parserState->consumeUntil(["\n", ';', '}'], true);
|
||||
@@ -84,10 +86,10 @@ public static function parseRuleSet(ParserState $parserState, RuleSet $ruleSet):
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$rule = Rule::parse($parserState, $commentsBeforeRule);
|
||||
$declaration = Declaration::parse($parserState, $commentsBeforeDeclaration);
|
||||
}
|
||||
if ($rule instanceof Rule) {
|
||||
$ruleSet->addRule($rule);
|
||||
if ($declaration instanceof Declaration) {
|
||||
$ruleSet->addDeclaration($declaration);
|
||||
}
|
||||
}
|
||||
$parserState->consume('}');
|
||||
@@ -95,31 +97,31 @@ public static function parseRuleSet(ParserState $parserState, RuleSet $ruleSet):
|
||||
|
||||
/**
|
||||
* @throws \UnexpectedValueException
|
||||
* if the last `Rule` is needed as a basis for setting position, but does not have a valid position,
|
||||
* if the last `Declaration` is needed as a basis for setting position, but does not have a valid position,
|
||||
* which should never happen
|
||||
*/
|
||||
public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void
|
||||
public function addDeclaration(Declaration $declarationToAdd, ?Declaration $sibling = null): void
|
||||
{
|
||||
$propertyName = $ruleToAdd->getRule();
|
||||
if (!isset($this->rules[$propertyName])) {
|
||||
$this->rules[$propertyName] = [];
|
||||
$propertyName = $declarationToAdd->getPropertyName();
|
||||
if (!isset($this->declarations[$propertyName])) {
|
||||
$this->declarations[$propertyName] = [];
|
||||
}
|
||||
|
||||
$position = \count($this->rules[$propertyName]);
|
||||
$position = \count($this->declarations[$propertyName]);
|
||||
|
||||
if ($sibling !== null) {
|
||||
$siblingIsInSet = false;
|
||||
$siblingPosition = \array_search($sibling, $this->rules[$propertyName], true);
|
||||
$siblingPosition = \array_search($sibling, $this->declarations[$propertyName], true);
|
||||
if ($siblingPosition !== false) {
|
||||
$siblingIsInSet = true;
|
||||
$position = $siblingPosition;
|
||||
} else {
|
||||
$siblingIsInSet = $this->hasRule($sibling);
|
||||
$siblingIsInSet = $this->hasDeclaration($sibling);
|
||||
if ($siblingIsInSet) {
|
||||
// Maintain ordering within `$this->rules[$propertyName]`
|
||||
// by inserting before first `Rule` with a same-or-later position than the sibling.
|
||||
foreach ($this->rules[$propertyName] as $index => $rule) {
|
||||
if (self::comparePositionable($rule, $sibling) >= 0) {
|
||||
// Maintain ordering within `$this->declarations[$propertyName]`
|
||||
// by inserting before first `Declaration` with a same-or-later position than the sibling.
|
||||
foreach ($this->declarations[$propertyName] as $index => $declaration) {
|
||||
if (self::comparePositionable($declaration, $sibling) >= 0) {
|
||||
$position = $index;
|
||||
break;
|
||||
}
|
||||
@@ -127,69 +129,71 @@ public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void
|
||||
}
|
||||
}
|
||||
if ($siblingIsInSet) {
|
||||
// Increment column number of all existing rules on same line, starting at sibling
|
||||
// Increment column number of all existing declarations on same line, starting at sibling
|
||||
$siblingLineNumber = $sibling->getLineNumber();
|
||||
$siblingColumnNumber = $sibling->getColumnNumber();
|
||||
foreach ($this->rules as $rulesForAProperty) {
|
||||
foreach ($rulesForAProperty as $rule) {
|
||||
foreach ($this->declarations as $declarationsForAProperty) {
|
||||
foreach ($declarationsForAProperty as $declaration) {
|
||||
if (
|
||||
$rule->getLineNumber() === $siblingLineNumber &&
|
||||
$rule->getColumnNumber() >= $siblingColumnNumber
|
||||
$declaration->getLineNumber() === $siblingLineNumber &&
|
||||
$declaration->getColumnNumber() >= $siblingColumnNumber
|
||||
) {
|
||||
$rule->setPosition($siblingLineNumber, $rule->getColumnNumber() + 1);
|
||||
$declaration->setPosition($siblingLineNumber, $declaration->getColumnNumber() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
$ruleToAdd->setPosition($siblingLineNumber, $siblingColumnNumber);
|
||||
$declarationToAdd->setPosition($siblingLineNumber, $siblingColumnNumber);
|
||||
}
|
||||
}
|
||||
|
||||
if ($ruleToAdd->getLineNumber() === null) {
|
||||
if ($declarationToAdd->getLineNumber() === null) {
|
||||
//this node is added manually, give it the next best line
|
||||
$columnNumber = $ruleToAdd->getColumnNumber() ?? 0;
|
||||
$rules = $this->getRules();
|
||||
$rulesCount = \count($rules);
|
||||
if ($rulesCount > 0) {
|
||||
$last = $rules[$rulesCount - 1];
|
||||
$columnNumber = $declarationToAdd->getColumnNumber() ?? 0;
|
||||
$declarations = $this->getDeclarations();
|
||||
$declarationsCount = \count($declarations);
|
||||
if ($declarationsCount > 0) {
|
||||
$last = $declarations[$declarationsCount - 1];
|
||||
$lastsLineNumber = $last->getLineNumber();
|
||||
if (!\is_int($lastsLineNumber)) {
|
||||
throw new \UnexpectedValueException(
|
||||
'A Rule without a line number was found during addRule',
|
||||
'A Declaration without a line number was found during addDeclaration',
|
||||
1750718399
|
||||
);
|
||||
}
|
||||
$ruleToAdd->setPosition($lastsLineNumber + 1, $columnNumber);
|
||||
$declarationToAdd->setPosition($lastsLineNumber + 1, $columnNumber);
|
||||
} else {
|
||||
$ruleToAdd->setPosition(1, $columnNumber);
|
||||
$declarationToAdd->setPosition(1, $columnNumber);
|
||||
}
|
||||
} elseif ($ruleToAdd->getColumnNumber() === null) {
|
||||
$ruleToAdd->setPosition($ruleToAdd->getLineNumber(), 0);
|
||||
} elseif ($declarationToAdd->getColumnNumber() === null) {
|
||||
$declarationToAdd->setPosition($declarationToAdd->getLineNumber(), 0);
|
||||
}
|
||||
|
||||
\array_splice($this->rules[$propertyName], $position, 0, [$ruleToAdd]);
|
||||
\array_splice($this->declarations[$propertyName], $position, 0, [$declarationToAdd]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all rules matching the given rule name
|
||||
* Returns all declarations matching the given property name
|
||||
*
|
||||
* @example $ruleSet->getRules('font') // returns array(0 => $rule, …) or array().
|
||||
* @example $ruleSet->getDeclarations('font') // returns array(0 => $declaration, …) or array().
|
||||
*
|
||||
* @example $ruleSet->getRules('font-')
|
||||
* //returns an array of all rules either beginning with font- or matching font.
|
||||
* @example $ruleSet->getDeclarations('font-')
|
||||
* //returns an array of all declarations either beginning with font- or matching font.
|
||||
*
|
||||
* @param string|null $searchPattern
|
||||
* Pattern to search for. If null, returns all rules.
|
||||
* If the pattern ends with a dash, all rules starting with the pattern are returned
|
||||
* Pattern to search for. If null, returns all declarations.
|
||||
* If the pattern ends with a dash, all declarations starting with the pattern are returned
|
||||
* as well as one matching the pattern with the dash excluded.
|
||||
*
|
||||
* @return array<int<0, max>, Rule>
|
||||
* @return array<int<0, max>, Declaration>
|
||||
*/
|
||||
public function getRules(?string $searchPattern = null): array
|
||||
public function getDeclarations(?string $searchPattern = null): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($this->rules as $propertyName => $rules) {
|
||||
// Either no search rule is given or the search rule matches the found rule exactly
|
||||
// or the search rule ends in “-” and the found rule starts with the search rule.
|
||||
foreach ($this->declarations as $propertyName => $declarations) {
|
||||
// Either no search pattern was given
|
||||
// or the search pattern matches the found declaration's property name exactly
|
||||
// or the search pattern ends in “-”
|
||||
// ... and the found declaration's property name starts with the search pattern
|
||||
if (
|
||||
$searchPattern === null || $propertyName === $searchPattern
|
||||
|| (
|
||||
@@ -198,7 +202,7 @@ public function getRules(?string $searchPattern = null): array
|
||||
|| $propertyName === \substr($searchPattern, 0, -1))
|
||||
)
|
||||
) {
|
||||
$result = \array_merge($result, $rules);
|
||||
$result = \array_merge($result, $declarations);
|
||||
}
|
||||
}
|
||||
\usort($result, [self::class, 'comparePositionable']);
|
||||
@@ -207,89 +211,90 @@ public function getRules(?string $searchPattern = null): array
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides all the rules of this set.
|
||||
* Overrides all the declarations of this set.
|
||||
*
|
||||
* @param array<Rule> $rules The rules to override with.
|
||||
* @param array<Declaration> $declarations
|
||||
*/
|
||||
public function setRules(array $rules): void
|
||||
public function setDeclarations(array $declarations): void
|
||||
{
|
||||
$this->rules = [];
|
||||
foreach ($rules as $rule) {
|
||||
$this->addRule($rule);
|
||||
$this->declarations = [];
|
||||
foreach ($declarations as $declaration) {
|
||||
$this->addDeclaration($declaration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all rules matching the given pattern and returns them in an associative array with the rule’s name
|
||||
* as keys. This method exists mainly for backwards-compatibility and is really only partially useful.
|
||||
* Returns all declarations with property names matching the given pattern and returns them in an associative array
|
||||
* with the property names as keys.
|
||||
* This method exists mainly for backwards-compatibility and is really only partially useful.
|
||||
*
|
||||
* Note: This method loses some information: Calling this (with an argument of `background-`) on a declaration block
|
||||
* like `{ background-color: green; background-color; rgba(0, 127, 0, 0.7); }` will only yield an associative array
|
||||
* containing the rgba-valued rule while `getRules()` would yield an indexed array containing both.
|
||||
* containing the rgba-valued declaration while `getDeclarations()` would yield an indexed array containing both.
|
||||
*
|
||||
* @param string|null $searchPattern
|
||||
* Pattern to search for. If null, returns all rules. If the pattern ends with a dash,
|
||||
* all rules starting with the pattern are returned as well as one matching the pattern with the dash
|
||||
* Pattern to search for. If null, returns all declarations. If the pattern ends with a dash,
|
||||
* all declarations starting with the pattern are returned as well as one matching the pattern with the dash
|
||||
* excluded.
|
||||
*
|
||||
* @return array<string, Rule>
|
||||
* @return array<string, Declaration>
|
||||
*/
|
||||
public function getRulesAssoc(?string $searchPattern = null): array
|
||||
public function getDeclarationsAssociative(?string $searchPattern = null): array
|
||||
{
|
||||
/** @var array<string, Rule> $result */
|
||||
/** @var array<string, Declaration> $result */
|
||||
$result = [];
|
||||
foreach ($this->getRules($searchPattern) as $rule) {
|
||||
$result[$rule->getRule()] = $rule;
|
||||
foreach ($this->getDeclarations($searchPattern) as $declaration) {
|
||||
$result[$declaration->getPropertyName()] = $declaration;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a `Rule` from this `RuleSet` by identity.
|
||||
* Removes a `Declaration` from this `RuleSet` by identity.
|
||||
*/
|
||||
public function removeRule(Rule $ruleToRemove): void
|
||||
public function removeDeclaration(Declaration $declarationToRemove): void
|
||||
{
|
||||
$nameOfPropertyToRemove = $ruleToRemove->getRule();
|
||||
if (!isset($this->rules[$nameOfPropertyToRemove])) {
|
||||
$nameOfPropertyToRemove = $declarationToRemove->getPropertyName();
|
||||
if (!isset($this->declarations[$nameOfPropertyToRemove])) {
|
||||
return;
|
||||
}
|
||||
foreach ($this->rules[$nameOfPropertyToRemove] as $key => $rule) {
|
||||
if ($rule === $ruleToRemove) {
|
||||
unset($this->rules[$nameOfPropertyToRemove][$key]);
|
||||
foreach ($this->declarations[$nameOfPropertyToRemove] as $key => $declaration) {
|
||||
if ($declaration === $declarationToRemove) {
|
||||
unset($this->declarations[$nameOfPropertyToRemove][$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes rules by property name or search pattern.
|
||||
* Removes declarations by property name or search pattern.
|
||||
*
|
||||
* @param string $searchPattern
|
||||
* pattern to remove.
|
||||
* If the pattern ends in a dash,
|
||||
* all rules starting with the pattern are removed as well as one matching the pattern with the dash
|
||||
* all declarations starting with the pattern are removed as well as one matching the pattern with the dash
|
||||
* excluded.
|
||||
*/
|
||||
public function removeMatchingRules(string $searchPattern): void
|
||||
public function removeMatchingDeclarations(string $searchPattern): void
|
||||
{
|
||||
foreach ($this->rules as $propertyName => $rules) {
|
||||
// Either the search rule matches the found rule exactly
|
||||
// or the search rule ends in “-” and the found rule starts with the search rule or equals it
|
||||
// (without the trailing dash).
|
||||
foreach ($this->declarations as $propertyName => $declarations) {
|
||||
// Either the search pattern matches the found declaration's property name exactly
|
||||
// or the search pattern ends in “-” and the found declaration's property name starts with the search
|
||||
// pattern or equals it (without the trailing dash).
|
||||
if (
|
||||
$propertyName === $searchPattern
|
||||
|| (\strrpos($searchPattern, '-') === \strlen($searchPattern) - \strlen('-')
|
||||
&& (\strpos($propertyName, $searchPattern) === 0
|
||||
|| $propertyName === \substr($searchPattern, 0, -1)))
|
||||
) {
|
||||
unset($this->rules[$propertyName]);
|
||||
unset($this->declarations[$propertyName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function removeAllRules(): void
|
||||
public function removeAllDeclarations(): void
|
||||
{
|
||||
$this->rules = [];
|
||||
$this->declarations = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -297,20 +302,22 @@ public function removeAllRules(): void
|
||||
*/
|
||||
public function render(OutputFormat $outputFormat): string
|
||||
{
|
||||
return $this->renderRules($outputFormat);
|
||||
return $this->renderDeclarations($outputFormat);
|
||||
}
|
||||
|
||||
protected function renderRules(OutputFormat $outputFormat): string
|
||||
protected function renderDeclarations(OutputFormat $outputFormat): string
|
||||
{
|
||||
$result = '';
|
||||
$isFirst = true;
|
||||
$nextLevelFormat = $outputFormat->nextLevel();
|
||||
foreach ($this->getRules() as $rule) {
|
||||
foreach ($this->getDeclarations() as $declaration) {
|
||||
$nextLevelFormatter = $nextLevelFormat->getFormatter();
|
||||
$renderedRule = $nextLevelFormatter->safely(static function () use ($rule, $nextLevelFormat): string {
|
||||
return $rule->render($nextLevelFormat);
|
||||
});
|
||||
if ($renderedRule === null) {
|
||||
$renderedDeclaration = $nextLevelFormatter->safely(
|
||||
static function () use ($declaration, $nextLevelFormat): string {
|
||||
return $declaration->render($nextLevelFormat);
|
||||
}
|
||||
);
|
||||
if ($renderedDeclaration === null) {
|
||||
continue;
|
||||
}
|
||||
if ($isFirst) {
|
||||
@@ -319,7 +326,7 @@ protected function renderRules(OutputFormat $outputFormat): string
|
||||
} else {
|
||||
$result .= $nextLevelFormatter->spaceBetweenRules();
|
||||
}
|
||||
$result .= $renderedRule;
|
||||
$result .= $renderedDeclaration;
|
||||
}
|
||||
|
||||
$formatter = $outputFormat->getFormatter();
|
||||
@@ -331,6 +338,16 @@ protected function renderRules(OutputFormat $outputFormat): string
|
||||
return $formatter->removeLastSemicolon($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|int|float|string|array<mixed>|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getArrayRepresentation(): array
|
||||
{
|
||||
throw new \BadMethodCallException('`getArrayRepresentation` is not yet implemented for `' . self::class . '`');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int negative if `$first` is before `$second`; zero if they have the same position; positive otherwise
|
||||
*
|
||||
@@ -342,7 +359,7 @@ private static function comparePositionable(Positionable $first, Positionable $s
|
||||
$secondsLineNumber = $second->getLineNumber();
|
||||
if (!\is_int($firstsLineNumber) || !\is_int($secondsLineNumber)) {
|
||||
throw new \UnexpectedValueException(
|
||||
'A Rule without a line number was passed to comparePositionable',
|
||||
'A Declaration without a line number was passed to comparePositionable',
|
||||
1750637683
|
||||
);
|
||||
}
|
||||
@@ -352,7 +369,7 @@ private static function comparePositionable(Positionable $first, Positionable $s
|
||||
$secondsColumnNumber = $second->getColumnNumber();
|
||||
if (!\is_int($firstsColumnNumber) || !\is_int($secondsColumnNumber)) {
|
||||
throw new \UnexpectedValueException(
|
||||
'A Rule without a column number was passed to comparePositionable',
|
||||
'A Declaration without a column number was passed to comparePositionable',
|
||||
1750637761
|
||||
);
|
||||
}
|
||||
@@ -362,10 +379,10 @@ private static function comparePositionable(Positionable $first, Positionable $s
|
||||
return $firstsLineNumber - $secondsLineNumber;
|
||||
}
|
||||
|
||||
private function hasRule(Rule $rule): bool
|
||||
private function hasDeclaration(Declaration $declaration): bool
|
||||
{
|
||||
foreach ($this->rules as $rulesForAProperty) {
|
||||
if (\in_array($rule, $rulesForAProperty, true)) {
|
||||
foreach ($this->declarations as $declarationsForAProperty) {
|
||||
if (\in_array($declaration, $declarationsForAProperty, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
21
vendor/sabberworm/php-css-parser/src/ShortClassNameProvider.php
vendored
Normal file
21
vendor/sabberworm/php-css-parser/src/ShortClassNameProvider.php
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabberworm\CSS;
|
||||
|
||||
/**
|
||||
* Provides a method to obtain the short name of the instantiated class (i.e. without namespace prefix).
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait ShortClassNameProvider
|
||||
{
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
private function getShortClassName(): string
|
||||
{
|
||||
return (new \ReflectionClass($this))->getShortName();
|
||||
}
|
||||
}
|
||||
@@ -113,4 +113,20 @@ public function render(OutputFormat $outputFormat): string
|
||||
$arguments = parent::render($outputFormat);
|
||||
return "{$this->name}({$arguments})";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|int|float|string|array<mixed>|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getArrayRepresentation(): array
|
||||
{
|
||||
return \array_merge(
|
||||
[
|
||||
'class' => 'placeholder',
|
||||
'name' => $this->name,
|
||||
],
|
||||
parent::getArrayRepresentation()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
use Sabberworm\CSS\Parsing\SourceException;
|
||||
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
|
||||
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
|
||||
use Sabberworm\CSS\ShortClassNameProvider;
|
||||
|
||||
use function Safe\preg_match;
|
||||
|
||||
/**
|
||||
* This class is a wrapper for quoted strings to distinguish them from keywords.
|
||||
@@ -17,6 +20,8 @@
|
||||
*/
|
||||
class CSSString extends PrimitiveValue
|
||||
{
|
||||
use ShortClassNameProvider;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
@@ -54,7 +59,7 @@ public static function parse(ParserState $parserState): CSSString
|
||||
$content = null;
|
||||
if ($quote === null) {
|
||||
// Unquoted strings end in whitespace or with braces, brackets, parentheses
|
||||
while (\preg_match('/[\\s{}()<>\\[\\]]/isu', $parserState->peek()) !== 1) {
|
||||
while (preg_match('/[\\s{}()<>\\[\\]]/isu', $parserState->peek()) === 0) {
|
||||
$result .= $parserState->parseCharacter(false);
|
||||
}
|
||||
} else {
|
||||
@@ -88,8 +93,33 @@ public function getString(): string
|
||||
*/
|
||||
public function render(OutputFormat $outputFormat): string
|
||||
{
|
||||
$string = \addslashes($this->string);
|
||||
$string = \str_replace("\n", '\\A', $string);
|
||||
return $outputFormat->getStringQuotingType() . $string . $outputFormat->getStringQuotingType();
|
||||
return $outputFormat->getStringQuotingType()
|
||||
. $this->escape($this->string, $outputFormat)
|
||||
. $outputFormat->getStringQuotingType();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|int|float|string|array<mixed>|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getArrayRepresentation(): array
|
||||
{
|
||||
return [
|
||||
'class' => $this->getShortClassName(),
|
||||
// We're using the term "contents" here to make the difference to the class more clear.
|
||||
'contents' => $this->string,
|
||||
];
|
||||
}
|
||||
|
||||
private function escape(string $string, OutputFormat $outputFormat): string
|
||||
{
|
||||
$charactersToEscape = '\\';
|
||||
$charactersToEscape .= ($outputFormat->getStringQuotingType() === '"' ? '"' : "'");
|
||||
$withEscapedQuotes = \addcslashes($string, $charactersToEscape);
|
||||
|
||||
$withNewlineEncoded = \str_replace("\n", '\\A', $withEscapedQuotes);
|
||||
|
||||
return $withNewlineEncoded;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,11 @@
|
||||
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
|
||||
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
|
||||
|
||||
use function Safe\preg_match;
|
||||
|
||||
class CalcFunction extends CSSFunction
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private const T_OPERAND = 1;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private const T_OPERATOR = 2;
|
||||
|
||||
/**
|
||||
@@ -67,9 +62,8 @@ public static function parse(ParserState $parserState, bool $ignoreCase = false)
|
||||
if (\in_array($parserState->peek(), $operators, true)) {
|
||||
if (($parserState->comes('-') || $parserState->comes('+'))) {
|
||||
if (
|
||||
$parserState->peek(1, -1) !== ' '
|
||||
|| !($parserState->comes('- ')
|
||||
|| $parserState->comes('+ '))
|
||||
preg_match('/\\s/', $parserState->peek(1, -1)) !== 1
|
||||
|| preg_match('/\\s/', $parserState->peek(1, 1)) !== 1
|
||||
) {
|
||||
throw new UnexpectedTokenException(
|
||||
" {$parserState->peek()} ",
|
||||
@@ -102,4 +96,14 @@ public static function parse(ParserState $parserState, bool $ignoreCase = false)
|
||||
}
|
||||
return new CalcFunction($function, $list, ',', $parserState->currentLine());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|int|float|string|array<mixed>|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getArrayRepresentation(): array
|
||||
{
|
||||
throw new \BadMethodCallException('`getArrayRepresentation` is not yet implemented for `' . self::class . '`');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,4 +20,14 @@ public function render(OutputFormat $outputFormat): string
|
||||
{
|
||||
return $outputFormat->getFormatter()->implode(' ', $this->components);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|int|float|string|array<mixed>|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getArrayRepresentation(): array
|
||||
{
|
||||
throw new \BadMethodCallException('`getArrayRepresentation` is not yet implemented for `' . self::class . '`');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,6 +239,16 @@ public function render(OutputFormat $outputFormat): string
|
||||
return parent::render($outputFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|int|float|string|array<mixed>|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getArrayRepresentation(): array
|
||||
{
|
||||
throw new \BadMethodCallException('`getArrayRepresentation` is not yet implemented for `' . self::class . '`');
|
||||
}
|
||||
|
||||
private function shouldRenderAsHex(OutputFormat $outputFormat): bool
|
||||
{
|
||||
return
|
||||
|
||||
@@ -56,4 +56,14 @@ public function render(OutputFormat $outputFormat): string
|
||||
{
|
||||
return '[' . parent::render(OutputFormat::createCompact()) . ']';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|int|float|string|array<mixed>|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getArrayRepresentation(): array
|
||||
{
|
||||
throw new \BadMethodCallException('`getArrayRepresentation` is not yet implemented for `' . self::class . '`');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,20 @@
|
||||
use Sabberworm\CSS\Parsing\ParserState;
|
||||
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
|
||||
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
|
||||
use Sabberworm\CSS\ShortClassNameProvider;
|
||||
|
||||
use function Safe\preg_match;
|
||||
use function Safe\preg_replace;
|
||||
|
||||
/**
|
||||
* A `Size` consists of a numeric `size` value and a unit.
|
||||
*/
|
||||
class Size extends PrimitiveValue
|
||||
{
|
||||
use ShortClassNameProvider;
|
||||
|
||||
/**
|
||||
* vh/vw/vm(ax)/vmin/rem are absolute insofar as they don’t scale to the immediate parent (only the viewport)
|
||||
*
|
||||
* @var list<non-empty-string>
|
||||
*/
|
||||
private const ABSOLUTE_SIZE_UNITS = [
|
||||
'px',
|
||||
@@ -37,14 +41,8 @@ class Size extends PrimitiveValue
|
||||
'rem',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var list<non-empty-string>
|
||||
*/
|
||||
private const RELATIVE_SIZE_UNITS = ['%', 'em', 'ex', 'ch', 'fr'];
|
||||
|
||||
/**
|
||||
* @var list<non-empty-string>
|
||||
*/
|
||||
private const NON_SIZE_UNITS = ['deg', 'grad', 'rad', 's', 'ms', 'turn', 'Hz', 'kHz'];
|
||||
|
||||
/**
|
||||
@@ -202,9 +200,24 @@ public function render(OutputFormat $outputFormat): string
|
||||
{
|
||||
$locale = \localeconv();
|
||||
$decimalPoint = \preg_quote($locale['decimal_point'], '/');
|
||||
$size = \preg_match('/[\\d\\.]+e[+-]?\\d+/i', (string) $this->size)
|
||||
? \preg_replace("/$decimalPoint?0+$/", '', \sprintf('%f', $this->size)) : (string) $this->size;
|
||||
$size = preg_match('/[\\d\\.]+e[+-]?\\d+/i', (string) $this->size) === 1
|
||||
? preg_replace("/$decimalPoint?0+$/", '', \sprintf('%f', $this->size)) : (string) $this->size;
|
||||
|
||||
return \preg_replace(["/$decimalPoint/", '/^(-?)0\\./'], ['.', '$1.'], $size) . ($this->unit ?? '');
|
||||
return preg_replace(["/$decimalPoint/", '/^(-?)0\\./'], ['.', '$1.'], $size) . ($this->unit ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|int|float|string|array<mixed>|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getArrayRepresentation(): array
|
||||
{
|
||||
return [
|
||||
'class' => $this->getShortClassName(),
|
||||
// 'number' is the official W3C terminology (not 'size')
|
||||
'number' => $this->size,
|
||||
'unit' => $this->unit,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,15 @@
|
||||
use Sabberworm\CSS\Parsing\SourceException;
|
||||
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
|
||||
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
|
||||
use Sabberworm\CSS\ShortClassNameProvider;
|
||||
|
||||
/**
|
||||
* This class represents URLs in CSS. `URL`s always output in `URL("")` notation.
|
||||
*/
|
||||
class URL extends PrimitiveValue
|
||||
{
|
||||
use ShortClassNameProvider;
|
||||
|
||||
/**
|
||||
* @var CSSString
|
||||
*/
|
||||
@@ -80,4 +83,19 @@ public function render(OutputFormat $outputFormat): string
|
||||
{
|
||||
return "url({$this->url->render($outputFormat)})";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|int|float|string|array<mixed>|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getArrayRepresentation(): array
|
||||
{
|
||||
return [
|
||||
'class' => $this->getShortClassName(),
|
||||
// We're using the term "uri" here to match the wording used in the specs:
|
||||
// https://www.w3.org/TR/CSS22/syndata.html#uri
|
||||
'uri' => $this->url->getArrayRepresentation(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
|
||||
use Sabberworm\CSS\Position\Position;
|
||||
use Sabberworm\CSS\Position\Positionable;
|
||||
use Sabberworm\CSS\ShortClassNameProvider;
|
||||
|
||||
use function Safe\preg_match;
|
||||
|
||||
/**
|
||||
* Abstract base class for specific classes of CSS values: `Size`, `Color`, `CSSString` and `URL`, and another
|
||||
@@ -19,6 +22,7 @@
|
||||
abstract class Value implements CSSElement, Positionable
|
||||
{
|
||||
use Position;
|
||||
use ShortClassNameProvider;
|
||||
|
||||
/**
|
||||
* @param int<1, max>|null $lineNumber
|
||||
@@ -179,6 +183,18 @@ public static function parsePrimitiveValue(ParserState $parserState)
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|int|float|string|array<mixed>|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getArrayRepresentation(): array
|
||||
{
|
||||
return [
|
||||
'class' => $this->getShortClassName(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws UnexpectedEOFException
|
||||
* @throws UnexpectedTokenException
|
||||
@@ -204,7 +220,9 @@ private static function parseUnicodeRangeValue(ParserState $parserState): string
|
||||
$codepointMaxLength = 13; // Max length is 2 six-digit code points + the dash(-) between them
|
||||
}
|
||||
$range .= $parserState->consume(1);
|
||||
} while (\strlen($range) < $codepointMaxLength && \preg_match('/[A-Fa-f0-9\\?-]/', $parserState->peek()));
|
||||
} while (
|
||||
(\strlen($range) < $codepointMaxLength) && (preg_match('/[A-Fa-f0-9\\?-]/', $parserState->peek()) === 1)
|
||||
);
|
||||
|
||||
return "U+{$range}";
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
namespace Sabberworm\CSS\Value;
|
||||
|
||||
use Sabberworm\CSS\OutputFormat;
|
||||
use Sabberworm\CSS\ShortClassNameProvider;
|
||||
|
||||
/**
|
||||
* A `ValueList` represents a lists of `Value`s, separated by some separation character
|
||||
@@ -14,6 +15,8 @@
|
||||
*/
|
||||
abstract class ValueList extends Value
|
||||
{
|
||||
use ShortClassNameProvider;
|
||||
|
||||
/**
|
||||
* @var array<Value|string>
|
||||
*
|
||||
@@ -93,4 +96,29 @@ public function render(OutputFormat $outputFormat): string
|
||||
$this->components
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|int|float|string|array<mixed>|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getArrayRepresentation(): array
|
||||
{
|
||||
return [
|
||||
'class' => $this->getShortClassName(),
|
||||
'components' => \array_map(
|
||||
/**
|
||||
* @parm Value|string $component
|
||||
*/
|
||||
function ($component): array {
|
||||
if (\is_string($component)) {
|
||||
return ['class' => 'string', 'value' => $component];
|
||||
}
|
||||
return $component->getArrayRepresentation();
|
||||
},
|
||||
$this->components
|
||||
),
|
||||
'separator' => $this->separator,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user