File "CSSList.php"
Full Path: /home/siazco/grocery.siazco.se/wp-content/plugins/woocommerce/vendor/sabberworm/php-css-parser/src/CSSList/CSSList.php
File size: 15.73 KB
MIME-type: text/x-php
Charset: utf-8
<?php
namespace Sabberworm\CSS\CSSList;
use Sabberworm\CSS\Comment\Comment;
use Sabberworm\CSS\Comment\Commentable;
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\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\Renderable;
use Sabberworm\CSS\RuleSet\AtRuleSet;
use Sabberworm\CSS\RuleSet\DeclarationBlock;
use Sabberworm\CSS\RuleSet\RuleSet;
use Sabberworm\CSS\Settings;
use Sabberworm\CSS\Value\CSSString;
use Sabberworm\CSS\Value\URL;
use Sabberworm\CSS\Value\Value;
/**
* 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.
*/
abstract class CSSList implements Renderable, Commentable
{
/**
* @var array<array-key, Comment>
*/
protected $aComments;
/**
* @var array<int, RuleSet|CSSList|Import|Charset>
*/
protected $aContents;
/**
* @var int
*/
protected $iLineNo;
/**
* @param int $iLineNo
*/
public function __construct($iLineNo = 0)
{
$this->aComments = [];
$this->aContents = [];
$this->iLineNo = $iLineNo;
}
/**
* @return void
*
* @throws UnexpectedTokenException
* @throws SourceException
*/
public static function parseList(ParserState $oParserState, CSSList $oList)
{
$bIsRoot = $oList instanceof Document;
if (is_string($oParserState)) {
$oParserState = new ParserState($oParserState, Settings::create());
}
$bLenientParsing = $oParserState->getSettings()->bLenientParsing;
$aComments = [];
while (!$oParserState->isEnd()) {
$aComments = array_merge($aComments, $oParserState->consumeWhiteSpace());
$oListItem = null;
if ($bLenientParsing) {
try {
$oListItem = self::parseListItem($oParserState, $oList);
} catch (UnexpectedTokenException $e) {
$oListItem = false;
}
} else {
$oListItem = self::parseListItem($oParserState, $oList);
}
if ($oListItem === null) {
// List parsing finished
return;
}
if ($oListItem) {
$oListItem->addComments($aComments);
$oList->append($oListItem);
}
$aComments = $oParserState->consumeWhiteSpace();
}
$oList->addComments($aComments);
if (!$bIsRoot && !$bLenientParsing) {
throw new SourceException("Unexpected end of document", $oParserState->currentLine());
}
}
/**
* @return AtRuleBlockList|KeyFrame|Charset|CSSNamespace|Import|AtRuleSet|DeclarationBlock|null|false
*
* @throws SourceException
* @throws UnexpectedEOFException
* @throws UnexpectedTokenException
*/
private static function parseListItem(ParserState $oParserState, CSSList $oList)
{
$bIsRoot = $oList instanceof Document;
if ($oParserState->comes('@')) {
$oAtRule = self::parseAtRule($oParserState);
if ($oAtRule instanceof Charset) {
if (!$bIsRoot) {
throw new UnexpectedTokenException(
'@charset may only occur in root document',
'',
'custom',
$oParserState->currentLine()
);
}
if (count($oList->getContents()) > 0) {
throw new UnexpectedTokenException(
'@charset must be the first parseable token in a document',
'',
'custom',
$oParserState->currentLine()
);
}
$oParserState->setCharset($oAtRule->getCharset());
}
return $oAtRule;
} elseif ($oParserState->comes('}')) {
if ($bIsRoot) {
if ($oParserState->getSettings()->bLenientParsing) {
return DeclarationBlock::parse($oParserState);
} else {
throw new SourceException("Unopened {", $oParserState->currentLine());
}
} else {
// End of list
return null;
}
} else {
return DeclarationBlock::parse($oParserState, $oList);
}
}
/**
* @param ParserState $oParserState
*
* @return AtRuleBlockList|KeyFrame|Charset|CSSNamespace|Import|AtRuleSet|null
*
* @throws SourceException
* @throws UnexpectedTokenException
* @throws UnexpectedEOFException
*/
private static function parseAtRule(ParserState $oParserState)
{
$oParserState->consume('@');
$sIdentifier = $oParserState->parseIdentifier();
$iIdentifierLineNum = $oParserState->currentLine();
$oParserState->consumeWhiteSpace();
if ($sIdentifier === 'import') {
$oLocation = URL::parse($oParserState);
$oParserState->consumeWhiteSpace();
$sMediaQuery = null;
if (!$oParserState->comes(';')) {
$sMediaQuery = trim($oParserState->consumeUntil([';', ParserState::EOF]));
}
$oParserState->consumeUntil([';', ParserState::EOF], true, true);
return new Import($oLocation, $sMediaQuery ?: null, $iIdentifierLineNum);
} elseif ($sIdentifier === 'charset') {
$oCharsetString = CSSString::parse($oParserState);
$oParserState->consumeWhiteSpace();
$oParserState->consumeUntil([';', ParserState::EOF], true, true);
return new Charset($oCharsetString, $iIdentifierLineNum);
} elseif (self::identifierIs($sIdentifier, 'keyframes')) {
$oResult = new KeyFrame($iIdentifierLineNum);
$oResult->setVendorKeyFrame($sIdentifier);
$oResult->setAnimationName(trim($oParserState->consumeUntil('{', false, true)));
CSSList::parseList($oParserState, $oResult);
if ($oParserState->comes('}')) {
$oParserState->consume('}');
}
return $oResult;
} elseif ($sIdentifier === 'namespace') {
$sPrefix = null;
$mUrl = Value::parsePrimitiveValue($oParserState);
if (!$oParserState->comes(';')) {
$sPrefix = $mUrl;
$mUrl = Value::parsePrimitiveValue($oParserState);
}
$oParserState->consumeUntil([';', ParserState::EOF], true, true);
if ($sPrefix !== null && !is_string($sPrefix)) {
throw new UnexpectedTokenException('Wrong namespace prefix', $sPrefix, 'custom', $iIdentifierLineNum);
}
if (!($mUrl instanceof CSSString || $mUrl instanceof URL)) {
throw new UnexpectedTokenException(
'Wrong namespace url of invalid type',
$mUrl,
'custom',
$iIdentifierLineNum
);
}
return new CSSNamespace($mUrl, $sPrefix, $iIdentifierLineNum);
} else {
// Unknown other at rule (font-face or such)
$sArgs = trim($oParserState->consumeUntil('{', false, true));
if (substr_count($sArgs, "(") != substr_count($sArgs, ")")) {
if ($oParserState->getSettings()->bLenientParsing) {
return null;
} else {
throw new SourceException("Unmatched brace count in media query", $oParserState->currentLine());
}
}
$bUseRuleSet = true;
foreach (explode('/', AtRule::BLOCK_RULES) as $sBlockRuleName) {
if (self::identifierIs($sIdentifier, $sBlockRuleName)) {
$bUseRuleSet = false;
break;
}
}
if ($bUseRuleSet) {
$oAtRule = new AtRuleSet($sIdentifier, $sArgs, $iIdentifierLineNum);
RuleSet::parseRuleSet($oParserState, $oAtRule);
} else {
$oAtRule = new AtRuleBlockList($sIdentifier, $sArgs, $iIdentifierLineNum);
CSSList::parseList($oParserState, $oAtRule);
if ($oParserState->comes('}')) {
$oParserState->consume('}');
}
}
return $oAtRule;
}
}
/**
* 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.
*
* @param string $sIdentifier
* @param string $sMatch
*
* @return bool
*/
private static function identifierIs($sIdentifier, $sMatch)
{
return (strcasecmp($sIdentifier, $sMatch) === 0)
?: preg_match("/^(-\\w+-)?$sMatch$/i", $sIdentifier) === 1;
}
/**
* @return int
*/
public function getLineNo()
{
return $this->iLineNo;
}
/**
* Prepends an item to the list of contents.
*
* @param RuleSet|CSSList|Import|Charset $oItem
*
* @return void
*/
public function prepend($oItem)
{
array_unshift($this->aContents, $oItem);
}
/**
* Appends an item to the list of contents.
*
* @param RuleSet|CSSList|Import|Charset $oItem
*
* @return void
*/
public function append($oItem)
{
$this->aContents[] = $oItem;
}
/**
* Splices the list of contents.
*
* @param int $iOffset
* @param int $iLength
* @param array<int, RuleSet|CSSList|Import|Charset> $mReplacement
*
* @return void
*/
public function splice($iOffset, $iLength = null, $mReplacement = null)
{
array_splice($this->aContents, $iOffset, $iLength, $mReplacement);
}
/**
* Inserts an item in the CSS list before its sibling. If the desired sibling cannot be found,
* the item is appended at the end.
*
* @param RuleSet|CSSList|Import|Charset $item
* @param RuleSet|CSSList|Import|Charset $sibling
*/
public function insertBefore($item, $sibling)
{
if (in_array($sibling, $this->aContents, true)) {
$this->replace($sibling, [$item, $sibling]);
} else {
$this->append($item);
}
}
/**
* Removes an item from the CSS list.
*
* @param RuleSet|Import|Charset|CSSList $oItemToRemove
* May be a RuleSet (most likely a DeclarationBlock), a Import,
* a Charset or another CSSList (most likely a MediaQuery)
*
* @return bool whether the item was removed
*/
public function remove($oItemToRemove)
{
$iKey = array_search($oItemToRemove, $this->aContents, true);
if ($iKey !== false) {
unset($this->aContents[$iKey]);
return true;
}
return false;
}
/**
* Replaces an item from the CSS list.
*
* @param RuleSet|Import|Charset|CSSList $oOldItem
* May be a `RuleSet` (most likely a `DeclarationBlock`), an `Import`, a `Charset`
* or another `CSSList` (most likely a `MediaQuery`)
*
* @return bool
*/
public function replace($oOldItem, $mNewItem)
{
$iKey = array_search($oOldItem, $this->aContents, true);
if ($iKey !== false) {
if (is_array($mNewItem)) {
array_splice($this->aContents, $iKey, 1, $mNewItem);
} else {
array_splice($this->aContents, $iKey, 1, [$mNewItem]);
}
return true;
}
return false;
}
/**
* @param array<int, RuleSet|Import|Charset|CSSList> $aContents
*/
public function setContents(array $aContents)
{
$this->aContents = [];
foreach ($aContents as $content) {
$this->append($content);
}
}
/**
* Removes a declaration block from the CSS list if it matches all given selectors.
*
* @param DeclarationBlock|array<array-key, Selector>|string $mSelector the selectors to match
* @param bool $bRemoveAll whether to stop at the first declaration block found or remove all blocks
*
* @return void
*/
public function removeDeclarationBlockBySelector($mSelector, $bRemoveAll = false)
{
if ($mSelector instanceof DeclarationBlock) {
$mSelector = $mSelector->getSelectors();
}
if (!is_array($mSelector)) {
$mSelector = explode(',', $mSelector);
}
foreach ($mSelector as $iKey => &$mSel) {
if (!($mSel instanceof Selector)) {
if (!Selector::isValid($mSel)) {
throw new UnexpectedTokenException(
"Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.",
$mSel,
"custom"
);
}
$mSel = new Selector($mSel);
}
}
foreach ($this->aContents as $iKey => $mItem) {
if (!($mItem instanceof DeclarationBlock)) {
continue;
}
if ($mItem->getSelectors() == $mSelector) {
unset($this->aContents[$iKey]);
if (!$bRemoveAll) {
return;
}
}
}
}
/**
* @return string
*/
public function __toString()
{
return $this->render(new OutputFormat());
}
/**
* @return string
*/
protected function renderListContents(OutputFormat $oOutputFormat)
{
$sResult = '';
$bIsFirst = true;
$oNextLevel = $oOutputFormat;
if (!$this->isRootList()) {
$oNextLevel = $oOutputFormat->nextLevel();
}
foreach ($this->aContents as $oContent) {
$sRendered = $oOutputFormat->safely(function () use ($oNextLevel, $oContent) {
return $oContent->render($oNextLevel);
});
if ($sRendered === null) {
continue;
}
if ($bIsFirst) {
$bIsFirst = false;
$sResult .= $oNextLevel->spaceBeforeBlocks();
} else {
$sResult .= $oNextLevel->spaceBetweenBlocks();
}
$sResult .= $sRendered;
}
if (!$bIsFirst) {
// Had some output
$sResult .= $oOutputFormat->spaceAfterBlocks();
}
return $sResult;
}
/**
* Return true if the list can not be further outdented. Only important when rendering.
*
* @return bool
*/
abstract public function isRootList();
/**
* Returns the stored items.
*
* @return array<int, RuleSet|Import|Charset|CSSList>
*/
public function getContents()
{
return $this->aContents;
}
/**
* @param array<array-key, Comment> $aComments
*
* @return void
*/
public function addComments(array $aComments)
{
$this->aComments = array_merge($this->aComments, $aComments);
}
/**
* @return array<array-key, Comment>
*/
public function getComments()
{
return $this->aComments;
}
/**
* @param array<array-key, Comment> $aComments
*
* @return void
*/
public function setComments(array $aComments)
{
$this->aComments = $aComments;
}
}