Vendor packages updated
This commit is contained in:
72
vendor/sabberworm/php-css-parser/src/CSSList/AtRuleBlockList.php
vendored
Normal file
72
vendor/sabberworm/php-css-parser/src/CSSList/AtRuleBlockList.php
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabberworm\CSS\CSSList;
|
||||
|
||||
use Sabberworm\CSS\OutputFormat;
|
||||
use Sabberworm\CSS\Property\AtRule;
|
||||
|
||||
/**
|
||||
* A `BlockList` constructed by an unknown at-rule. `@media` rules are rendered into `AtRuleBlockList` objects.
|
||||
*/
|
||||
class AtRuleBlockList extends CSSBlockList implements AtRule
|
||||
{
|
||||
/**
|
||||
* @var non-empty-string
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $arguments;
|
||||
|
||||
/**
|
||||
* @param non-empty-string $type
|
||||
* @param int<1, max>|null $lineNumber
|
||||
*/
|
||||
public function __construct(string $type, string $arguments = '', ?int $lineNumber = null)
|
||||
{
|
||||
parent::__construct($lineNumber);
|
||||
$this->type = $type;
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function atRuleName(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function atRuleArgs(): string
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function render(OutputFormat $outputFormat): string
|
||||
{
|
||||
$formatter = $outputFormat->getFormatter();
|
||||
$result = $formatter->comments($this);
|
||||
$result .= $outputFormat->getContentBeforeAtRuleBlock();
|
||||
$arguments = $this->arguments;
|
||||
if ($arguments !== '') {
|
||||
$arguments = ' ' . $arguments;
|
||||
}
|
||||
$result .= "@{$this->type}$arguments{$formatter->spaceBeforeOpeningBrace()}{";
|
||||
$result .= $this->renderListContents($outputFormat);
|
||||
$result .= '}';
|
||||
$result .= $outputFormat->getContentAfterAtRuleBlock();
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function isRootList(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
183
vendor/sabberworm/php-css-parser/src/CSSList/CSSBlockList.php
vendored
Normal file
183
vendor/sabberworm/php-css-parser/src/CSSList/CSSBlockList.php
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabberworm\CSS\CSSList;
|
||||
|
||||
use Sabberworm\CSS\CSSElement;
|
||||
use Sabberworm\CSS\Property\Declaration;
|
||||
use Sabberworm\CSS\Property\Selector;
|
||||
use Sabberworm\CSS\RuleSet\DeclarationBlock;
|
||||
use Sabberworm\CSS\RuleSet\DeclarationList;
|
||||
use Sabberworm\CSS\RuleSet\RuleSet;
|
||||
use Sabberworm\CSS\Value\CSSFunction;
|
||||
use Sabberworm\CSS\Value\Value;
|
||||
use Sabberworm\CSS\Value\ValueList;
|
||||
|
||||
/**
|
||||
* A `CSSBlockList` is a `CSSList` whose `DeclarationBlock`s are guaranteed to contain valid declaration blocks or
|
||||
* at-rules.
|
||||
*
|
||||
* Most `CSSList`s conform to this category but some at-rules (such as `@keyframes`) do not.
|
||||
*/
|
||||
abstract class CSSBlockList extends CSSList
|
||||
{
|
||||
/**
|
||||
* Gets all `DeclarationBlock` objects recursively, no matter how deeply nested the selectors are.
|
||||
*
|
||||
* @return list<DeclarationBlock>
|
||||
*/
|
||||
public function getAllDeclarationBlocks(): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($this->contents as $item) {
|
||||
if ($item instanceof DeclarationBlock) {
|
||||
$result[] = $item;
|
||||
} elseif ($item instanceof CSSBlockList) {
|
||||
$result = \array_merge($result, $item->getAllDeclarationBlocks());
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all `RuleSet` objects recursively found in the tree, no matter how deeply nested the rule sets are.
|
||||
*
|
||||
* @return list<RuleSet>
|
||||
*/
|
||||
public function getAllRuleSets(): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($this->contents as $item) {
|
||||
if ($item instanceof RuleSet) {
|
||||
$result[] = $item;
|
||||
} elseif ($item instanceof CSSBlockList) {
|
||||
$result = \array_merge($result, $item->getAllRuleSets());
|
||||
} elseif ($item instanceof DeclarationBlock) {
|
||||
$result[] = $item->getRuleSet();
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
* @param string|null $ruleSearchPattern
|
||||
* This allows filtering rules by property name
|
||||
* (e.g. if "color" is passed, only `Value`s from `color` properties will be returned,
|
||||
* or if "font-" is provided, `Value`s from all font rules, like `font-size`, and including `font` itself,
|
||||
* will be returned).
|
||||
* @param bool $searchInFunctionArguments whether to also return `Value` objects used as `CSSFunction` arguments.
|
||||
*
|
||||
* @return list<Value>
|
||||
*
|
||||
* @see RuleSet->getRules()
|
||||
*/
|
||||
public function getAllValues(
|
||||
?CSSElement $element = null,
|
||||
?string $ruleSearchPattern = null,
|
||||
bool $searchInFunctionArguments = false
|
||||
): array {
|
||||
$element = $element ?? $this;
|
||||
|
||||
$result = [];
|
||||
if ($element instanceof CSSBlockList) {
|
||||
foreach ($element->getContents() as $contentItem) {
|
||||
// Statement at-rules are skipped since they do not contain values.
|
||||
if ($contentItem instanceof CSSElement) {
|
||||
$result = \array_merge(
|
||||
$result,
|
||||
$this->getAllValues($contentItem, $ruleSearchPattern, $searchInFunctionArguments)
|
||||
);
|
||||
}
|
||||
}
|
||||
} elseif ($element instanceof DeclarationList) {
|
||||
foreach ($element->getRules($ruleSearchPattern) as $rule) {
|
||||
$result = \array_merge(
|
||||
$result,
|
||||
$this->getAllValues($rule, $ruleSearchPattern, $searchInFunctionArguments)
|
||||
);
|
||||
}
|
||||
} elseif ($element instanceof Declaration) {
|
||||
$value = $element->getValue();
|
||||
// `string` values are discarded.
|
||||
if ($value instanceof CSSElement) {
|
||||
$result = \array_merge(
|
||||
$result,
|
||||
$this->getAllValues($value, $ruleSearchPattern, $searchInFunctionArguments)
|
||||
);
|
||||
}
|
||||
} elseif ($element instanceof ValueList) {
|
||||
if ($searchInFunctionArguments || !($element instanceof CSSFunction)) {
|
||||
foreach ($element->getListComponents() as $component) {
|
||||
// `string` components are discarded.
|
||||
if ($component instanceof CSSElement) {
|
||||
$result = \array_merge(
|
||||
$result,
|
||||
$this->getAllValues($component, $ruleSearchPattern, $searchInFunctionArguments)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif ($element instanceof Value) {
|
||||
$result[] = $element;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<Selector>
|
||||
*/
|
||||
protected function getAllSelectors(?string $specificitySearch = null): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($this->getAllDeclarationBlocks() as $declarationBlock) {
|
||||
foreach ($declarationBlock->getSelectors() as $selector) {
|
||||
if ($specificitySearch === null) {
|
||||
$result[] = $selector;
|
||||
} else {
|
||||
$comparator = '===';
|
||||
$expressionParts = \explode(' ', $specificitySearch);
|
||||
$targetSpecificity = $expressionParts[0];
|
||||
if (\count($expressionParts) > 1) {
|
||||
$comparator = $expressionParts[0];
|
||||
$targetSpecificity = $expressionParts[1];
|
||||
}
|
||||
$targetSpecificity = (int) $targetSpecificity;
|
||||
$selectorSpecificity = $selector->getSpecificity();
|
||||
switch ($comparator) {
|
||||
case '<=':
|
||||
$comparatorMatched = $selectorSpecificity <= $targetSpecificity;
|
||||
break;
|
||||
case '<':
|
||||
$comparatorMatched = $selectorSpecificity < $targetSpecificity;
|
||||
break;
|
||||
case '>=':
|
||||
$comparatorMatched = $selectorSpecificity >= $targetSpecificity;
|
||||
break;
|
||||
case '>':
|
||||
$comparatorMatched = $selectorSpecificity > $targetSpecificity;
|
||||
break;
|
||||
default:
|
||||
$comparatorMatched = $selectorSpecificity === $targetSpecificity;
|
||||
break;
|
||||
}
|
||||
if ($comparatorMatched) {
|
||||
$result[] = $selector;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
478
vendor/sabberworm/php-css-parser/src/CSSList/CSSList.php
vendored
Normal file
478
vendor/sabberworm/php-css-parser/src/CSSList/CSSList.php
vendored
Normal file
@@ -0,0 +1,478 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabberworm\CSS\CSSList;
|
||||
|
||||
use Sabberworm\CSS\Comment\CommentContainer;
|
||||
use Sabberworm\CSS\CSSElement;
|
||||
use Sabberworm\CSS\OutputFormat;
|
||||
use Sabberworm\CSS\Parsing\ParserState;
|
||||
use Sabberworm\CSS\Parsing\SourceException;
|
||||
use Sabberworm\CSS\Parsing\UnexpectedEOFException;
|
||||
use Sabberworm\CSS\Parsing\UnexpectedTokenException;
|
||||
use Sabberworm\CSS\Position\Position;
|
||||
use Sabberworm\CSS\Position\Positionable;
|
||||
use Sabberworm\CSS\Property\AtRule;
|
||||
use Sabberworm\CSS\Property\Charset;
|
||||
use Sabberworm\CSS\Property\CSSNamespace;
|
||||
use Sabberworm\CSS\Property\Import;
|
||||
use Sabberworm\CSS\Property\Selector;
|
||||
use Sabberworm\CSS\RuleSet\AtRuleSet;
|
||||
use Sabberworm\CSS\RuleSet\DeclarationBlock;
|
||||
use Sabberworm\CSS\RuleSet\RuleSet;
|
||||
use Sabberworm\CSS\Value\CSSString;
|
||||
use Sabberworm\CSS\Value\URL;
|
||||
use Sabberworm\CSS\Value\Value;
|
||||
|
||||
use function Safe\preg_match;
|
||||
|
||||
/**
|
||||
* This is the most generic container available. It can contain `DeclarationBlock`s (rule sets with a selector),
|
||||
* `RuleSet`s as well as other `CSSList` objects.
|
||||
*
|
||||
* It can also contain `Import` and `Charset` objects stemming from at-rules.
|
||||
*
|
||||
* Note that `CSSListItem` extends both `Commentable` and `Renderable`,
|
||||
* so those interfaces must also be implemented by concrete subclasses.
|
||||
*/
|
||||
abstract class CSSList implements CSSElement, CSSListItem, Positionable
|
||||
{
|
||||
use CommentContainer;
|
||||
use Position;
|
||||
|
||||
/**
|
||||
* @var array<int<0, max>, CSSListItem>
|
||||
*
|
||||
* @internal since 8.8.0
|
||||
*/
|
||||
protected $contents = [];
|
||||
|
||||
/**
|
||||
* @param int<1, max>|null $lineNumber
|
||||
*/
|
||||
public function __construct(?int $lineNumber = null)
|
||||
{
|
||||
$this->setPosition($lineNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws UnexpectedTokenException
|
||||
* @throws SourceException
|
||||
*
|
||||
* @internal since V8.8.0
|
||||
*/
|
||||
public static function parseList(ParserState $parserState, CSSList $list): void
|
||||
{
|
||||
$isRoot = $list instanceof Document;
|
||||
$usesLenientParsing = $parserState->getSettings()->usesLenientParsing();
|
||||
$comments = [];
|
||||
$parserState->consumeWhiteSpace($comments);
|
||||
while (!$parserState->isEnd()) {
|
||||
$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) {
|
||||
// ... the unexpected token needs to be skipped, otherwise there'll be an infinite loop.
|
||||
$parserState->consume(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$listItem = self::parseListItem($parserState, $list);
|
||||
}
|
||||
if ($listItem === null) {
|
||||
// List parsing finished
|
||||
return;
|
||||
}
|
||||
if ($listItem) {
|
||||
$listItem->addComments($comments);
|
||||
$list->append($listItem);
|
||||
}
|
||||
$comments = [];
|
||||
$parserState->consumeWhiteSpace($comments);
|
||||
}
|
||||
$list->addComments($comments);
|
||||
if (!$isRoot && !$usesLenientParsing) {
|
||||
throw new SourceException('Unexpected end of document', $parserState->currentLine());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CSSListItem|false|null
|
||||
* If `null` is returned, it means the end of the list has been reached.
|
||||
* If `false` is returned, it means an invalid item has been encountered,
|
||||
* but parsing of the next item should proceed.
|
||||
*
|
||||
* @throws SourceException
|
||||
* @throws UnexpectedEOFException
|
||||
* @throws UnexpectedTokenException
|
||||
*/
|
||||
private static function parseListItem(ParserState $parserState, CSSList $list)
|
||||
{
|
||||
$isRoot = $list instanceof Document;
|
||||
if ($parserState->comes('@')) {
|
||||
$atRule = self::parseAtRule($parserState);
|
||||
if ($atRule instanceof Charset) {
|
||||
if (!$isRoot) {
|
||||
throw new UnexpectedTokenException(
|
||||
'@charset may only occur in root document',
|
||||
'',
|
||||
'custom',
|
||||
$parserState->currentLine()
|
||||
);
|
||||
}
|
||||
if (\count($list->getContents()) > 0) {
|
||||
throw new UnexpectedTokenException(
|
||||
'@charset must be the first parseable token in a document',
|
||||
'',
|
||||
'custom',
|
||||
$parserState->currentLine()
|
||||
);
|
||||
}
|
||||
$parserState->setCharset($atRule->getCharset());
|
||||
}
|
||||
return $atRule;
|
||||
} elseif ($parserState->comes('}')) {
|
||||
if ($isRoot) {
|
||||
if ($parserState->getSettings()->usesLenientParsing()) {
|
||||
$parserState->consume(1);
|
||||
return self::parseListItem($parserState, $list);
|
||||
} else {
|
||||
throw new SourceException('Unopened {', $parserState->currentLine());
|
||||
}
|
||||
} else {
|
||||
// End of list
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return DeclarationBlock::parse($parserState, $list) ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SourceException
|
||||
* @throws UnexpectedTokenException
|
||||
* @throws UnexpectedEOFException
|
||||
*/
|
||||
private static function parseAtRule(ParserState $parserState): ?CSSListItem
|
||||
{
|
||||
$parserState->consume('@');
|
||||
$identifier = $parserState->parseIdentifier();
|
||||
$identifierLineNumber = $parserState->currentLine();
|
||||
$parserState->consumeWhiteSpace();
|
||||
if ($identifier === 'import') {
|
||||
$location = URL::parse($parserState);
|
||||
$parserState->consumeWhiteSpace();
|
||||
$mediaQuery = null;
|
||||
if (!$parserState->comes(';')) {
|
||||
$mediaQuery = \trim($parserState->consumeUntil([';', ParserState::EOF]));
|
||||
if ($mediaQuery === '') {
|
||||
$mediaQuery = null;
|
||||
}
|
||||
}
|
||||
$parserState->consumeUntil([';', ParserState::EOF], true, true);
|
||||
return new Import($location, $mediaQuery, $identifierLineNumber);
|
||||
} elseif ($identifier === 'charset') {
|
||||
$charsetString = CSSString::parse($parserState);
|
||||
$parserState->consumeWhiteSpace();
|
||||
$parserState->consumeUntil([';', ParserState::EOF], true, true);
|
||||
return new Charset($charsetString, $identifierLineNumber);
|
||||
} elseif (self::identifierIs($identifier, 'keyframes')) {
|
||||
$result = new KeyFrame($identifierLineNumber);
|
||||
$result->setVendorKeyFrame($identifier);
|
||||
$result->setAnimationName(\trim($parserState->consumeUntil('{', false, true)));
|
||||
CSSList::parseList($parserState, $result);
|
||||
if ($parserState->comes('}')) {
|
||||
$parserState->consume('}');
|
||||
}
|
||||
return $result;
|
||||
} elseif ($identifier === 'namespace') {
|
||||
$prefix = null;
|
||||
$url = Value::parsePrimitiveValue($parserState);
|
||||
if (!$parserState->comes(';')) {
|
||||
$prefix = $url;
|
||||
$url = Value::parsePrimitiveValue($parserState);
|
||||
}
|
||||
$parserState->consumeUntil([';', ParserState::EOF], true, true);
|
||||
if ($prefix !== null && !\is_string($prefix)) {
|
||||
throw new UnexpectedTokenException('Wrong namespace prefix', $prefix, 'custom', $identifierLineNumber);
|
||||
}
|
||||
if (!($url instanceof CSSString || $url instanceof URL)) {
|
||||
throw new UnexpectedTokenException(
|
||||
'Wrong namespace url of invalid type',
|
||||
$url,
|
||||
'custom',
|
||||
$identifierLineNumber
|
||||
);
|
||||
}
|
||||
return new CSSNamespace($url, $prefix, $identifierLineNumber);
|
||||
} else {
|
||||
// Unknown other at rule (font-face or such)
|
||||
$arguments = \trim($parserState->consumeUntil('{', false, true));
|
||||
if (\substr_count($arguments, '(') !== \substr_count($arguments, ')')) {
|
||||
if ($parserState->getSettings()->usesLenientParsing()) {
|
||||
return null;
|
||||
} else {
|
||||
throw new SourceException('Unmatched brace count in media query', $parserState->currentLine());
|
||||
}
|
||||
}
|
||||
$useRuleSet = true;
|
||||
foreach (AtRule::BLOCK_RULES as $blockRuleName) {
|
||||
if (self::identifierIs($identifier, $blockRuleName)) {
|
||||
$useRuleSet = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($useRuleSet) {
|
||||
$atRule = new AtRuleSet($identifier, $arguments, $identifierLineNumber);
|
||||
RuleSet::parseRuleSet($parserState, $atRule);
|
||||
} else {
|
||||
$atRule = new AtRuleBlockList($identifier, $arguments, $identifierLineNumber);
|
||||
CSSList::parseList($parserState, $atRule);
|
||||
if ($parserState->comes('}')) {
|
||||
$parserState->consume('}');
|
||||
}
|
||||
}
|
||||
return $atRule;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests an identifier for a given value. Since identifiers are all keywords, they can be vendor-prefixed.
|
||||
* We need to check for these versions too.
|
||||
*/
|
||||
private static function identifierIs(string $identifier, string $match): bool
|
||||
{
|
||||
if (\strcasecmp($identifier, $match) === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return preg_match("/^(-\\w+-)?$match$/i", $identifier) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends an item to the list of contents.
|
||||
*/
|
||||
public function prepend(CSSListItem $item): void
|
||||
{
|
||||
\array_unshift($this->contents, $item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends an item to the list of contents.
|
||||
*/
|
||||
public function append(CSSListItem $item): void
|
||||
{
|
||||
$this->contents[] = $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splices the list of contents.
|
||||
*
|
||||
* @param array<int, CSSListItem> $replacement
|
||||
*/
|
||||
public function splice(int $offset, ?int $length = null, ?array $replacement = null): void
|
||||
{
|
||||
\array_splice($this->contents, $offset, $length, $replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts an item in the CSS list before its sibling. If the desired sibling cannot be found,
|
||||
* the item is appended at the end.
|
||||
*/
|
||||
public function insertBefore(CSSListItem $item, CSSListItem $sibling): void
|
||||
{
|
||||
if (\in_array($sibling, $this->contents, true)) {
|
||||
$this->replace($sibling, [$item, $sibling]);
|
||||
} else {
|
||||
$this->append($item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item from the CSS list.
|
||||
*
|
||||
* @param CSSListItem $itemToRemove
|
||||
* May be a `RuleSet` (most likely a `DeclarationBlock`), an `Import`,
|
||||
* a `Charset` or another `CSSList` (most likely a `MediaQuery`)
|
||||
*
|
||||
* @return bool whether the item was removed
|
||||
*/
|
||||
public function remove(CSSListItem $itemToRemove): bool
|
||||
{
|
||||
$key = \array_search($itemToRemove, $this->contents, true);
|
||||
if ($key !== false) {
|
||||
unset($this->contents[$key]);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces an item from the CSS list.
|
||||
*
|
||||
* @param CSSListItem $oldItem
|
||||
* May be a `RuleSet` (most likely a `DeclarationBlock`), an `Import`, a `Charset`
|
||||
* or another `CSSList` (most likely a `MediaQuery`)
|
||||
* @param CSSListItem|array<CSSListItem> $newItem
|
||||
*/
|
||||
public function replace(CSSListItem $oldItem, $newItem): bool
|
||||
{
|
||||
$key = \array_search($oldItem, $this->contents, true);
|
||||
if ($key !== false) {
|
||||
if (\is_array($newItem)) {
|
||||
\array_splice($this->contents, $key, 1, $newItem);
|
||||
} else {
|
||||
\array_splice($this->contents, $key, 1, [$newItem]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, CSSListItem> $contents
|
||||
*/
|
||||
public function setContents(array $contents): void
|
||||
{
|
||||
$this->contents = [];
|
||||
foreach ($contents as $content) {
|
||||
$this->append($content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a declaration block from the CSS list if it matches all given selectors.
|
||||
*
|
||||
* @param DeclarationBlock|array<Selector>|string $selectors the selectors to match
|
||||
* @param bool $removeAll whether to stop at the first declaration block found or remove all blocks
|
||||
*/
|
||||
public function removeDeclarationBlockBySelector($selectors, bool $removeAll = false): void
|
||||
{
|
||||
if ($selectors instanceof DeclarationBlock) {
|
||||
$selectors = $selectors->getSelectors();
|
||||
}
|
||||
if (!\is_array($selectors)) {
|
||||
$selectors = \explode(',', $selectors);
|
||||
}
|
||||
foreach ($selectors as &$selector) {
|
||||
if (!($selector instanceof Selector)) {
|
||||
if (!Selector::isValid($selector)) {
|
||||
throw new UnexpectedTokenException(
|
||||
"Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.",
|
||||
$selector,
|
||||
'custom'
|
||||
);
|
||||
}
|
||||
$selector = new Selector($selector);
|
||||
}
|
||||
}
|
||||
foreach ($this->contents as $key => $item) {
|
||||
if (!($item instanceof DeclarationBlock)) {
|
||||
continue;
|
||||
}
|
||||
if (self::selectorsMatch($item->getSelectors(), $selectors)) {
|
||||
unset($this->contents[$key]);
|
||||
if (!$removeAll) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function renderListContents(OutputFormat $outputFormat): string
|
||||
{
|
||||
$result = '';
|
||||
$isFirst = true;
|
||||
$nextLevelFormat = $outputFormat;
|
||||
if (!$this->isRootList()) {
|
||||
$nextLevelFormat = $outputFormat->nextLevel();
|
||||
}
|
||||
$nextLevelFormatter = $nextLevelFormat->getFormatter();
|
||||
$formatter = $outputFormat->getFormatter();
|
||||
foreach ($this->contents as $listItem) {
|
||||
$renderedCss = $formatter->safely(static function () use ($nextLevelFormat, $listItem): string {
|
||||
return $listItem->render($nextLevelFormat);
|
||||
});
|
||||
if ($renderedCss === null) {
|
||||
continue;
|
||||
}
|
||||
if ($isFirst) {
|
||||
$isFirst = false;
|
||||
$result .= $nextLevelFormatter->spaceBeforeBlocks();
|
||||
} else {
|
||||
$result .= $nextLevelFormatter->spaceBetweenBlocks();
|
||||
}
|
||||
$result .= $renderedCss;
|
||||
}
|
||||
|
||||
if (!$isFirst) {
|
||||
// Had some output
|
||||
$result .= $formatter->spaceAfterBlocks();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the list can not be further outdented. Only important when rendering.
|
||||
*/
|
||||
abstract public function isRootList(): bool;
|
||||
|
||||
/**
|
||||
* Returns the stored items.
|
||||
*
|
||||
* @return array<int<0, max>, CSSListItem>
|
||||
*/
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
18
vendor/sabberworm/php-css-parser/src/CSSList/CSSListItem.php
vendored
Normal file
18
vendor/sabberworm/php-css-parser/src/CSSList/CSSListItem.php
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabberworm\CSS\CSSList;
|
||||
|
||||
use Sabberworm\CSS\Comment\Commentable;
|
||||
use Sabberworm\CSS\Renderable;
|
||||
|
||||
/**
|
||||
* Represents anything that can be in the `$contents` of a `CSSList`.
|
||||
*
|
||||
* The interface does not define any methods to implement.
|
||||
* It's purpose is to allow a single type to be specified for `CSSList::$contents` and manipulation methods thereof.
|
||||
* It extends `Commentable` and `Renderable` because all `CSSListItem`s are both.
|
||||
* This allows implementations to call methods from those interfaces without any additional type checks.
|
||||
*/
|
||||
interface CSSListItem extends Commentable, Renderable {}
|
||||
65
vendor/sabberworm/php-css-parser/src/CSSList/Document.php
vendored
Normal file
65
vendor/sabberworm/php-css-parser/src/CSSList/Document.php
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabberworm\CSS\CSSList;
|
||||
|
||||
use Sabberworm\CSS\OutputFormat;
|
||||
use Sabberworm\CSS\Parsing\ParserState;
|
||||
use Sabberworm\CSS\Parsing\SourceException;
|
||||
use Sabberworm\CSS\Property\Selector;
|
||||
|
||||
/**
|
||||
* This class represents the root of a parsed CSS file. It contains all top-level CSS contents: mostly declaration
|
||||
* blocks, but also any at-rules encountered (`Import` and `Charset`).
|
||||
*/
|
||||
class Document extends CSSBlockList
|
||||
{
|
||||
/**
|
||||
* @throws SourceException
|
||||
*
|
||||
* @internal since V8.8.0
|
||||
*/
|
||||
public static function parse(ParserState $parserState): Document
|
||||
{
|
||||
$document = new Document($parserState->currentLine());
|
||||
CSSList::parseList($parserState, $document);
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all `Selector` objects with the requested specificity found recursively in the tree.
|
||||
*
|
||||
* Note that this does not yield the full `DeclarationBlock` that the selector belongs to
|
||||
* (and, currently, there is no way to get to that).
|
||||
*
|
||||
* @param string|null $specificitySearch
|
||||
* An optional filter by specificity.
|
||||
* May contain a comparison operator and a number or just a number (defaults to "==").
|
||||
*
|
||||
* @return list<Selector>
|
||||
*
|
||||
* @example `getSelectorsBySpecificity('>= 100')`
|
||||
*/
|
||||
public function getSelectorsBySpecificity(?string $specificitySearch = null): array
|
||||
{
|
||||
return $this->getAllSelectors($specificitySearch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides `render()` to make format argument optional.
|
||||
*/
|
||||
public function render(?OutputFormat $outputFormat = null): string
|
||||
{
|
||||
if ($outputFormat === null) {
|
||||
$outputFormat = new OutputFormat();
|
||||
}
|
||||
return $outputFormat->getFormatter()->comments($this) . $this->renderListContents($outputFormat);
|
||||
}
|
||||
|
||||
public function isRootList(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
87
vendor/sabberworm/php-css-parser/src/CSSList/KeyFrame.php
vendored
Normal file
87
vendor/sabberworm/php-css-parser/src/CSSList/KeyFrame.php
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sabberworm\CSS\CSSList;
|
||||
|
||||
use Sabberworm\CSS\OutputFormat;
|
||||
use Sabberworm\CSS\Property\AtRule;
|
||||
|
||||
class KeyFrame extends CSSList implements AtRule
|
||||
{
|
||||
/**
|
||||
* @var non-empty-string
|
||||
*/
|
||||
private $vendorKeyFrame = 'keyframes';
|
||||
|
||||
/**
|
||||
* @var non-empty-string
|
||||
*/
|
||||
private $animationName = 'none';
|
||||
|
||||
/**
|
||||
* @param non-empty-string $vendorKeyFrame
|
||||
*/
|
||||
public function setVendorKeyFrame(string $vendorKeyFrame): void
|
||||
{
|
||||
$this->vendorKeyFrame = $vendorKeyFrame;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function getVendorKeyFrame(): string
|
||||
{
|
||||
return $this->vendorKeyFrame;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-string $animationName
|
||||
*/
|
||||
public function setAnimationName(string $animationName): void
|
||||
{
|
||||
$this->animationName = $animationName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function getAnimationName(): string
|
||||
{
|
||||
return $this->animationName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function render(OutputFormat $outputFormat): string
|
||||
{
|
||||
$formatter = $outputFormat->getFormatter();
|
||||
$result = $formatter->comments($this);
|
||||
$result .= "@{$this->vendorKeyFrame} {$this->animationName}{$formatter->spaceBeforeOpeningBrace()}{";
|
||||
$result .= $this->renderListContents($outputFormat);
|
||||
$result .= '}';
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function isRootList(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function atRuleName(): string
|
||||
{
|
||||
return $this->vendorKeyFrame;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*/
|
||||
public function atRuleArgs(): string
|
||||
{
|
||||
return $this->animationName;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user