Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
<?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; /**
< * A CSSList is the most generic container available. Its contents include RuleSet as well as other CSSList objects. < * Also, it may contain Import and Charset objects stemming from @-rules.
> * A `CSSList` is the most generic container available. Its contents include `RuleSet` as well as other `CSSList` > * objects. > * > * Also, it may contain `Import` and `Charset` objects stemming from at-rules. > */ > abstract class CSSList implements Renderable, Commentable > { > /** > * @var array<array-key, Comment>
*/
< abstract class CSSList implements Renderable, Commentable { <
protected $aComments;
> protected $aContents; > /** protected $iLineNo; > * @var array<int, RuleSet|CSSList|Import|Charset> > */
public function __construct($iLineNo = 0) {
> $this->aComments = array(); > /** $this->aContents = array(); > * @var int $this->iLineNo = $iLineNo; > */
< public function __construct($iLineNo = 0) { < $this->aComments = array(); < $this->aContents = array();
> /** > * @param int $iLineNo > */ > public function __construct($iLineNo = 0) > { > $this->aComments = []; > $this->aContents = [];
< public static function parseList(ParserState $oParserState, CSSList $oList) {
> /** > * @return void > * > * @throws UnexpectedTokenException > * @throws SourceException > */ > public static function parseList(ParserState $oParserState, CSSList $oList) > {
if(is_string($oParserState)) {
< $oParserState = new ParserState($oParserState);
> $oParserState = new ParserState($oParserState, Settings::create());
} $bLenientParsing = $oParserState->getSettings()->bLenientParsing; while(!$oParserState->isEnd()) { $comments = $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->setComments($comments); $oList->append($oListItem); } } if(!$bIsRoot && !$bLenientParsing) { throw new SourceException("Unexpected end of document", $oParserState->currentLine()); } }
< private static function parseListItem(ParserState $oParserState, CSSList $oList) {
> /** > * @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());
> 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());
> throw new UnexpectedTokenException( > '@charset must be the first parseable token in a document', > '', > 'custom', > $oParserState->currentLine() > );
} $oParserState->setCharset($oAtRule->getCharset()->getString()); } return $oAtRule; } else if ($oParserState->comes('}')) {
< $oParserState->consume('}');
> if (!$oParserState->getSettings()->bLenientParsing) { > throw new UnexpectedTokenException('CSS selector', '}', 'identifier', $oParserState->currentLine()); > } else {
if ($bIsRoot) { if ($oParserState->getSettings()->bLenientParsing) {
< while ($oParserState->comes('}')) $oParserState->consume('}');
return DeclarationBlock::parse($oParserState); } else { throw new SourceException("Unopened {", $oParserState->currentLine()); } } else { return null; }
> }
} else {
< return DeclarationBlock::parse($oParserState);
> return DeclarationBlock::parse($oParserState, $oList);
} }
< private static function parseAtRule(ParserState $oParserState) {
> /** > * @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 = $oParserState->consumeUntil(';');
> $sMediaQuery = trim($oParserState->consumeUntil([';', ParserState::EOF]));
}
< $oParserState->consume(';'); < return new Import($oLocation, $sMediaQuery, $iIdentifierLineNum);
> $oParserState->consumeUntil([';', ParserState::EOF], true, true); > return new Import($oLocation, $sMediaQuery ?: null, $iIdentifierLineNum);
} else if ($sIdentifier === 'charset') { $sCharset = CSSString::parse($oParserState); $oParserState->consumeWhiteSpace();
< $oParserState->consume(';');
> $oParserState->consumeUntil([';', ParserState::EOF], true, true);
return new Charset($sCharset, $iIdentifierLineNum); } else if (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('}')) { return $oResult; > $oParserState->consume('}'); } else if ($sIdentifier === 'namespace') { > }
$sPrefix = null; $mUrl = Value::parsePrimitiveValue($oParserState); if (!$oParserState->comes(';')) { $sPrefix = $mUrl; $mUrl = Value::parsePrimitiveValue($oParserState); }
< $oParserState->consume(';');
> $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);
> 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;
> 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.
> * 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) {
> private static function identifierIs($sIdentifier, $sMatch) > {
return (strcasecmp($sIdentifier, $sMatch) === 0) ?: preg_match("/^(-\\w+-)?$sMatch$/i", $sIdentifier) === 1; }
<
/** * @return int */
< public function getLineNo() {
> public function getLineNo() > {
return $this->iLineNo; } /**
< * Prepend item to list of contents.
> * Prepends an item to the list of contents.
*
< * @param object $oItem Item.
> * @param RuleSet|CSSList|Import|Charset $oItem > * > * @return void
*/
< public function prepend($oItem) {
> public function prepend($oItem) > {
array_unshift($this->aContents, $oItem); } /**
< * Append item to list of contents.
> * Appends an item to tje list of contents.
*
< * @param object $oItem Item. < */ < public function append($oItem) { < $this->aContents[] = $oItem; < } < < /** < * Splice the list of contents.
> * @param RuleSet|CSSList|Import|Charset $oItem
*
< * @param int $iOffset Offset. < * @param int $iLength Length. Optional. < * @param RuleSet[] $mReplacement Replacement. Optional.
> * @return void
*/
< public function splice($iOffset, $iLength = null, $mReplacement = null) { < array_splice($this->aContents, $iOffset, $iLength, $mReplacement);
> public function append($oItem) > { > $this->aContents[] = $oItem;
} /** * Insert an item before its sibling. * * @param mixed $oItem The item. * @param mixed $oSibling The sibling. */ public function insert($oItem, $oSibling) { $iIndex = array_search($oSibling, $this->aContents); if ($iIndex === false) { return $this->append($oItem); } array_splice($this->aContents, $iIndex, 0, array($oItem)); } /**
> * Splices the list of contents. * 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) > * @param int $iOffset * @return bool Whether the item was removed. > * @param int $iLength */ > * @param array<int, RuleSet|CSSList|Import|Charset> $mReplacement public function remove($oItemToRemove) { > * $iKey = array_search($oItemToRemove, $this->aContents, true); > * @return void if ($iKey !== false) { > */ unset($this->aContents[$iKey]); > public function splice($iOffset, $iLength = null, $mReplacement = null) return true; > { } > array_splice($this->aContents, $iOffset, $iLength, $mReplacement); return false; > } } > > /**
< * @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.
> * > * @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) {
> public function remove($oItemToRemove) > {
< * @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)
> * > * @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, $oNewItem) {
> public function replace($oOldItem, $mNewItem) > {
$iKey = array_search($oOldItem, $this->aContents, true); if ($iKey !== false) {
< array_splice($this->aContents, $iKey, 1, $oNewItem);
> if (is_array($mNewItem)) { > array_splice($this->aContents, $iKey, 1, $mNewItem); > } else { > array_splice($this->aContents, $iKey, 1, [$mNewItem]); > }
return true; } return false; } /**
< * Set the contents. < * @param array $aContents Objects to set as content.
> * @param array<int, RuleSet|Import|Charset|CSSList> $aContents
*/
< public function setContents(array $aContents) { < $this->aContents = array();
> 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 array|string $mSelector The selectors to match. < * @param boolean $bRemoveAll Whether to stop at the first declaration block found or remove all blocks
> * > * @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) {
> 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)) { $mSel = new Selector($mSel); > throw new UnexpectedTokenException( } > "Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.", } > $mSel, foreach ($this->aContents as $iKey => $mItem) { > "custom" if (!($mItem instanceof DeclarationBlock)) { > ); continue; > }
} if ($mItem->getSelectors() == $mSelector) { unset($this->aContents[$iKey]); if (!$bRemoveAll) { return; } } } }
< public function __toString() { < return $this->render(new \Sabberworm\CSS\OutputFormat());
> /** > * @return string > */ > public function __toString() > { > return $this->render(new OutputFormat());
}
< public function render(\Sabberworm\CSS\OutputFormat $oOutputFormat) {
> /** > * @return string > */ > public function render(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
< public abstract function isRootList();
> abstract public function isRootList();
< public function getContents() {
> /** > * @return array<int, RuleSet|Import|Charset|CSSList> > */ > public function getContents() > {
return $this->aContents; } /**
< * @param array $aComments Array of comments.
> * @param array<array-key, Comment> $aComments > * > * @return void
*/
< public function addComments(array $aComments) {
> public function addComments(array $aComments) > {
$this->aComments = array_merge($this->aComments, $aComments); } /**
< * @return array
> * @return array<array-key, Comment>
*/
< public function getComments() {
> public function getComments() > {
return $this->aComments; } /**
< * @param array $aComments Array containing Comment objects.
> * @param array<array-key, Comment> $aComments > * > * @return void
*/
< public function setComments(array $aComments) {
> public function setComments(array $aComments) > {
$this->aComments = $aComments; }
<
}