<?php
>
namespace Sabberworm\CSS\Parsing;
use Sabberworm\CSS\Comment\Comment;
< use Sabberworm\CSS\Parsing\UnexpectedTokenException;
use Sabberworm\CSS\Settings;
< class ParserState {
> class ParserState
> {
> /**
> * @var null
> */
> const EOF = null;
>
> /**
> * @var Settings
> */
private $oParserSettings;
> /**
private $sText;
> * @var string
> */
private $aText;
> /**
private $iCurrentPosition;
> * @var array<int, string>
private $sCharset;
> */
private $iLength;
>
private $iLineNo;
> /**
> * @var int
public function __construct($sText, Settings $oParserSettings, $iLineNo = 1) {
> */
$this->oParserSettings = $oParserSettings;
>
$this->sText = $sText;
> /**
$this->iCurrentPosition = 0;
> * @var string
$this->iLineNo = $iLineNo;
> */
$this->setCharset($this->oParserSettings->sDefaultCharset);
>
}
> /**
> * @var int
public function setCharset($sCharset) {
> */
$this->sCharset = $sCharset;
>
$this->aText = $this->strsplit($this->sText);
> /**
$this->iLength = count($this->aText);
> * @var int
}
> */
< public function __construct($sText, Settings $oParserSettings, $iLineNo = 1) {
> /**
> * @param string $sText
> * @param int $iLineNo
> */
> public function __construct($sText, Settings $oParserSettings, $iLineNo = 1)
> {
< public function setCharset($sCharset) {
> /**
> * @param string $sCharset
> *
> * @return void
> */
> public function setCharset($sCharset)
> {
$this->oParserHelper->getCharset();
> if (is_array($this->aText)) {
return $this->sCharset;
> }
< public function getCharset() {
< $this->oParserHelper->getCharset();
> /**
> * @return string
> */
> public function getCharset()
> {
< public function currentLine() {
> /**
> * @return int
> */
> public function currentLine()
> {
return $this->iLineNo;
}
< public function getSettings() {
> /**
> * @return int
> */
> public function currentColumn()
> {
> return $this->iCurrentPosition;
> }
>
> /**
> * @return Settings
> */
> public function getSettings()
> {
return $this->oParserSettings;
}
< public function parseIdentifier($bIgnoreCase = true) {
> /**
> * @param bool $bIgnoreCase
> *
> * @return string
> *
> * @throws UnexpectedTokenException
> */
> public function parseIdentifier($bIgnoreCase = true)
> {
$sResult = $this->parseCharacter(true);
if ($sResult === null) {
throw new UnexpectedTokenException($sResult, $this->peek(5), 'identifier', $this->iLineNo);
}
$sCharacter = null;
while (($sCharacter = $this->parseCharacter(true)) !== null) {
> if (preg_match('/[a-zA-Z0-9\x{00A0}-\x{FFFF}_-]/Sux', $sCharacter)) {
$sResult .= $sCharacter;
> } else {
}
> $sResult .= '\\' . $sCharacter;
if ($bIgnoreCase) {
> }
$sResult = $this->strtolower($sResult);
}
return $sResult;
}
< public function parseCharacter($bIsForIdentifier) {
> /**
> * @param bool $bIsForIdentifier
> *
> * @return string|null
> *
> * @throws UnexpectedEOFException
> * @throws UnexpectedTokenException
> */
> public function parseCharacter($bIsForIdentifier)
> {
if ($this->peek() === '\\') {
< if ($bIsForIdentifier && $this->oParserSettings->bLenientParsing && ($this->comes('\0') || $this->comes('\9'))) {
> if (
> $bIsForIdentifier && $this->oParserSettings->bLenientParsing
> && ($this->comes('\0') || $this->comes('\9'))
> ) {
// Non-strings can contain \0 or \9 which is an IE hack supported in lenient parsing.
return null;
}
$this->consume('\\');
if ($this->comes('\n') || $this->comes('\r')) {
return '';
}
if (preg_match('/[0-9a-fA-F]/Su', $this->peek()) === 0) {
return $this->consume(1);
}
$sUnicode = $this->consumeExpression('/^[0-9a-fA-F]{1,6}/u', 6);
if ($this->strlen($sUnicode) < 6) {
//Consume whitespace after incomplete unicode escape
if (preg_match('/\\s/isSu', $this->peek())) {
if ($this->comes('\r\n')) {
$this->consume(2);
} else {
$this->consume(1);
}
}
}
$iUnicode = intval($sUnicode, 16);
$sUtf32 = "";
for ($i = 0; $i < 4; ++$i) {
$sUtf32 .= chr($iUnicode & 0xff);
$iUnicode = $iUnicode >> 8;
}
return iconv('utf-32le', $this->sCharset, $sUtf32);
}
if ($bIsForIdentifier) {
$peek = ord($this->peek());
// Ranges: a-z A-Z 0-9 - _
< if (($peek >= 97 && $peek <= 122) ||
< ($peek >= 65 && $peek <= 90) ||
< ($peek >= 48 && $peek <= 57) ||
< ($peek === 45) ||
< ($peek === 95) ||
< ($peek > 0xa1)) {
> if (
> ($peek >= 97 && $peek <= 122)
> || ($peek >= 65 && $peek <= 90)
> || ($peek >= 48 && $peek <= 57)
> || ($peek === 45)
> || ($peek === 95)
> || ($peek > 0xa1)
> ) {
return $this->consume(1);
}
} else {
return $this->consume(1);
}
return null;
}
< public function consumeWhiteSpace() {
< $comments = array();
> /**
> * @return array<int, Comment>|void
> *
> * @throws UnexpectedEOFException
> * @throws UnexpectedTokenException
> */
> public function consumeWhiteSpace()
> {
> $comments = [];
do {
while (preg_match('/\\s/isSu', $this->peek()) === 1) {
$this->consume(1);
}
if($this->oParserSettings->bLenientParsing) {
try {
$oComment = $this->consumeComment();
< } catch(UnexpectedTokenException $e) {
< // When we can’t find the end of a comment, we assume the document is finished.
> } catch (UnexpectedEOFException $e) {
$this->iCurrentPosition = $this->iLength;
return;
}
} else {
$oComment = $this->consumeComment();
}
if ($oComment !== false) {
$comments[] = $oComment;
}
} while($oComment !== false);
return $comments;
}
< public function comes($sString, $bCaseInsensitive = false) {
> /**
> * @param string $sString
> * @param bool $bCaseInsensitive
> *
> * @return bool
> */
> public function comes($sString, $bCaseInsensitive = false)
> {
$sPeek = $this->peek(strlen($sString));
return ($sPeek == '')
? false
: $this->streql($sPeek, $sString, $bCaseInsensitive);
}
< public function peek($iLength = 1, $iOffset = 0) {
> /**
> * @param int $iLength
> * @param int $iOffset
> *
> * @return string
> */
> public function peek($iLength = 1, $iOffset = 0)
> {
$iOffset += $this->iCurrentPosition;
if ($iOffset >= $this->iLength) {
return '';
}
return $this->substr($iOffset, $iLength);
}
< public function consume($mValue = 1) {
> /**
> * @param int $mValue
> *
> * @return string
> *
> * @throws UnexpectedEOFException
> * @throws UnexpectedTokenException
> */
> public function consume($mValue = 1)
> {
if (is_string($mValue)) {
$iLineCount = substr_count($mValue, "\n");
$iLength = $this->strlen($mValue);
if (!$this->streql($this->substr($this->iCurrentPosition, $iLength), $mValue)) {
throw new UnexpectedTokenException($mValue, $this->peek(max($iLength, 5)), $this->iLineNo);
}
$this->iLineNo += $iLineCount;
$this->iCurrentPosition += $this->strlen($mValue);
return $mValue;
} else {
if ($this->iCurrentPosition + $mValue > $this->iLength) {
< throw new UnexpectedTokenException($mValue, $this->peek(5), 'count', $this->iLineNo);
> throw new UnexpectedEOFException($mValue, $this->peek(5), 'count', $this->iLineNo);
}
$sResult = $this->substr($this->iCurrentPosition, $mValue);
$iLineCount = substr_count($sResult, "\n");
$this->iLineNo += $iLineCount;
$this->iCurrentPosition += $mValue;
return $sResult;
}
}
< public function consumeExpression($mExpression, $iMaxLength = null) {
> /**
> * @param string $mExpression
> * @param int|null $iMaxLength
> *
> * @return string
> *
> * @throws UnexpectedEOFException
> * @throws UnexpectedTokenException
> */
> public function consumeExpression($mExpression, $iMaxLength = null)
> {
$aMatches = null;
$sInput = $iMaxLength !== null ? $this->peek($iMaxLength) : $this->inputLeft();
if (preg_match($mExpression, $sInput, $aMatches, PREG_OFFSET_CAPTURE) === 1) {
return $this->consume($aMatches[0][0]);
}
throw new UnexpectedTokenException($mExpression, $this->peek(5), 'expression', $this->iLineNo);
}
/**
< * @return false|Comment
> * @return Comment|false
*/
< public function consumeComment() {
> public function consumeComment()
> {
$mComment = false;
if ($this->comes('/*')) {
$iLineNo = $this->iLineNo;
$this->consume(1);
$mComment = '';
while (($char = $this->consume(1)) !== '') {
$mComment .= $char;
if ($this->comes('*/')) {
$this->consume(2);
break;
}
}
}
if ($mComment !== false) {
// We skip the * which was included in the comment.
return new Comment(substr($mComment, 1), $iLineNo);
}
return $mComment;
}
< public function isEnd() {
> /**
> * @return bool
> */
> public function isEnd()
> {
return $this->iCurrentPosition >= $this->iLength;
}
< public function consumeUntil($aEnd, $bIncludeEnd = false, $consumeEnd = false, array &$comments = array()) {
< $aEnd = is_array($aEnd) ? $aEnd : array($aEnd);
> /**
> * @param array<array-key, string>|string $aEnd
> * @param string $bIncludeEnd
> * @param string $consumeEnd
> * @param array<int, Comment> $comments
> *
> * @return string
> *
> * @throws UnexpectedEOFException
> * @throws UnexpectedTokenException
> */
> public function consumeUntil($aEnd, $bIncludeEnd = false, $consumeEnd = false, array &$comments = [])
> {
> $aEnd = is_array($aEnd) ? $aEnd : [$aEnd];
$out = '';
$start = $this->iCurrentPosition;
< while (($char = $this->consume(1)) !== '') {
> while (!$this->isEnd()) {
> $char = $this->consume(1);
if (in_array($char, $aEnd)) {
if ($bIncludeEnd) {
$out .= $char;
} elseif (!$consumeEnd) {
$this->iCurrentPosition -= $this->strlen($char);
}
return $out;
}
$out .= $char;
if ($comment = $this->consumeComment()) {
$comments[] = $comment;
}
}
> if (in_array(self::EOF, $aEnd)) {
$this->iCurrentPosition = $start;
> return $out;
throw new UnexpectedTokenException('One of ("'.implode('","', $aEnd).'")', $this->peek(5), 'search', $this->iLineNo);
> }
}
>
< throw new UnexpectedTokenException('One of ("'.implode('","', $aEnd).'")', $this->peek(5), 'search', $this->iLineNo);
> throw new UnexpectedEOFException(
> 'One of ("' . implode('","', $aEnd) . '")',
> $this->peek(5),
> 'search',
> $this->iLineNo
> );
< private function inputLeft() {
> /**
> * @return string
> */
> private function inputLeft()
> {
return $this->substr($this->iCurrentPosition, -1);
}
< public function streql($sString1, $sString2, $bCaseInsensitive = true) {
> /**
> * @param string $sString1
> * @param string $sString2
> * @param bool $bCaseInsensitive
> *
> * @return bool
> */
> public function streql($sString1, $sString2, $bCaseInsensitive = true)
> {
if($bCaseInsensitive) {
return $this->strtolower($sString1) === $this->strtolower($sString2);
} else {
return $sString1 === $sString2;
}
}
< public function backtrack($iAmount) {
> /**
> * @param int $iAmount
> *
> * @return void
> */
> public function backtrack($iAmount)
> {
$this->iCurrentPosition -= $iAmount;
}
< public function strlen($sString) {
> /**
> * @param string $sString
> *
> * @return int
> */
> public function strlen($sString)
> {
if ($this->oParserSettings->bMultibyteSupport) {
return mb_strlen($sString, $this->sCharset);
} else {
return strlen($sString);
}
}
< private function substr($iStart, $iLength) {
> /**
> * @param int $iStart
> * @param int $iLength
> *
> * @return string
> */
> private function substr($iStart, $iLength)
> {
if ($iLength < 0) {
$iLength = $this->iLength - $iStart + $iLength;
}
if ($iStart + $iLength > $this->iLength) {
$iLength = $this->iLength - $iStart;
}
$sResult = '';
while ($iLength > 0) {
$sResult .= $this->aText[$iStart];
$iStart++;
$iLength--;
}
return $sResult;
}
< private function strtolower($sString) {
> /**
> * @param string $sString
> *
> * @return string
> */
> private function strtolower($sString)
> {
if ($this->oParserSettings->bMultibyteSupport) {
return mb_strtolower($sString, $this->sCharset);
} else {
return strtolower($sString);
}
}
< private function strsplit($sString) {
> /**
> * @param string $sString
> *
> * @return array<int, string>
> */
> private function strsplit($sString)
> {
if ($this->oParserSettings->bMultibyteSupport) {
if ($this->streql($this->sCharset, 'utf-8')) {
< return preg_split('//u', $sString, null, PREG_SPLIT_NO_EMPTY);
> return preg_split('//u', $sString, -1, PREG_SPLIT_NO_EMPTY);
} else {
$iLength = mb_strlen($sString, $this->sCharset);
< $aResult = array();
> $aResult = [];
for ($i = 0; $i < $iLength; ++$i) {
$aResult[] = mb_substr($sString, $i, 1, $this->sCharset);
}
return $aResult;
}
} else {
if($sString === '') {
< return array();
> return [];
} else {
return str_split($sString);
}
}
}
< private function strpos($sString, $sNeedle, $iOffset) {
> /**
> * @param string $sString
> * @param string $sNeedle
> * @param int $iOffset
> *
> * @return int|false
> */
> private function strpos($sString, $sNeedle, $iOffset)
> {
if ($this->oParserSettings->bMultibyteSupport) {
return mb_strpos($sString, $sNeedle, $iOffset, $this->sCharset);
} else {
return strpos($sString, $sNeedle, $iOffset);
}
}
}