From 8a7d32afd88dd29cd0645808455d7f8d7c3776e1 Mon Sep 17 00:00:00 2001 From: Andreas Sandberg Date: Fri, 29 Jun 2012 13:06:38 -0700 Subject: [PATCH 1/3] Adding font name retrieval capabilities --- lib/CSSList.php | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/lib/CSSList.php b/lib/CSSList.php index ac07caee..91e148f8 100644 --- a/lib/CSSList.php +++ b/lib/CSSList.php @@ -106,6 +106,48 @@ protected function allValues($oElement, &$aResult, $sSearchString = null, $bSear } } + /** + * For all font-face declarations, return an array with the font name as the key + * and the src as the value. + * + * Note: Added specificially for Metrodigi use case. + * + * @return array + */ + public function retrieveFontNameToSrcMapFromFontFamilies() + { + $embededFonts = array(); + foreach($this->getAllRuleSets() as $oRuleSet) + { + if(($oRuleSet instanceof CSSAtRule) && strtolower($oRuleSet->getType()) == 'font-face') + { + $fontFamily = "";$fontSrc = ""; + $fam = $oRuleSet->getRules('font-family'); + $src = $oRuleSet->getRules('src'); + if(isset($fam['font-family'])) + { + $fontFamily = (string) $fam['font-family']->getValue(); + } + if(isset($src['src'])) + { + $fontSrc = (string) $src['src']->getValue(); + //parse out the url + $urlMatches = array(); + preg_match_all('/url\([\'"]{1}(.*?)[\'"]{1}\)/', $fontSrc, $urlMatches,PREG_SET_ORDER); + if(isset($urlMatches[0]) && isset($urlMatches[0][1])) + { + $fontSrc = $urlMatches[0][1]; + } + } + if(!empty($fontFamily)) + { + $embededFonts[$fontFamily] = $fontSrc; + } + } + } + return $embededFonts; + } + /** * Retrieve a selector by id or name. * From 9aa6e039ce34ae6515b6b479e5153fd474b32a37 Mon Sep 17 00:00:00 2001 From: Andreas Sandberg Date: Thu, 4 Oct 2012 11:55:28 -0700 Subject: [PATCH 2/3] Add ability to retrieve single list of class selectors --- lib/CSSList.php | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/lib/CSSList.php b/lib/CSSList.php index 91e148f8..f08000fa 100644 --- a/lib/CSSList.php +++ b/lib/CSSList.php @@ -206,7 +206,38 @@ public function getAllDeclarationBlocks() { public function getAllSelectors() { return $this->getAllDeclarationBlocks(); } - + + /** + * Return a list of single class selectors + * + * @return array + */ + public function getSingleClassSelectorList() { + $results = array(); + $blocks = $this->getAllDeclarationBlocks(); + foreach($blocks as $block) + { + $selectors = $block->getSelectors(); + + foreach($selectors as $sel) + { + $selctorParts = explode(" ", $sel); + if(count($selctorParts) > 1) + { + continue; + } + + //Only accept class selectors + if(preg_match('/^\./', $sel)) + { + $results[] = (string)$sel; + } + } + } + return $results; + } + + /** * Returns all CSSRuleSet objects found recursively in the tree. */ From 705b34afd3ba19f2b261a6cf38623cc6acd7badf Mon Sep 17 00:00:00 2001 From: Andreas Sandberg Date: Fri, 5 Oct 2012 16:33:50 -0700 Subject: [PATCH 3/3] Revert "Merge branch 'master' of https://github.com/sabberworm/PHP-CSS-Parser" This reverts commit b0d875a1a68bb78928b6ad5d7152d837255ac9b1, reversing changes made to 9aa6e039ce34ae6515b6b479e5153fd474b32a37. --- .../CSS/Parser.php => CSSParser.php | 353 +++++----- README.md | 461 ++++++------ composer.json | 17 - lib/CSSProperties.php | 124 ++++ lib/CSSRule.php | 135 ++++ lib/CSSRuleSet.php | 657 ++++++++++++++++++ lib/CSSValue.php | 110 +++ lib/CSSValueList.php | 92 +++ lib/Sabberworm/CSS/CSSList/CSSList.php | 135 ---- lib/Sabberworm/CSS/CSSList/Document.php | 87 --- lib/Sabberworm/CSS/CSSList/MediaQuery.php | 32 - lib/Sabberworm/CSS/Property/Charset.php | 32 - lib/Sabberworm/CSS/Property/Import.php | 30 - lib/Sabberworm/CSS/Property/Selector.php | 75 -- lib/Sabberworm/CSS/Rule/Rule.php | 142 ---- lib/Sabberworm/CSS/RuleSet/AtRule.php | 28 - .../CSS/RuleSet/DeclarationBlock.php | 585 ---------------- lib/Sabberworm/CSS/RuleSet/RuleSet.php | 74 -- lib/Sabberworm/CSS/Value/CSSFunction.php | 31 - lib/Sabberworm/CSS/Value/Color.php | 24 - lib/Sabberworm/CSS/Value/PrimitiveValue.php | 7 - lib/Sabberworm/CSS/Value/RuleValueList.php | 11 - lib/Sabberworm/CSS/Value/Size.php | 63 -- lib/Sabberworm/CSS/Value/String.php | 27 - lib/Sabberworm/CSS/Value/URL.php | 26 - lib/Sabberworm/CSS/Value/Value.php | 8 - lib/Sabberworm/CSS/Value/ValueList.php | 44 -- tests/CSSDeclarationBlockTest.php | 223 ++++++ .../CSS/ParserTest.php => CSSParserTests.php} | 215 +++--- .../CSS/RuleSet/DeclarationBlockTest.php | 208 ------ tests/bootstrap.php | 10 - tests/files/1readme.css | 10 - tests/files/2readme.css | 5 - tests/files/docuwiki.css | 1 - tests/quickdump.php | 4 +- 35 files changed, 1843 insertions(+), 2243 deletions(-) rename lib/Sabberworm/CSS/Parser.php => CSSParser.php (54%) delete mode 100644 composer.json create mode 100644 lib/CSSProperties.php create mode 100644 lib/CSSRule.php create mode 100644 lib/CSSRuleSet.php create mode 100644 lib/CSSValue.php create mode 100644 lib/CSSValueList.php delete mode 100644 lib/Sabberworm/CSS/CSSList/CSSList.php delete mode 100644 lib/Sabberworm/CSS/CSSList/Document.php delete mode 100644 lib/Sabberworm/CSS/CSSList/MediaQuery.php delete mode 100644 lib/Sabberworm/CSS/Property/Charset.php delete mode 100644 lib/Sabberworm/CSS/Property/Import.php delete mode 100644 lib/Sabberworm/CSS/Property/Selector.php delete mode 100644 lib/Sabberworm/CSS/Rule/Rule.php delete mode 100644 lib/Sabberworm/CSS/RuleSet/AtRule.php delete mode 100644 lib/Sabberworm/CSS/RuleSet/DeclarationBlock.php delete mode 100644 lib/Sabberworm/CSS/RuleSet/RuleSet.php delete mode 100644 lib/Sabberworm/CSS/Value/CSSFunction.php delete mode 100644 lib/Sabberworm/CSS/Value/Color.php delete mode 100644 lib/Sabberworm/CSS/Value/PrimitiveValue.php delete mode 100644 lib/Sabberworm/CSS/Value/RuleValueList.php delete mode 100644 lib/Sabberworm/CSS/Value/Size.php delete mode 100644 lib/Sabberworm/CSS/Value/String.php delete mode 100644 lib/Sabberworm/CSS/Value/URL.php delete mode 100644 lib/Sabberworm/CSS/Value/Value.php delete mode 100644 lib/Sabberworm/CSS/Value/ValueList.php create mode 100644 tests/CSSDeclarationBlockTest.php rename tests/{Sabberworm/CSS/ParserTest.php => CSSParserTests.php} (53%) delete mode 100644 tests/Sabberworm/CSS/RuleSet/DeclarationBlockTest.php delete mode 100644 tests/bootstrap.php delete mode 100644 tests/files/1readme.css delete mode 100644 tests/files/2readme.css delete mode 100644 tests/files/docuwiki.css diff --git a/lib/Sabberworm/CSS/Parser.php b/CSSParser.php similarity index 54% rename from lib/Sabberworm/CSS/Parser.php rename to CSSParser.php index e32eb808..7a86c5a1 100644 --- a/lib/Sabberworm/CSS/Parser.php +++ b/CSSParser.php @@ -1,76 +1,65 @@ sText = $sText; $this->iCurrentPosition = 0; $this->setCharset($sDefaultCharset); } - + public function setCharset($sCharset) { $this->sCharset = $sCharset; $this->iLength = $this->strlen($this->sText); } public function getCharset() { - return $this->sCharset; + return $this->sCharset; } - + public function parse() { - $oResult = new Document(); + $oResult = new CSSDocument(); $this->parseDocument($oResult); return $oResult; } - public function setUseMbFlag($bFlag) { - $this->bUseMbFunctions = (bool) $bFlag; - } + public function setUseMbFlag($bFlag){ + $this->bUseMbFunctions = (bool) $bFlag; + } - private function parseDocument(Document $oDocument) { + private function parseDocument(CSSDocument $oDocument) { $this->consumeWhiteSpace(); $this->parseList($oDocument, true); } - + private function parseList(CSSList $oList, $bIsRoot = false) { - while (!$this->isEnd()) { - if ($this->comes('@')) { + while(!$this->isEnd()) { + if($this->comes('@')) { $oList->append($this->parseAtRule()); - } else if ($this->comes('}')) { + } else if($this->comes('}')) { $this->consume('}'); - if ($bIsRoot) { - throw new \Exception("Unopened {"); + if($bIsRoot) { + throw new Exception("Unopened {"); } else { return; } @@ -79,110 +68,110 @@ private function parseList(CSSList $oList, $bIsRoot = false) { } $this->consumeWhiteSpace(); } - if (!$bIsRoot) { - throw new \Exception("Unexpected end of document"); + if(!$bIsRoot) { + throw new Exception("Unexpected end of document"); } } - + private function parseAtRule() { $this->consume('@'); $sIdentifier = $this->parseIdentifier(); $this->consumeWhiteSpace(); - if ($sIdentifier === 'media') { - $oResult = new MediaQuery(); + if($sIdentifier === 'media') { + $oResult = new CSSMediaQuery(); $oResult->setQuery(trim($this->consumeUntil('{'))); $this->consume('{'); $this->consumeWhiteSpace(); $this->parseList($oResult); return $oResult; - } else if ($sIdentifier === 'import') { + } else if($sIdentifier === 'import') { $oLocation = $this->parseURLValue(); $this->consumeWhiteSpace(); $sMediaQuery = null; - if (!$this->comes(';')) { + if(!$this->comes(';')) { $sMediaQuery = $this->consumeUntil(';'); } $this->consume(';'); - return new Import($oLocation, $sMediaQuery); - } else if ($sIdentifier === 'charset') { + return new CSSImport($oLocation, $sMediaQuery); + } else if($sIdentifier === 'charset') { $sCharset = $this->parseStringValue(); $this->consumeWhiteSpace(); $this->consume(';'); $this->setCharset($sCharset->getString()); - return new Charset($sCharset); + return new CSSCharset($sCharset); } else { //Unknown other at rule (font-face or such) $this->consume('{'); $this->consumeWhiteSpace(); - $oAtRule = new AtRule($sIdentifier); + $oAtRule = new CSSAtRule($sIdentifier); $this->parseRuleSet($oAtRule); return $oAtRule; } } - + private function parseIdentifier($bAllowFunctions = true) { $sResult = $this->parseCharacter(true); - if ($sResult === null) { - throw new \Exception("Identifier expected, got {$this->peek(5)}"); + if($sResult === null) { + throw new Exception("Identifier expected, got {$this->peek(5)}"); } $sCharacter; - while (($sCharacter = $this->parseCharacter(true)) !== null) { + while(($sCharacter = $this->parseCharacter(true)) !== null) { $sResult .= $sCharacter; } - if ($bAllowFunctions && $this->comes('(')) { + if($bAllowFunctions && $this->comes('(')) { $this->consume('('); $sResult = new CSSFunction($sResult, $this->parseValue(array('=', ',', ' '))); $this->consume(')'); } return $sResult; } - + private function parseStringValue() { $sBegin = $this->peek(); $sQuote = null; - if ($sBegin === "'") { + if($sBegin === "'") { $sQuote = "'"; - } else if ($sBegin === '"') { + } else if($sBegin === '"') { $sQuote = '"'; } - if ($sQuote !== null) { + if($sQuote !== null) { $this->consume($sQuote); } $sResult = ""; $sContent = null; - if ($sQuote === null) { + if($sQuote === null) { //Unquoted strings end in whitespace or with braces, brackets, parentheses - while (!preg_match('/[\\s{}()<>\\[\\]]/isu', $this->peek())) { + while(!preg_match('/[\\s{}()<>\\[\\]]/isu', $this->peek())) { $sResult .= $this->parseCharacter(false); } } else { - while (!$this->comes($sQuote)) { + while(!$this->comes($sQuote)) { $sContent = $this->parseCharacter(false); - if ($sContent === null) { - throw new \Exception("Non-well-formed quoted string {$this->peek(3)}"); + if($sContent === null) { + throw new Exception("Non-well-formed quoted string {$this->peek(3)}"); } $sResult .= $sContent; } $this->consume($sQuote); } - return new String($sResult); + return new CSSString($sResult); } - + private function parseCharacter($bIsForIdentifier) { - if ($this->peek() === '\\') { + if($this->peek() === '\\') { $this->consume('\\'); - if ($this->comes('\n') || $this->comes('\r')) { + if($this->comes('\n') || $this->comes('\r')) { return ''; } $aMatches; - if (preg_match('/[0-9a-fA-F]/Su', $this->peek()) === 0) { + 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'); - if ($this->strlen($sUnicode) < 6) { + if($this->strlen($sUnicode) < 6) { //Consume whitespace after incomplete unicode escape - if (preg_match('/\\s/isSu', $this->peek())) { - if ($this->comes('\r\n')) { + if(preg_match('/\\s/isSu', $this->peek())) { + if($this->comes('\r\n')) { $this->consume(2); } else { $this->consume(1); @@ -191,16 +180,16 @@ private function parseCharacter($bIsForIdentifier) { } $iUnicode = intval($sUnicode, 16); $sUtf32 = ""; - for ($i = 0; $i < 4; $i++) { + for($i=0;$i<4;$i++) { $sUtf32 .= chr($iUnicode & 0xff); $iUnicode = $iUnicode >> 8; } return iconv('utf-32le', $this->sCharset, $sUtf32); } - if ($bIsForIdentifier) { - if (preg_match('/[a-zA-Z0-9]|-|_/u', $this->peek()) === 1) { + if($bIsForIdentifier) { + if(preg_match('/[a-zA-Z0-9]|-|_/u', $this->peek()) === 1) { return $this->consume(1); - } else if (ord($this->peek()) > 0xa1) { + } else if(ord($this->peek()) > 0xa1) { return $this->consume(1); } else { return null; @@ -211,42 +200,41 @@ private function parseCharacter($bIsForIdentifier) { // Does not reach here return null; } - + private function parseSelector() { - $oResult = new DeclarationBlock(); + $oResult = new CSSDeclarationBlock(); $oResult->setSelector($this->consumeUntil('{')); $this->consume('{'); $this->consumeWhiteSpace(); $this->parseRuleSet($oResult); return $oResult; } - + private function parseRuleSet($oRuleSet) { - while (!$this->comes('}')) { + while(!$this->comes('}')) { $oRuleSet->addRule($this->parseRule()); $this->consumeWhiteSpace(); } $this->consume('}'); } - + private function parseRule() { - $oRule = new Rule($this->parseIdentifier()); + $oRule = new CSSRule($this->parseIdentifier()); $this->consumeWhiteSpace(); $this->consume(':'); $oValue = $this->parseValue(self::listDelimiterForRule($oRule->getRule())); $oRule->setValue($oValue); - if ($this->comes('!')) { + if($this->comes('!')) { $this->consume('!'); $this->consumeWhiteSpace(); $sImportantMarker = $this->consume(strlen('important')); - if (mb_convert_case($sImportantMarker, MB_CASE_LOWER) !== 'important') { - throw new \Exception("! was followed by “" . $sImportantMarker . "”. Expected “important”"); + if(mb_convert_case($sImportantMarker, MB_CASE_LOWER) !== 'important') { + throw new Exception("! was followed by “".$sImportantMarker."”. Expected “important”"); } $oRule->setIsImportant(true); } - while ($this->comes(';')) { + if($this->comes(';')) { $this->consume(';'); - $this->consumeWhiteSpace(); } return $oRule; } @@ -254,18 +242,18 @@ private function parseRule() { private function parseValue($aListDelimiters) { $aStack = array(); $this->consumeWhiteSpace(); - while (!($this->comes('}') || $this->comes(';') || $this->comes('!') || $this->comes(')'))) { - if (count($aStack) > 0) { + while(!($this->comes('}') || $this->comes(';') || $this->comes('!') || $this->comes(')'))) { + if(count($aStack) > 0) { $bFoundDelimiter = false; - foreach ($aListDelimiters as $sDelimiter) { - if ($this->comes($sDelimiter)) { + foreach($aListDelimiters as $sDelimiter) { + if($this->comes($sDelimiter)) { array_push($aStack, $this->consume($sDelimiter)); $this->consumeWhiteSpace(); $bFoundDelimiter = true; break; } } - if (!$bFoundDelimiter) { + if(!$bFoundDelimiter) { //Whitespace was the list delimiter array_push($aStack, ' '); } @@ -273,46 +261,46 @@ private function parseValue($aListDelimiters) { array_push($aStack, $this->parsePrimitiveValue()); $this->consumeWhiteSpace(); } - foreach ($aListDelimiters as $sDelimiter) { - if (count($aStack) === 1) { + foreach($aListDelimiters as $sDelimiter) { + if(count($aStack) === 1) { return $aStack[0]; } $iStartPosition = null; - while (($iStartPosition = array_search($sDelimiter, $aStack, true)) !== false) { + while(($iStartPosition = array_search($sDelimiter, $aStack, true)) !== false) { $iLength = 2; //Number of elements to be joined - for ($i = $iStartPosition + 2; $i < count($aStack); $i+=2) { - if ($sDelimiter !== $aStack[$i]) { + for($i=$iStartPosition+2;$iaddListComponent($aStack[$i]); } - array_splice($aStack, $iStartPosition - 1, $iLength * 2 - 1, array($oList)); + array_splice($aStack, $iStartPosition-1, $iLength*2-1, array($oList)); } } return $aStack[0]; } private static function listDelimiterForRule($sRule) { - if (preg_match('/^font($|-)/', $sRule)) { + if(preg_match('/^font($|-)/', $sRule)) { return array(',', '/', ' '); } return array(',', ' ', '/'); } - + private function parsePrimitiveValue() { $oValue = null; $this->consumeWhiteSpace(); - if (is_numeric($this->peek()) || (($this->comes('-') || $this->comes('.')) && is_numeric($this->peek(1, 1)))) { + if(is_numeric($this->peek()) || (($this->comes('-') || $this->comes('.')) && is_numeric($this->peek(1, 1)))) { $oValue = $this->parseNumericValue(); - } else if ($this->comes('#') || $this->comes('rgb') || $this->comes('hsl')) { + } else if($this->comes('#') || $this->comes('rgb') || $this->comes('hsl')) { $oValue = $this->parseColorValue(); - } else if ($this->comes('url')) { + } else if($this->comes('url')){ $oValue = $this->parseURLValue(); - } else if ($this->comes("'") || $this->comes('"')) { + } else if($this->comes("'") || $this->comes('"')){ $oValue = $this->parseStringValue(); } else { $oValue = $this->parseIdentifier(); @@ -320,14 +308,14 @@ private function parsePrimitiveValue() { $this->consumeWhiteSpace(); return $oValue; } - + private function parseNumericValue($bForColor = false) { $sSize = ''; - if ($this->comes('-')) { + if($this->comes('-')) { $sSize .= $this->consume('-'); } - while (is_numeric($this->peek()) || $this->comes('.')) { - if ($this->comes('.')) { + while(is_numeric($this->peek()) || $this->comes('.')) { + if($this->comes('.')) { $sSize .= $this->consume('.'); } else { $sSize .= $this->consume(1); @@ -335,171 +323,174 @@ private function parseNumericValue($bForColor = false) { } $fSize = floatval($sSize); $sUnit = null; - if ($this->comes('%')) { + if($this->comes('%')) { $sUnit = $this->consume('%'); - } else if ($this->comes('em')) { + } else if($this->comes('em')) { $sUnit = $this->consume('em'); - } else if ($this->comes('ex')) { + } else if($this->comes('ex')) { $sUnit = $this->consume('ex'); - } else if ($this->comes('px')) { + } else if($this->comes('px')) { $sUnit = $this->consume('px'); - } else if ($this->comes('deg')) { + } else if($this->comes('deg')) { $sUnit = $this->consume('deg'); - } else if ($this->comes('s')) { + } else if($this->comes('s')) { $sUnit = $this->consume('s'); - } else if ($this->comes('cm')) { + } else if($this->comes('cm')) { $sUnit = $this->consume('cm'); - } else if ($this->comes('pt')) { + } else if($this->comes('pt')) { $sUnit = $this->consume('pt'); - } else if ($this->comes('in')) { + } else if($this->comes('in')) { $sUnit = $this->consume('in'); - } else if ($this->comes('pc')) { + } else if($this->comes('pc')) { $sUnit = $this->consume('pc'); - } else if ($this->comes('cm')) { + } else if($this->comes('cm')) { $sUnit = $this->consume('cm'); - } else if ($this->comes('mm')) { + } else if($this->comes('mm')) { $sUnit = $this->consume('mm'); } - return new Size($fSize, $sUnit, $bForColor); + return new CSSSize($fSize, $sUnit, $bForColor); } - + private function parseColorValue() { $aColor = array(); - if ($this->comes('#')) { + if($this->comes('#')) { $this->consume('#'); $sValue = $this->parseIdentifier(false); - if ($this->strlen($sValue) === 3) { - $sValue = $sValue[0] . $sValue[0] . $sValue[1] . $sValue[1] . $sValue[2] . $sValue[2]; + if($this->strlen($sValue) === 3) { + $sValue = $sValue[0].$sValue[0].$sValue[1].$sValue[1].$sValue[2].$sValue[2]; } - $aColor = array('r' => new Size(intval($sValue[0] . $sValue[1], 16), null, true), 'g' => new Size(intval($sValue[2] . $sValue[3], 16), null, true), 'b' => new Size(intval($sValue[4] . $sValue[5], 16), null, true)); + $aColor = array('r' => new CSSSize(intval($sValue[0].$sValue[1], 16), null, true), 'g' => new CSSSize(intval($sValue[2].$sValue[3], 16), null, true), 'b' => new CSSSize(intval($sValue[4].$sValue[5], 16), null, true)); } else { $sColorMode = $this->parseIdentifier(false); $this->consumeWhiteSpace(); $this->consume('('); $iLength = $this->strlen($sColorMode); - for ($i = 0; $i < $iLength; $i++) { + for($i=0;$i<$iLength;$i++) { $this->consumeWhiteSpace(); $aColor[$sColorMode[$i]] = $this->parseNumericValue(true); $this->consumeWhiteSpace(); - if ($i < ($iLength - 1)) { + if($i < ($iLength-1)) { $this->consume(','); } } $this->consume(')'); } - return new Color($aColor); + return new CSSColor($aColor); } - + private function parseURLValue() { $bUseUrl = $this->comes('url'); - if ($bUseUrl) { + if($bUseUrl) { $this->consume('url'); $this->consumeWhiteSpace(); $this->consume('('); } $this->consumeWhiteSpace(); - $oResult = new URL($this->parseStringValue()); - if ($bUseUrl) { + $oResult = new CSSURL($this->parseStringValue()); + if($bUseUrl) { $this->consumeWhiteSpace(); $this->consume(')'); } return $oResult; } - + private function comes($sString, $iOffset = 0) { - if ($this->isEnd()) { + if($this->isEnd()) { return false; } return $this->peek($sString, $iOffset) == $sString; } - + private function peek($iLength = 1, $iOffset = 0) { - if ($this->isEnd()) { + if($this->isEnd()) { return ''; } - if (is_string($iLength)) { + if(is_string($iLength)) { $iLength = $this->strlen($iLength); } - if (is_string($iOffset)) { + if(is_string($iOffset)) { $iOffset = $this->strlen($iOffset); } - return $this->substr($this->sText, $this->iCurrentPosition + $iOffset, $iLength); + return $this->substr($this->sText, $this->iCurrentPosition+$iOffset, $iLength); } - + private function consume($mValue = 1) { - if (is_string($mValue)) { + if(is_string($mValue)) { $iLength = $this->strlen($mValue); - if ($this->substr($this->sText, $this->iCurrentPosition, $iLength) !== $mValue) { - throw new \Exception("Expected $mValue, got " . $this->peek(5)); + if($this->substr($this->sText, $this->iCurrentPosition, $iLength) !== $mValue) { + throw new Exception("Expected $mValue, got ".$this->peek(5)); } $this->iCurrentPosition += $this->strlen($mValue); return $mValue; } else { - if ($this->iCurrentPosition + $mValue > $this->iLength) { - throw new \Exception("Tried to consume $mValue chars, exceeded file end"); + if($this->iCurrentPosition+$mValue > $this->iLength) { + throw new Exception("Tried to consume $mValue chars, exceeded file end"); } $sResult = $this->substr($this->sText, $this->iCurrentPosition, $mValue); $this->iCurrentPosition += $mValue; return $sResult; } } - + private function consumeExpression($mExpression) { $aMatches; - if (preg_match($mExpression, $this->inputLeft(), $aMatches, PREG_OFFSET_CAPTURE) === 1) { + if(preg_match($mExpression, $this->inputLeft(), $aMatches, PREG_OFFSET_CAPTURE) === 1) { return $this->consume($aMatches[0][0]); } - throw new \Exception("Expected pattern $mExpression not found, got: {$this->peek(5)}"); + throw new Exception("Expected pattern $mExpression not found, got: {$this->peek(5)}"); } - + private function consumeWhiteSpace() { do { - while (preg_match('/\\s/isSu', $this->peek()) === 1) { + while(preg_match('/\\s/isSu', $this->peek()) === 1) { $this->consume(1); } - } while ($this->consumeComment()); + } while($this->consumeComment()); } - + private function consumeComment() { - if ($this->comes('/*')) { + if($this->comes('/*')) { $this->consumeUntil('*/'); $this->consume('*/'); return true; } return false; } - + private function isEnd() { return $this->iCurrentPosition >= $this->iLength; } - + private function consumeUntil($sEnd) { $iEndPos = mb_strpos($this->sText, $sEnd, $this->iCurrentPosition, $this->sCharset); - if ($iEndPos === false) { - throw new \Exception("Required $sEnd not found, got {$this->peek(5)}"); + if($iEndPos === false) { + throw new Exception("Required $sEnd not found, got {$this->peek(5)}"); } - return $this->consume($iEndPos - $this->iCurrentPosition); + return $this->consume($iEndPos-$this->iCurrentPosition); } - + private function inputLeft() { return $this->substr($this->sText, $this->iCurrentPosition, -1); } - private function substr($string, $start, $length) { - if ($this->bUseMbFunctions) { - return mb_substr($string, $start, $length, $this->sCharset); - } else { - return substr($string, $start, $length); - } - } - - private function strlen($text) { - if ($this->bUseMbFunctions) { - return mb_strlen($text, $this->sCharset); - } else { - return strlen($text); - } - } - + private function substr($string, $start, $length){ + if($this->bUseMbFunctions) { + return mb_substr($string, $start, $length, $this->sCharset); + } + else { + return substr($string, $start, $length); + } + } + + private function strlen($text) + { + if($this->bUseMbFunctions) { + return mb_strlen($text, $this->sCharset); + } + else { + return strlen($text); + } + + } } diff --git a/README.md b/README.md index 63645273..594574f4 100644 --- a/README.md +++ b/README.md @@ -5,89 +5,78 @@ A Parser for CSS Files written in PHP. Allows extraction of CSS files into a dat ## Usage -### Installation using composer +### Installation -Add php-css-parser to your composer.json - - { - "require": { - "sabberworm/php-css-parser": "*" - } - } +Include the `CSSParser.php` file somewhere in your code using `require_once` (or `include_once`, if you prefer), the given `lib` folder needs to exist next to the file. ### Extraction To use the CSS Parser, create a new instance. The constructor takes the following form: - new Sabberworm\CSS\Parser($sText, $sDefaultCharset = 'utf-8'); + new CSSParser($sCssContents, $sCharset = 'utf-8'); The charset is used only if no @charset declaration is found in the CSS file. To read a file, for example, you’d do the following: - $oCssParser = new Sabberworm\CSS\Parser(file_get_contents('somefile.css')); + $oCssParser = new CSSParser(file_get_contents('somefile.css')); $oCssDocument = $oCssParser->parse(); The resulting CSS document structure can be manipulated prior to being output. ### Manipulation -The resulting data structure consists mainly of five basic types: `CSSList`, `RuleSet`, `Rule`, `Selector` and `Value`. There are two additional types used: `Import` and `Charset` which you won’t use often. +The resulting data structure consists mainly of five basic types: `CSSList`, `CSSRuleSet`, `CSSRule`, `CSSSelector` and `CSSValue`. There are two additional types used: `CSSImport` and `CSSCharset` which you won’t use often. #### CSSList `CSSList` represents a generic CSS container, most likely containing declaration blocks (rule sets with a selector) but it may also contain at-rules, charset declarations, etc. `CSSList` has the following concrete subtypes: -* `Document` – representing the root of a CSS file. -* `MediaQuery` – represents a subsection of a CSSList that only applies to a output device matching the contained media query. - -#### RuleSet +* `CSSDocument` – representing the root of a CSS file. +* `CSSMediaQuery` – represents a subsection of a CSSList that only applies to a output device matching the contained media query. -`RuleSet` is a container for individual rules. The most common form of a rule set is one constrained by a selector. The following concrete subtypes exist: +#### CSSRuleSet -* `AtRule` – for generic at-rules which do not match the ones specifically mentioned like @import, @charset or @media. A common example for this is @font-face. -* `DeclarationBlock` – a RuleSet constrained by a `Selector`; contains an array of selector objects (comma-separated in the CSS) as well as the rules to be applied to the matching elements. +`CSSRuleSet` is a container for individual rules. The most common form of a rule set is one constrained by a selector. The following concrete subtypes exist: -Note: A `CSSList` can contain other `CSSList`s (and `Import`s as well as a `Charset`) while a `RuleSet` can only contain `Rule`s. +* `CSSAtRule` – for generic at-rules which do not match the ones specifically mentioned like @import, @charset or @media. A common example for this is @font-face. +* `CSSDeclarationBlock` – a RuleSet constrained by a `CSSSelector; contains an array of selector objects (comma-separated in the CSS) as well as the rules to be applied to the matching elements. -#### Rule +Note: A `CSSList` can contain other `CSSList`s (and `CSSImport`s as well as a `CSSCharset`) while a `CSSRuleSet` can only contain `CSSRule`s. -`Rule`s just have a key (the rule) and a value. These values are all instances of a `Value`. +#### CSSRule -#### Value +`CSSRule`s just have a key (the rule) and multiple values (the part after the colon in the CSS file). This means the `values` attribute is an array consisting of arrays. The inner level of arrays is comma-separated in the CSS file while the outer level is whitespace-separated. -`Value` is an abstract class that only defines the `__toString` method. The concrete subclasses for atomic value types are: +#### CSSValue -* `Size` – consists of a numeric `size` value and a unit. -* `Color` – colors can be input in the form #rrggbb, #rgb or schema(val1, val2, …) but are alwas stored as an array of ('s' => val1, 'c' => val2, 'h' => val3, …) and output in the second form. -* `String` – this is just a wrapper for quoted strings to distinguish them from keywords; always output with double quotes. -* `URL` – URLs in CSS; always output in URL("") notation. +`CSSValue` is an abstract class that only defines the `__toString` method. The concrete subclasses are: -There is another abstract subclass of `Value`, `ValueList`. A `ValueList` represents a lists of `Value`s, separated by some separation character (mostly `,`, whitespace, or `/`). There are two types of `ValueList`s: +* `CSSSize` – consists of a numeric `size` value and a unit. +* `CSSColor` – colors can be input in the form #rrggbb, #rgb or schema(val1, val2, …) but are alwas stored as an array of ('s' => val1, 'c' => val2, 'h' => val3, …) and output in the second form. +* `CSSString` – this is just a wrapper for quoted strings to distinguish them from keywords; always output with double quotes. +* `CSSURL` – URLs in CSS; always output in URL("") notation. -* `RuleValueList` – The default type, used to represent all multi-valued rules like `font: bold 12px/3 Helvetica, Verdana, sans-serif;` (where the value would be a whitespace-separated list of the primitive value `bold`, a slash-separated list and a comma-separated list). -* `CSSFunction` – A special kind of value that also contains a function name and where the values are the function’s arguments. Also handles equals-sign-separated argument lists like `filter: alpha(opacity=90);`. - -To access the items stored in a `CSSList` – like the document you got back when calling `$oCssParser->parse()` –, use `getContents()`, then iterate over that collection and use instanceof to check whether you’re dealing with another `CSSList`, a `RuleSet`, a `Import` or a `Charset`. +To access the items stored in a `CSSList` – like the document you got back when calling `$oCssParser->parse()` –, use `getContents()`, then iterate over that collection and use instanceof to check whether you’re dealing with another `CSSList`, a `CSSRuleSet`, a `CSSImport` or a `CSSCharset`. To append a new item (selector, media query, etc.) to an existing `CSSList`, construct it using the constructor for this class and use the `append($oItem)` method. -If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $oRule)`, `getRules()` and `removeRule($mRule)` (which accepts either a Rule instance or a rule name; optionally suffixed by a dash to remove all related rules). +If you want to manipulate a `CSSRuleSet`, use the methods `addRule(CSSRule $oRule)`, `getRules()` and `removeRule($mRule)` (which accepts either a CSSRule instance or a rule name; optionally suffixed by a dash to remove all related rules). #### Convenience methods -There are a few convenience methods on Document to ease finding, manipulating and deleting rules: +There are a few convenience methods on CSSDocument to ease finding, manipulating and deleting rules: * `getAllDeclarationBlocks()` – does what it says; no matter how deeply nested your selectors are. Aliased as `getAllSelectors()`. * `getAllRuleSets()` – does what it says; no matter how deeply nested your rule sets are. -* `getAllValues()` – finds all `Value` objects inside `Rule`s. +* `getAllValues()` – finds all `CSSValue` objects inside `CSSRule`s. ### Use cases -#### Use `Parser` to prepend an id to all selectors +#### Use `CSSParser` to prepend an id to all selectors $sMyId = "#my_id"; - $oParser = new Sabberworm\CSS\Parser($sText); + $oParser = new CSSParser($sCssContents); $oCss = $oParser->parse(); foreach($oCss->getAllDeclarationBlocks() as $oBlock) { foreach($oBlock->getSelectors() as $oSelector) { @@ -98,7 +87,7 @@ There are a few convenience methods on Document to ease finding, manipulating an #### Shrink all absolute sizes to half - $oParser = new Sabberworm\CSS\Parser($sText); + $oParser = new CSSParser($sCssContents); $oCss = $oParser->parse(); foreach($oCss->getAllValues() as $mValue) { if($mValue instanceof CSSSize && !$mValue->isRelative()) { @@ -108,7 +97,7 @@ There are a few convenience methods on Document to ease finding, manipulating an #### Remove unwanted rules - $oParser = new Sabberworm\CSS\Parser($sText); + $oParser = new CSSParser($sCssContents); $oCss = $oParser->parse(); foreach($oCss->getAllRuleSets() as $oRuleSet) { $oRuleSet->removeRule('font-'); //Note that the added dash will make this remove all rules starting with font- (like font-size, font-weight, etc.) as well as a potential font-rule @@ -119,7 +108,7 @@ There are a few convenience methods on Document to ease finding, manipulating an To output the entire CSS document into a variable, just use `->__toString()`: - $oCssParser = new Sabberworm\CSS\Parser(file_get_contents('somefile.css')); + $oCssParser = new CSSParser(file_get_contents('somefile.css')); $oCssDocument = $oCssParser->parse(); print $oCssDocument->__toString(); @@ -142,92 +131,92 @@ To output the entire CSS document into a variable, just use `->__toString()`: #### Structure (`var_dump()`) - object(Sabberworm\CSS\CSSList\Document)#219 (1) { - ["aContents":"Sabberworm\CSS\CSSList\CSSList":private]=> - array(3) { - [0]=> - object(Sabberworm\CSS\Property\Charset)#11 (1) { - ["sCharset":"Sabberworm\CSS\Property\Charset":private]=> - object(Sabberworm\CSS\Value\String)#220 (1) { - ["sString":"Sabberworm\CSS\Value\String":private]=> - string(5) "utf-8" - } - } - [1]=> - object(Sabberworm\CSS\RuleSet\AtRule)#6 (2) { - ["sType":"Sabberworm\CSS\RuleSet\AtRule":private]=> - string(9) "font-face" - ["aRules":"Sabberworm\CSS\RuleSet\RuleSet":private]=> - array(2) { - ["font-family"]=> - object(Sabberworm\CSS\Rule\Rule)#15 (3) { - ["sRule":"Sabberworm\CSS\Rule\Rule":private]=> - string(11) "font-family" - ["mValue":"Sabberworm\CSS\Rule\Rule":private]=> - object(Sabberworm\CSS\Value\String)#13 (1) { - ["sString":"Sabberworm\CSS\Value\String":private]=> - string(10) "CrassRoots" - } - ["bIsImportant":"Sabberworm\CSS\Rule\Rule":private]=> - bool(false) - } - ["src"]=> - object(Sabberworm\CSS\Rule\Rule)#12 (3) { - ["sRule":"Sabberworm\CSS\Rule\Rule":private]=> - string(3) "src" - ["mValue":"Sabberworm\CSS\Rule\Rule":private]=> - object(Sabberworm\CSS\Value\URL)#14 (1) { - ["oURL":"Sabberworm\CSS\Value\URL":private]=> - object(Sabberworm\CSS\Value\String)#16 (1) { - ["sString":"Sabberworm\CSS\Value\String":private]=> - string(15) "../media/cr.ttf" - } - } - ["bIsImportant":"Sabberworm\CSS\Rule\Rule":private]=> - bool(false) - } - } - } - [2]=> - object(Sabberworm\CSS\RuleSet\DeclarationBlock)#17 (2) { - ["aSelectors":"Sabberworm\CSS\RuleSet\DeclarationBlock":private]=> - array(2) { - [0]=> - object(Sabberworm\CSS\Property\Selector)#18 (2) { - ["sSelector":"Sabberworm\CSS\Property\Selector":private]=> - string(4) "html" - ["iSpecificity":"Sabberworm\CSS\Property\Selector":private]=> - NULL - } - [1]=> - object(Sabberworm\CSS\Property\Selector)#19 (2) { - ["sSelector":"Sabberworm\CSS\Property\Selector":private]=> - string(4) "body" - ["iSpecificity":"Sabberworm\CSS\Property\Selector":private]=> - NULL - } - } - ["aRules":"Sabberworm\CSS\RuleSet\RuleSet":private]=> - array(1) { - ["font-size"]=> - object(Sabberworm\CSS\Rule\Rule)#20 (3) { - ["sRule":"Sabberworm\CSS\Rule\Rule":private]=> - string(9) "font-size" - ["mValue":"Sabberworm\CSS\Rule\Rule":private]=> - object(Sabberworm\CSS\Value\Size)#21 (3) { - ["fSize":"Sabberworm\CSS\Value\Size":private]=> - float(1.6) - ["sUnit":"Sabberworm\CSS\Value\Size":private]=> - string(2) "em" - ["bIsColorComponent":"Sabberworm\CSS\Value\Size":private]=> - bool(false) - } - ["bIsImportant":"Sabberworm\CSS\Rule\Rule":private]=> - bool(false) - } - } - } - } + object(CSSDocument)#2 (1) { + ["aContents":"CSSList":private]=> + array(3) { + [0]=> + object(CSSCharset)#4 (1) { + ["sCharset":"CSSCharset":private]=> + object(CSSString)#3 (1) { + ["sString":"CSSString":private]=> + string(5) "utf-8" + } + } + [1]=> + object(CSSAtRule)#5 (2) { + ["sType":"CSSAtRule":private]=> + string(9) "font-face" + ["aRules":"CSSRuleSet":private]=> + array(2) { + ["font-family"]=> + object(CSSRule)#6 (3) { + ["sRule":"CSSRule":private]=> + string(11) "font-family" + ["mValue":"CSSRule":private]=> + object(CSSString)#7 (1) { + ["sString":"CSSString":private]=> + string(10) "CrassRoots" + } + ["bIsImportant":"CSSRule":private]=> + bool(false) + } + ["src"]=> + object(CSSRule)#8 (3) { + ["sRule":"CSSRule":private]=> + string(3) "src" + ["mValue":"CSSRule":private]=> + object(CSSURL)#9 (1) { + ["oURL":"CSSURL":private]=> + object(CSSString)#10 (1) { + ["sString":"CSSString":private]=> + string(15) "../media/cr.ttf" + } + } + ["bIsImportant":"CSSRule":private]=> + bool(false) + } + } + } + [2]=> + object(CSSDeclarationBlock)#11 (2) { + ["aSelectors":"CSSDeclarationBlock":private]=> + array(2) { + [0]=> + object(CSSSelector)#12 (2) { + ["sSelector":"CSSSelector":private]=> + string(4) "html" + ["iSpecificity":"CSSSelector":private]=> + NULL + } + [1]=> + object(CSSSelector)#13 (2) { + ["sSelector":"CSSSelector":private]=> + string(4) "body" + ["iSpecificity":"CSSSelector":private]=> + NULL + } + } + ["aRules":"CSSRuleSet":private]=> + array(1) { + ["font-size"]=> + object(CSSRule)#14 (3) { + ["sRule":"CSSRule":private]=> + string(9) "font-size" + ["mValue":"CSSRule":private]=> + object(CSSSize)#15 (3) { + ["fSize":"CSSSize":private]=> + float(1.6) + ["sUnit":"CSSSize":private]=> + string(2) "em" + ["bIsColorComponent":"CSSSize":private]=> + bool(false) + } + ["bIsImportant":"CSSRule":private]=> + bool(false) + } + } + } + } } #### Output (`__toString()`) @@ -246,112 +235,112 @@ To output the entire CSS document into a variable, just use `->__toString()`: #### Structure (`var_dump()`) - object(Sabberworm\CSS\CSSList\Document)#217 (1) { - ["aContents":"Sabberworm\CSS\CSSList\CSSList":private]=> - array(1) { - [0]=> - object(Sabberworm\CSS\RuleSet\DeclarationBlock)#17 (2) { - ["aSelectors":"Sabberworm\CSS\RuleSet\DeclarationBlock":private]=> - array(1) { - [0]=> - object(Sabberworm\CSS\Property\Selector)#20 (2) { - ["sSelector":"Sabberworm\CSS\Property\Selector":private]=> - string(7) "#header" - ["iSpecificity":"Sabberworm\CSS\Property\Selector":private]=> - NULL - } - } - ["aRules":"Sabberworm\CSS\RuleSet\RuleSet":private]=> - array(3) { - ["margin"]=> - object(Sabberworm\CSS\Rule\Rule)#21 (3) { - ["sRule":"Sabberworm\CSS\Rule\Rule":private]=> - string(6) "margin" - ["mValue":"Sabberworm\CSS\Rule\Rule":private]=> - object(Sabberworm\CSS\Value\RuleValueList)#14 (2) { - ["aComponents":protected]=> - array(4) { - [0]=> - object(Sabberworm\CSS\Value\Size)#19 (3) { - ["fSize":"Sabberworm\CSS\Value\Size":private]=> - float(10) - ["sUnit":"Sabberworm\CSS\Value\Size":private]=> - string(2) "px" - ["bIsColorComponent":"Sabberworm\CSS\Value\Size":private]=> - bool(false) - } - [1]=> - object(Sabberworm\CSS\Value\Size)#18 (3) { - ["fSize":"Sabberworm\CSS\Value\Size":private]=> - float(2) - ["sUnit":"Sabberworm\CSS\Value\Size":private]=> - string(2) "em" - ["bIsColorComponent":"Sabberworm\CSS\Value\Size":private]=> - bool(false) - } - [2]=> - object(Sabberworm\CSS\Value\Size)#6 (3) { - ["fSize":"Sabberworm\CSS\Value\Size":private]=> - float(1) - ["sUnit":"Sabberworm\CSS\Value\Size":private]=> - string(2) "cm" - ["bIsColorComponent":"Sabberworm\CSS\Value\Size":private]=> - bool(false) - } - [3]=> - object(Sabberworm\CSS\Value\Size)#12 (3) { - ["fSize":"Sabberworm\CSS\Value\Size":private]=> - float(2) - ["sUnit":"Sabberworm\CSS\Value\Size":private]=> - string(1) "%" - ["bIsColorComponent":"Sabberworm\CSS\Value\Size":private]=> - bool(false) - } - } - ["sSeparator":protected]=> - string(1) " " - } - ["bIsImportant":"Sabberworm\CSS\Rule\Rule":private]=> - bool(false) - } - ["font-family"]=> - object(Sabberworm\CSS\Rule\Rule)#16 (3) { - ["sRule":"Sabberworm\CSS\Rule\Rule":private]=> - string(11) "font-family" - ["mValue":"Sabberworm\CSS\Rule\Rule":private]=> - object(Sabberworm\CSS\Value\RuleValueList)#13 (2) { - ["aComponents":protected]=> - array(4) { - [0]=> - string(7) "Verdana" - [1]=> - string(9) "Helvetica" - [2]=> - object(Sabberworm\CSS\Value\String)#15 (1) { - ["sString":"Sabberworm\CSS\Value\String":private]=> - string(9) "Gill Sans" - } - [3]=> - string(10) "sans-serif" - } - ["sSeparator":protected]=> - string(1) "," - } - ["bIsImportant":"Sabberworm\CSS\Rule\Rule":private]=> - bool(false) - } - ["color"]=> - object(Sabberworm\CSS\Rule\Rule)#11 (3) { - ["sRule":"Sabberworm\CSS\Rule\Rule":private]=> - string(5) "color" - ["mValue":"Sabberworm\CSS\Rule\Rule":private]=> - string(3) "red" - ["bIsImportant":"Sabberworm\CSS\Rule\Rule":private]=> - bool(true) - } - } - } - } + object(CSSDocument)#2 (1) { + ["aContents":"CSSList":private]=> + array(1) { + [0]=> + object(CSSDeclarationBlock)#3 (2) { + ["aSelectors":"CSSDeclarationBlock":private]=> + array(1) { + [0]=> + object(CSSSelector)#4 (2) { + ["sSelector":"CSSSelector":private]=> + string(7) "#header" + ["iSpecificity":"CSSSelector":private]=> + NULL + } + } + ["aRules":"CSSRuleSet":private]=> + array(3) { + ["margin"]=> + object(CSSRule)#5 (3) { + ["sRule":"CSSRule":private]=> + string(6) "margin" + ["mValue":"CSSRule":private]=> + object(CSSRuleValueList)#10 (2) { + ["aComponents":protected]=> + array(4) { + [0]=> + object(CSSSize)#6 (3) { + ["fSize":"CSSSize":private]=> + float(10) + ["sUnit":"CSSSize":private]=> + string(2) "px" + ["bIsColorComponent":"CSSSize":private]=> + bool(false) + } + [1]=> + object(CSSSize)#7 (3) { + ["fSize":"CSSSize":private]=> + float(2) + ["sUnit":"CSSSize":private]=> + string(2) "em" + ["bIsColorComponent":"CSSSize":private]=> + bool(false) + } + [2]=> + object(CSSSize)#8 (3) { + ["fSize":"CSSSize":private]=> + float(1) + ["sUnit":"CSSSize":private]=> + string(2) "cm" + ["bIsColorComponent":"CSSSize":private]=> + bool(false) + } + [3]=> + object(CSSSize)#9 (3) { + ["fSize":"CSSSize":private]=> + float(2) + ["sUnit":"CSSSize":private]=> + string(1) "%" + ["bIsColorComponent":"CSSSize":private]=> + bool(false) + } + } + ["sSeparator":protected]=> + string(1) " " + } + ["bIsImportant":"CSSRule":private]=> + bool(false) + } + ["font-family"]=> + object(CSSRule)#11 (3) { + ["sRule":"CSSRule":private]=> + string(11) "font-family" + ["mValue":"CSSRule":private]=> + object(CSSRuleValueList)#13 (2) { + ["aComponents":protected]=> + array(4) { + [0]=> + string(7) "Verdana" + [1]=> + string(9) "Helvetica" + [2]=> + object(CSSString)#12 (1) { + ["sString":"CSSString":private]=> + string(9) "Gill Sans" + } + [3]=> + string(10) "sans-serif" + } + ["sSeparator":protected]=> + string(1) "," + } + ["bIsImportant":"CSSRule":private]=> + bool(false) + } + ["color"]=> + object(CSSRule)#14 (3) { + ["sRule":"CSSRule":private]=> + string(5) "color" + ["mValue":"CSSRule":private]=> + string(3) "red" + ["bIsImportant":"CSSRule":private]=> + bool(true) + } + } + } + } } #### Output (`__toString()`) @@ -363,7 +352,7 @@ To output the entire CSS document into a variable, just use `->__toString()`: * More convenience methods [like `selectorsWithElement($sId/Class/TagName)`, `removeSelector($oSelector)`, `attributesOfType($sType)`, `removeAttributesOfType($sType)`] * Options for output (compact, verbose, etc.) * Support for @namespace -* Named color support (using `Color` instead of an anonymous string literal) +* Named color support (using `CSSColor` instead of an anonymous string literal) * Test suite * Adopt lenient parsing rules * Support for @-rules (other than @media) that are CSSLists (to support @-webkit-keyframes) @@ -373,12 +362,6 @@ To output the entire CSS document into a variable, just use `->__toString()`: * [ju1ius](https://github.com/ju1ius) for the specificity parsing code and the ability to expand/compact shorthand properties. * [GaryJones](https://github.com/GaryJones) for lots of input and [http://css-specificity.info/](http://css-specificity.info/). * [docteurklein](https://github.com/docteurklein) for output formatting and `CSSList->remove()` inspiration. -* [nicolopignatelli](https://github.com/nicolopignatelli) for PSR-0 compatibility. - -## Misc - -* Legacy Support: The latest pre-PSR-0 version of this project can be checked out as the `v0.9` tag. -* Running Tests: To run all unit tests for this project, have `phpunit` installed, `cd` to the `tests` dir and run `phpunit --bootstrap bootstrap.php .`. ## License diff --git a/composer.json b/composer.json deleted file mode 100644 index bd3830ce..00000000 --- a/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "sabberworm/php-css-parser", - "type": "library", - "description": "Parser for CSS Files written in PHP", - "keywords": ["parser", "css", "stylesheet"], - "homepage": "http://www.sabberworm.com/blog/2010/6/10/php-css-parser", - "license": "MIT", - "authors": [ - {"name": "Raphael Schweikert"} - ], - "require": { - "php": ">=5.3.2" - }, - "autoload": { - "psr-0": { "Sabberworm\\CSS": "lib/" } - } -} diff --git a/lib/CSSProperties.php b/lib/CSSProperties.php new file mode 100644 index 00000000..15c9edbd --- /dev/null +++ b/lib/CSSProperties.php @@ -0,0 +1,124 @@ +oLocation = $oLocation; + $this->sMediaQuery = $sMediaQuery; + } + + public function setLocation($oLocation) { + $this->oLocation = $oLocation; + } + + public function getLocation() { + return $this->oLocation; + } + + public function __toString() { + return "@import ".$this->oLocation->__toString().($this->sMediaQuery === null ? '' : ' '.$this->sMediaQuery).';'; + } +} + +/** +* Class representing an @charset rule. +* The following restrictions apply: +* • May not be found in any CSSList other than the CSSDocument. +* • May only appear at the very top of a CSSDocument’s contents. +* • Must not appear more than once. +*/ +class CSSCharset { + private $sCharset; + + public function __construct($sCharset) { + $this->sCharset = $sCharset; + } + + public function setCharset($sCharset) { + $this->sCharset = $sCharset; + } + + public function getCharset() { + return $this->sCharset; + } + + public function __toString() { + return "@charset {$this->sCharset->__toString()};"; + } +} + +/** +* Class representing a single CSS selector. Selectors have to be split by the comma prior to being passed into this class. +*/ +class CSSSelector { + const + NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX = '/ + (\.[\w]+) # classes + | + \[(\w+) # attributes + | + (\:( # pseudo classes + link|visited|active + |hover|focus + |lang + |target + |enabled|disabled|checked|indeterminate + |root + |nth-child|nth-last-child|nth-of-type|nth-last-of-type + |first-child|last-child|first-of-type|last-of-type + |only-child|only-of-type + |empty|contains + )) + /ix', + ELEMENTS_AND_PSEUDO_ELEMENTS_RX = '/ + ((^|[\s\+\>\~]+)[\w]+ # elements + | + \:{1,2}( # pseudo-elements + after|before + |first-letter|first-line + |selection + ) + )/ix'; + + private $sSelector; + private $iSpecificity; + + public function __construct($sSelector, $bCalculateSpecificity = false) { + $this->setSelector($sSelector); + if($bCalculateSpecificity) { + $this->getSpecificity(); + } + } + + public function getSelector() { + return $this->sSelector; + } + + public function setSelector($sSelector) { + $this->sSelector = trim($sSelector); + $this->iSpecificity = null; + } + + public function __toString() { + return $this->getSelector(); + } + + public function getSpecificity() { + if($this->iSpecificity === null) { + $a = 0; + /// @todo should exclude \# as well as "#" + $aMatches; + $b = substr_count($this->sSelector, '#'); + $c = preg_match_all(self::NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX, $this->sSelector, $aMatches); + $d = preg_match_all(self::ELEMENTS_AND_PSEUDO_ELEMENTS_RX, $this->sSelector, $aMatches); + $this->iSpecificity = ($a*1000) + ($b*100) + ($c*10) + $d; + } + return $this->iSpecificity; + } +} + diff --git a/lib/CSSRule.php b/lib/CSSRule.php new file mode 100644 index 00000000..d6ab6ac4 --- /dev/null +++ b/lib/CSSRule.php @@ -0,0 +1,135 @@ +sRule = $sRule; + $this->mValue = null; + $this->bIsImportant = false; + } + + public function setRule($sRule) { + $this->sRule = $sRule; + } + + public function getRule() { + return $this->sRule; + } + + public function getValue() { + return $this->mValue; + } + + public function setValue($mValue) { + $this->mValue = $mValue; + } + + /** + * @deprecated Old-Style 2-dimensional array given. Retained for (some) backwards-compatibility. Use setValue() instead and wrapp the value inside a CSSRuleValueList if necessary. + */ + public function setValues($aSpaceSeparatedValues) { + $oSpaceSeparatedList = null; + if(count($aSpaceSeparatedValues) > 1) { + $oSpaceSeparatedList = new CSSRuleValueList(' '); + } + foreach($aSpaceSeparatedValues as $aCommaSeparatedValues) { + $oCommaSeparatedList = null; + if(count($aCommaSeparatedValues) > 1) { + $oCommaSeparatedList = new CSSRuleValueList(','); + } + foreach($aCommaSeparatedValues as $mValue) { + if(!$oSpaceSeparatedList && !$oCommaSeparatedList) { + $this->mValue = $mValue; + return $mValue; + } + if($oCommaSeparatedList) { + $oCommaSeparatedList->addListComponent($mValue); + } else { + $oSpaceSeparatedList->addListComponent($mValue); + } + } + if(!$oSpaceSeparatedList) { + $this->mValue = $oCommaSeparatedList; + return $oCommaSeparatedList; + } else { + $oSpaceSeparatedList->addListComponent($oCommaSeparatedList); + } + } + $this->mValue = $oSpaceSeparatedList; + return $oSpaceSeparatedList; + } + + /** + * @deprecated Old-Style 2-dimensional array returned. Retained for (some) backwards-compatibility. Use getValue() instead and check for the existance of a (nested set of) CSSValueList object(s). + */ + public function getValues() { + if(!$this->mValue instanceof CSSRuleValueList) { + return array(array($this->mValue)); + } + if($this->mValue->getListSeparator() === ',') { + return array($this->mValue->getListComponents()); + } + $aResult = array(); + foreach($this->mValue->getListComponents() as $mValue) { + if(!$mValue instanceof CSSRuleValueList || $mValue->getListSeparator() !== ',') { + $aResult[] = array($mValue); + continue; + } + if($this->mValue->getListSeparator() === ' ' || count($aResult) === 0) { + $aResult[] = array(); + } + foreach($mValue->getListComponents() as $mValue) { + $aResult[count($aResult)-1][] = $mValue; + } + } + return $aResult; + } + + /** + * Adds a value to the existing value. Value will be appended if a CSSRuleValueList exists of the given type. Otherwise, the existing value will be wrapped by one. + */ + public function addValue($mValue, $sType = ' ') { + if(!is_array($mValue)) { + $mValue = array($mValue); + } + if(!$this->mValue instanceof CSSRuleValueList || $this->mValue->getListSeparator() !== $sType) { + $mCurrentValue = $this->mValue; + $this->mValue = new CSSRuleValueList($sType); + if($mCurrentValue) { + $this->mValue->addListComponent($mCurrentValue); + } + } + foreach($mValue as $mValueItem) { + $this->mValue->addListComponent($mValueItem); + } + } + + public function setIsImportant($bIsImportant) { + $this->bIsImportant = $bIsImportant; + } + + public function getIsImportant() { + return $this->bIsImportant; + } + + public function __toString() { + $sResult = "{$this->sRule}: "; + if($this->mValue instanceof CSSValue) { //Can also be a CSSValueList + $sResult .= $this->mValue->__toString(); + } else { + $sResult .= $this->mValue; + } + if($this->bIsImportant) { + $sResult .= ' !important'; + } + $sResult .= ';'; + return $sResult; + } +} diff --git a/lib/CSSRuleSet.php b/lib/CSSRuleSet.php new file mode 100644 index 00000000..b1b5b1f4 --- /dev/null +++ b/lib/CSSRuleSet.php @@ -0,0 +1,657 @@ +aRules = array(); + } + + public function addRule(CSSRule $oRule) { + $this->aRules[$oRule->getRule()] = $oRule; + } + + /** + * Returns all rules matching the given pattern + * @param (null|string|CSSRule) $mRule 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 excluded. passing a CSSRule behaves like calling getRules($mRule->getRule()). + * @example $oRuleSet->getRules('font-') //returns an array of all rules either beginning with font- or matching font. + * @example $oRuleSet->getRules('font') //returns array('font' => $oRule) or array(). + */ + public function getRules($mRule = null) { + if($mRule === null) { + return $this->aRules; + } + $aResult = array(); + if($mRule instanceof CSSRule) { + $mRule = $mRule->getRule(); + } + if(strrpos($mRule, '-')===strlen($mRule)-strlen('-')) { + $sStart = substr($mRule, 0, -1); + foreach($this->aRules as $oRule) { + if($oRule->getRule() === $sStart || strpos($oRule->getRule(), $mRule) === 0) { + $aResult[$oRule->getRule()] = $this->aRules[$oRule->getRule()]; + } + } + } else if(isset($this->aRules[$mRule])) { + $aResult[$mRule] = $this->aRules[$mRule]; + } + return $aResult; + } + + public function removeRule($mRule) { + if($mRule instanceof CSSRule) { + $mRule = $mRule->getRule(); + } + if(strrpos($mRule, '-')===strlen($mRule)-strlen('-')) { + $sStart = substr($mRule, 0, -1); + foreach($this->aRules as $oRule) { + if($oRule->getRule() === $sStart || strpos($oRule->getRule(), $mRule) === 0) { + unset($this->aRules[$oRule->getRule()]); + } + } + } else if(isset($this->aRules[$mRule])) { + unset($this->aRules[$mRule]); + } + } + + public function __toString() { + $sResult = ''; + foreach($this->aRules as $oRule) { + $sResult .= $oRule->__toString(); + } + return $sResult; + } +} + +/** +* A CSSRuleSet constructed by an unknown @-rule. @font-face rules are rendered into CSSAtRule objects. +*/ +class CSSAtRule extends CSSRuleSet { + private $sType; + + public function __construct($sType) { + parent::__construct(); + $this->sType = $sType; + } + + public function getType() { + return $this->sType; + } + + public function __toString() { + $sResult = "@{$this->sType} {"; + $sResult .= parent::__toString(); + $sResult .= '}'; + return $sResult; + } +} + +/** +* Declaration blocks are the parts of a css file which denote the rules belonging to a selector. +* Declaration blocks usually appear directly inside a CSSDocument or another CSSList (mostly a CSSMediaQuery). +*/ +class CSSDeclarationBlock extends CSSRuleSet { + + private $aSelectors; + + public function __construct() { + parent::__construct(); + $this->aSelectors = array(); + } + + public function setSelectors($mSelector) { + if(is_array($mSelector)) { + $this->aSelectors = $mSelector; + } else { + $this->aSelectors = explode(',', $mSelector); + } + foreach($this->aSelectors as $iKey => $mSelector) { + if(!($mSelector instanceof CSSSelector)) { + $this->aSelectors[$iKey] = new CSSSelector($mSelector); + } + } + } + + /** + * @deprecated use getSelectors() + */ + public function getSelector() { + return $this->getSelectors(); + } + + /** + * @deprecated use setSelectors() + */ + public function setSelector($mSelector) { + $this->setSelectors($mSelector); + } + + public function getSelectors() { + return $this->aSelectors; + } + + /** + * Split shorthand declarations (e.g. +margin+ or +font+) into their constituent parts. + **/ + public function expandShorthands() { + // border must be expanded before dimensions + $this->expandBorderShorthand(); + $this->expandDimensionsShorthand(); + $this->expandFontShorthand(); + $this->expandBackgroundShorthand(); + $this->expandListStyleShorthand(); + } + + /** + * Create shorthand declarations (e.g. +margin+ or +font+) whenever possible. + **/ + public function createShorthands() { + $this->createBackgroundShorthand(); + $this->createDimensionsShorthand(); + // border must be shortened after dimensions + $this->createBorderShorthand(); + $this->createFontShorthand(); + $this->createListStyleShorthand(); + } + + /** + * Split shorthand border declarations (e.g. border: 1px red;) + * Additional splitting happens in expandDimensionsShorthand + * Multiple borders are not yet supported as of CSS3 + **/ + public function expandBorderShorthand() { + $aBorderRules = array( + 'border', 'border-left', 'border-right', 'border-top', 'border-bottom' + ); + $aBorderSizes = array( + 'thin', 'medium', 'thick' + ); + $aRules = $this->getRules(); + foreach ($aBorderRules as $sBorderRule) { + if(!isset($aRules[$sBorderRule])) continue; + $oRule = $aRules[$sBorderRule]; + $mRuleValue = $oRule->getValue(); + $aValues = array(); + if(!$mRuleValue instanceof CSSRuleValueList) { + $aValues[] = $mRuleValue; + } else { + $aValues = $mRuleValue->getListComponents(); + } + foreach ($aValues as $mValue) { + if($mValue instanceof CSSValue) { + $mNewValue = clone $mValue; + } else { + $mNewValue = $mValue; + } + if($mValue instanceof CSSSize) { + $sNewRuleName = $sBorderRule."-width"; + } else if($mValue instanceof CSSColor) { + $sNewRuleName = $sBorderRule."-color"; + } else { + if(in_array($mValue, $aBorderSizes)) { + $sNewRuleName = $sBorderRule."-width"; + } else/* if(in_array($mValue, $aBorderStyles))*/ { + $sNewRuleName = $sBorderRule."-style"; + } + } + $oNewRule = new CSSRule($sNewRuleName); + $oNewRule->setIsImportant($oRule->getIsImportant()); + $oNewRule->addValue(array($mNewValue)); + $this->addRule($oNewRule); + } + $this->removeRule($sBorderRule); + } + } + + /** + * Split shorthand dimensional declarations (e.g. margin: 0px auto;) + * into their constituent parts. + * Handles margin, padding, border-color, border-style and border-width. + **/ + public function expandDimensionsShorthand() { + $aExpansions = array( + 'margin' => 'margin-%s', + 'padding' => 'padding-%s', + 'border-color' => 'border-%s-color', + 'border-style' => 'border-%s-style', + 'border-width' => 'border-%s-width' + ); + $aRules = $this->getRules(); + foreach ($aExpansions as $sProperty => $sExpanded) { + if(!isset($aRules[$sProperty])) continue; + $oRule = $aRules[$sProperty]; + $mRuleValue = $oRule->getValue(); + $aValues = array(); + if(!$mRuleValue instanceof CSSRuleValueList) { + $aValues[] = $mRuleValue; + } else { + $aValues = $mRuleValue->getListComponents(); + } + $top = $right = $bottom = $left = null; + switch(count($aValues)) { + case 1: + $top = $right = $bottom = $left = $aValues[0]; + break; + case 2: + $top = $bottom = $aValues[0]; + $left = $right = $aValues[1]; + break; + case 3: + $top = $aValues[0]; + $left = $right = $aValues[1]; + $bottom = $aValues[2]; + break; + case 4: + $top = $aValues[0]; + $right = $aValues[1]; + $bottom = $aValues[2]; + $left = $aValues[3]; + break; + } + foreach(array('top', 'right', 'bottom', 'left') as $sPosition) { + $oNewRule = new CSSRule(sprintf($sExpanded, $sPosition)); + $oNewRule->setIsImportant($oRule->getIsImportant()); + $oNewRule->addValue(${$sPosition}); + $this->addRule($oNewRule); + } + $this->removeRule($sProperty); + } + } + + /** + * Convert shorthand font declarations + * (e.g. font: 300 italic 11px/14px verdana, helvetica, sans-serif;) + * into their constituent parts. + **/ + public function expandFontShorthand() { + $aRules = $this->getRules(); + if(!isset($aRules['font'])) return; + $oRule = $aRules['font']; + // reset properties to 'normal' per http://www.w3.org/TR/CSS21/fonts.html#font-shorthand + $aFontProperties = array( + 'font-style' => 'normal', + 'font-variant' => 'normal', + 'font-weight' => 'normal', + 'font-size' => 'normal', + 'line-height' => 'normal' + ); + $mRuleValue = $oRule->getValue(); + $aValues = array(); + if(!$mRuleValue instanceof CSSRuleValueList) { + $aValues[] = $mRuleValue; + } else { + $aValues = $mRuleValue->getListComponents(); + } + foreach($aValues as $mValue) { + if(!$mValue instanceof CSSValue) { + $mValue = mb_strtolower($mValue); + } + if(in_array($mValue, array('normal', 'inherit'))) { + foreach (array('font-style', 'font-weight', 'font-variant') as $sProperty) { + if(!isset($aFontProperties[$sProperty])) { + $aFontProperties[$sProperty] = $mValue; + } + } + } else if(in_array($mValue, array('italic', 'oblique'))) { + $aFontProperties['font-style'] = $mValue; + } else if($mValue == 'small-caps') { + $aFontProperties['font-variant'] = $mValue; + } else if( + in_array($mValue, array('bold', 'bolder', 'lighter')) + || ($mValue instanceof CSSSize + && in_array($mValue->getSize(), range(100, 900, 100))) + ) { + $aFontProperties['font-weight'] = $mValue; + } else if($mValue instanceof CSSRuleValueList && $mValue->getListSeparator() == '/') { + list($oSize, $oHeight) = $mValue->getListComponents(); + $aFontProperties['font-size'] = $oSize; + $aFontProperties['line-height'] = $oHeight; + } else if($mValue instanceof CSSSize && $mValue->getUnit() !== null) { + $aFontProperties['font-size'] = $mValue; + } else { + $aFontProperties['font-family'] = $mValue; + } + } + foreach ($aFontProperties as $sProperty => $mValue) { + $oNewRule = new CSSRule($sProperty); + $oNewRule->addValue($mValue); + $oNewRule->setIsImportant($oRule->getIsImportant()); + $this->addRule($oNewRule); + } + $this->removeRule('font'); + } + + /* + * Convert shorthand background declarations + * (e.g. background: url("chess.png") gray 50% repeat fixed;) + * into their constituent parts. + * @see http://www.w3.org/TR/CSS21/colors.html#propdef-background + **/ + public function expandBackgroundShorthand() { + $aRules = $this->getRules(); + if(!isset($aRules['background'])) return; + $oRule = $aRules['background']; + $aBgProperties = array( + 'background-color' => array('transparent'), 'background-image' => array('none'), + 'background-repeat' => array('repeat'), 'background-attachment' => array('scroll'), + 'background-position' => array(new CSSSize(0, '%'), new CSSSize(0, '%')) + ); + $mRuleValue = $oRule->getValue(); + $aValues = array(); + if(!$mRuleValue instanceof CSSRuleValueList) { + $aValues[] = $mRuleValue; + } else { + $aValues = $mRuleValue->getListComponents(); + } + if(count($aValues) == 1 && $aValues[0] == 'inherit') { + foreach ($aBgProperties as $sProperty => $mValue) { + $oNewRule = new CSSRule($sProperty); + $oNewRule->addValue('inherit'); + $oNewRule->setIsImportant($oRule->getIsImportant()); + $this->addRule($oNewRule); + } + $this->removeRule('background'); + return; + } + $iNumBgPos = 0; + foreach($aValues as $mValue) { + if(!$mValue instanceof CSSValue) { + $mValue = mb_strtolower($mValue); + } + if ($mValue instanceof CSSURL) { + $aBgProperties['background-image'] = $mValue; + } else if($mValue instanceof CSSColor) { + $aBgProperties['background-color'] = $mValue; + } else if(in_array($mValue, array('scroll', 'fixed'))) { + $aBgProperties['background-attachment'] = $mValue; + } else if(in_array($mValue, array('repeat','no-repeat', 'repeat-x', 'repeat-y'))) { + $aBgProperties['background-repeat'] = $mValue; + } else if(in_array($mValue, array('left','center','right','top','bottom')) + || $mValue instanceof CSSSize + ){ + if($iNumBgPos == 0) { + $aBgProperties['background-position'][0] = $mValue; + $aBgProperties['background-position'][1] = 'center'; + } else { + $aBgProperties['background-position'][$iNumBgPos] = $mValue; + } + $iNumBgPos++; + } + } + foreach ($aBgProperties as $sProperty => $mValue) { + $oNewRule = new CSSRule($sProperty); + $oNewRule->setIsImportant($oRule->getIsImportant()); + $oNewRule->addValue($mValue); + $this->addRule($oNewRule); + } + $this->removeRule('background'); + } + + public function expandListStyleShorthand() { + $aListProperties = array( + 'list-style-type' => 'disc', + 'list-style-position' => 'outside', + 'list-style-image' => 'none' + ); + $aListStyleTypes = array( + 'none', 'disc', 'circle', 'square', 'decimal-leading-zero', 'decimal', + 'lower-roman', 'upper-roman', 'lower-greek', 'lower-alpha', 'lower-latin', + 'upper-alpha', 'upper-latin', 'hebrew', 'armenian', 'georgian', 'cjk-ideographic', + 'hiragana', 'hira-gana-iroha', 'katakana-iroha', 'katakana' + ); + $aListStylePositions = array( + 'inside', 'outside' + ); + $aRules = $this->getRules(); + if(!isset($aRules['list-style'])) return; + $oRule = $aRules['list-style']; + $mRuleValue = $oRule->getValue(); + $aValues = array(); + if(!$mRuleValue instanceof CSSRuleValueList) { + $aValues[] = $mRuleValue; + } else { + $aValues = $mRuleValue->getListComponents(); + } + if(count($aValues) == 1 && $aValues[0] == 'inherit') { + foreach ($aListProperties as $sProperty => $mValue) { + $oNewRule = new CSSRule($sProperty); + $oNewRule->addValue('inherit'); + $oNewRule->setIsImportant($oRule->getIsImportant()); + $this->addRule($oNewRule); + } + $this->removeRule('list-style'); + return; + } + foreach($aValues as $mValue) { + if(!$mValue instanceof CSSValue) { + $mValue = mb_strtolower($mValue); + } + if($mValue instanceof CSSUrl) { + $aListProperties['list-style-image'] = $mValue; + } else if(in_array($mValue, $aListStyleTypes)) { + $aListProperties['list-style-types'] = $mValue; + } else if(in_array($mValue, $aListStylePositions)) { + $aListProperties['list-style-position'] = $mValue; + } + } + foreach ($aListProperties as $sProperty => $mValue) { + $oNewRule = new CSSRule($sProperty); + $oNewRule->setIsImportant($oRule->getIsImportant()); + $oNewRule->addValue($mValue); + $this->addRule($oNewRule); + } + $this->removeRule('list-style'); + } + + public function createShorthandProperties(array $aProperties, $sShorthand) { + $aRules = $this->getRules(); + $aNewValues = array(); + foreach($aProperties as $sProperty) { + if(!isset($aRules[$sProperty])) continue; + $oRule = $aRules[$sProperty]; + if(!$oRule->getIsImportant()) { + $mRuleValue = $oRule->getValue(); + $aValues = array(); + if(!$mRuleValue instanceof CSSRuleValueList) { + $aValues[] = $mRuleValue; + } else { + $aValues = $mRuleValue->getListComponents(); + } + foreach($aValues as $mValue) { + $aNewValues[] = $mValue; + } + $this->removeRule($sProperty); + } + } + if(count($aNewValues)) { + $oNewRule = new CSSRule($sShorthand); + foreach($aNewValues as $mValue) { + $oNewRule->addValue($mValue); + } + $this->addRule($oNewRule); + } + } + + public function createBackgroundShorthand() { + $aProperties = array( + 'background-color', 'background-image', 'background-repeat', + 'background-position', 'background-attachment' + ); + $this->createShorthandProperties($aProperties, 'background'); + } + + public function createListStyleShorthand() { + $aProperties = array( + 'list-style-type', 'list-style-position', 'list-style-image' + ); + $this->createShorthandProperties($aProperties, 'list-style'); + } + + /** + * Combine border-color, border-style and border-width into border + * Should be run after create_dimensions_shorthand! + **/ + public function createBorderShorthand() { + $aProperties = array( + 'border-width', 'border-style', 'border-color' + ); + $this->createShorthandProperties($aProperties, 'border'); + } + + /* + * Looks for long format CSS dimensional properties + * (margin, padding, border-color, border-style and border-width) + * and converts them into shorthand CSS properties. + **/ + public function createDimensionsShorthand() { + $aPositions = array('top', 'right', 'bottom', 'left'); + $aExpansions = array( + 'margin' => 'margin-%s', + 'padding' => 'padding-%s', + 'border-color' => 'border-%s-color', + 'border-style' => 'border-%s-style', + 'border-width' => 'border-%s-width' + ); + $aRules = $this->getRules(); + foreach ($aExpansions as $sProperty => $sExpanded) { + $aFoldable = array(); + foreach($aRules as $sRuleName => $oRule) { + foreach ($aPositions as $sPosition) { + if($sRuleName == sprintf($sExpanded, $sPosition)) { + $aFoldable[$sRuleName] = $oRule; + } + } + } + // All four dimensions must be present + if(count($aFoldable) == 4) { + $aValues = array(); + foreach ($aPositions as $sPosition) { + $oRule = $aRules[sprintf($sExpanded, $sPosition)]; + $mRuleValue = $oRule->getValue(); + $aRuleValues = array(); + if(!$mRuleValue instanceof CSSRuleValueList) { + $aRuleValues[] = $mRuleValue; + } else { + $aRuleValues = $mRuleValue->getListComponents(); + } + $aValues[$sPosition] = $aRuleValues; + } + $oNewRule = new CSSRule($sProperty); + if((string)$aValues['left'][0] == (string)$aValues['right'][0]) { + if((string)$aValues['top'][0] == (string)$aValues['bottom'][0]) { + if((string)$aValues['top'][0] == (string)$aValues['left'][0]) { + // All 4 sides are equal + $oNewRule->addValue($aValues['top']); + } else { + // Top and bottom are equal, left and right are equal + $oNewRule->addValue($aValues['top']); + $oNewRule->addValue($aValues['left']); + } + } else { + // Only left and right are equal + $oNewRule->addValue($aValues['top']); + $oNewRule->addValue($aValues['left']); + $oNewRule->addValue($aValues['bottom']); + } + } else { + // No sides are equal + $oNewRule->addValue($aValues['top']); + $oNewRule->addValue($aValues['left']); + $oNewRule->addValue($aValues['bottom']); + $oNewRule->addValue($aValues['right']); + } + $this->addRule($oNewRule); + foreach ($aPositions as $sPosition) + { + $this->removeRule(sprintf($sExpanded, $sPosition)); + } + } + } + } + + /** + * Looks for long format CSS font properties (e.g. font-weight) and + * tries to convert them into a shorthand CSS font property. + * At least font-size AND font-family must be present in order to create a shorthand declaration. + **/ + public function createFontShorthand() { + $aFontProperties = array( + 'font-style', 'font-variant', 'font-weight', 'font-size', 'line-height', 'font-family' + ); + $aRules = $this->getRules(); + if(!isset($aRules['font-size']) || !isset($aRules['font-family'])) { + return; + } + $oNewRule = new CSSRule('font'); + foreach(array('font-style', 'font-variant', 'font-weight') as $sProperty) { + if(isset($aRules[$sProperty])) { + $oRule = $aRules[$sProperty]; + $mRuleValue = $oRule->getValue(); + $aValues = array(); + if(!$mRuleValue instanceof CSSRuleValueList) { + $aValues[] = $mRuleValue; + } else { + $aValues = $mRuleValue->getListComponents(); + } + if($aValues[0] !== 'normal') { + $oNewRule->addValue($aValues[0]); + } + } + } + // Get the font-size value + $oRule = $aRules['font-size']; + $mRuleValue = $oRule->getValue(); + $aFSValues = array(); + if(!$mRuleValue instanceof CSSRuleValueList) { + $aFSValues[] = $mRuleValue; + } else { + $aFSValues = $mRuleValue->getListComponents(); + } + // But wait to know if we have line-height to add it + if(isset($aRules['line-height'])) { + $oRule = $aRules['line-height']; + $mRuleValue = $oRule->getValue(); + $aLHValues = array(); + if(!$mRuleValue instanceof CSSRuleValueList) { + $aLHValues[] = $mRuleValue; + } else { + $aLHValues = $mRuleValue->getListComponents(); + } + if($aLHValues[0] !== 'normal') { + $val = new CSSRuleValueList('/'); + $val->addListComponent($aFSValues[0]); + $val->addListComponent($aLHValues[0]); + $oNewRule->addValue($val); + } + } else { + $oNewRule->addValue($aFSValues[0]); + } + $oRule = $aRules['font-family']; + $mRuleValue = $oRule->getValue(); + $aFFValues = array(); + if(!$mRuleValue instanceof CSSRuleValueList) { + $aFFValues[] = $mRuleValue; + } else { + $aFFValues = $mRuleValue->getListComponents(); + } + $oFFValue = new CSSRuleValueList(','); + $oFFValue->setListComponents($aFFValues); + $oNewRule->addValue($oFFValue); + + $this->addRule($oNewRule); + foreach ($aFontProperties as $sProperty) { + $this->removeRule($sProperty); + } + } + + public function __toString() { + $sResult = implode(', ', $this->aSelectors).' {'; + $sResult .= parent::__toString(); + $sResult .= '}'."\n"; + return $sResult; + } +} diff --git a/lib/CSSValue.php b/lib/CSSValue.php new file mode 100644 index 00000000..6dc3fb9c --- /dev/null +++ b/lib/CSSValue.php @@ -0,0 +1,110 @@ +fSize = floatval($fSize); + $this->sUnit = $sUnit; + $this->bIsColorComponent = $bIsColorComponent; + } + + public function setUnit($sUnit) { + $this->sUnit = $sUnit; + } + + public function getUnit() { + return $this->sUnit; + } + + public function setSize($fSize) { + $this->fSize = floatval($fSize); + } + + public function getSize() { + return $this->fSize; + } + + public function isColorComponent() { + return $this->bIsColorComponent; + } + + /** + * Returns whether the number stored in this CSSSize really represents a size (as in a length of something on screen). + * @return false if the unit an angle, a duration, a frequency or the number is a component in a CSSColor object. + */ + public function isSize() { + $aNonSizeUnits = array('deg', 'grad', 'rad', 'turns', 's', 'ms', 'Hz', 'kHz'); + if(in_array($this->sUnit, $aNonSizeUnits)) { + return false; + } + return !$this->isColorComponent(); + } + + public function isRelative() { + if($this->sUnit === '%' || $this->sUnit === 'em' || $this->sUnit === 'ex') { + return true; + } + if($this->sUnit === null && $this->fSize != 0) { + return true; + } + return false; + } + + public function __toString() { + return $this->fSize.($this->sUnit === null ? '' : $this->sUnit); + } +} + +class CSSString extends CSSPrimitiveValue { + private $sString; + + public function __construct($sString) { + $this->sString = $sString; + } + + public function setString($sString) { + $this->sString = $sString; + } + + public function getString() { + return $this->sString; + } + + public function __toString() { + $sString = addslashes($this->sString); + $sString = str_replace("\n", '\A', $sString); + return '"'.$sString.'"'; + } +} + +class CSSURL extends CSSPrimitiveValue { + private $oURL; + + public function __construct(CSSString $oURL) { + $this->oURL = $oURL; + } + + public function setURL(CSSString $oURL) { + $this->oURL = $oURL; + } + + public function getURL() { + return $this->oURL; + } + + public function __toString() { + return "url({$this->oURL->__toString()})"; + } +} + diff --git a/lib/CSSValueList.php b/lib/CSSValueList.php new file mode 100644 index 00000000..35269e23 --- /dev/null +++ b/lib/CSSValueList.php @@ -0,0 +1,92 @@ +getListSeparator() === $sSeparator) { + $aComponents = $aComponents->getListComponents(); + } else if(!is_array($aComponents)) { + $aComponents = array($aComponents); + } + $this->aComponents = $aComponents; + $this->sSeparator = $sSeparator; + } + + public function addListComponent($mComponent) { + $this->aComponents[] = $mComponent; + } + + public function getListComponents() { + return $this->aComponents; + } + + public function setListComponents($aComponents) { + $this->aComponents = $aComponents; + } + + public function getListSeparator() { + return $this->sSeparator; + } + + public function setListSeparator($sSeparator) { + $this->sSeparator = $sSeparator; + } + + function __toString() { + return implode($this->sSeparator, $this->aComponents); + } +} + +class CSSRuleValueList extends CSSValueList { + public function __construct($sSeparator = ',') { + parent::__construct(array(), $sSeparator); + } +} + +class CSSFunction extends CSSValueList { + private $sName; + public function __construct($sName, $aArguments) { + $this->sName = $sName; + parent::__construct($aArguments); + } + + public function getName() { + return $this->sName; + } + + public function setName($sName) { + $this->sName = $sName; + } + + public function getArguments() { + return $this->aComponents; + } + + public function __toString() { + $aArguments = parent::__toString(); + return "{$this->sName}({$aArguments})"; + } +} + +class CSSColor extends CSSFunction { + public function __construct($aColor) { + parent::__construct(implode('', array_keys($aColor)), $aColor); + } + + public function getColor() { + return $this->aComponents; + } + + public function setColor($aColor) { + $this->setName(implode('', array_keys($aColor))); + $this->aComponents = $aColor; + } + + public function getColorDescription() { + return $this->getName(); + } +} + + diff --git a/lib/Sabberworm/CSS/CSSList/CSSList.php b/lib/Sabberworm/CSS/CSSList/CSSList.php deleted file mode 100644 index 950588b7..00000000 --- a/lib/Sabberworm/CSS/CSSList/CSSList.php +++ /dev/null @@ -1,135 +0,0 @@ -aContents = array(); - } - - public function append($oItem) { - $this->aContents[] = $oItem; - } - - /** - * 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) - */ - public function remove($oItemToRemove) { - $iKey = array_search($oItemToRemove, $this->aContents, true); - if ($iKey !== false) { - unset($this->aContents[$iKey]); - } - } - - 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)) { - $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; - } - } - } - } - - public function __toString() { - $sResult = ''; - foreach ($this->aContents as $oContent) { - $sResult .= $oContent->__toString(); - } - return $sResult; - } - - public function getContents() { - return $this->aContents; - } - - protected function allDeclarationBlocks(&$aResult) { - foreach ($this->aContents as $mContent) { - if ($mContent instanceof DeclarationBlock) { - $aResult[] = $mContent; - } else if ($mContent instanceof CSSList) { - $mContent->allDeclarationBlocks($aResult); - } - } - } - - protected function allRuleSets(&$aResult) { - foreach ($this->aContents as $mContent) { - if ($mContent instanceof RuleSet) { - $aResult[] = $mContent; - } else if ($mContent instanceof CSSList) { - $mContent->allRuleSets($aResult); - } - } - } - - protected function allValues($oElement, &$aResult, $sSearchString = null, $bSearchInFunctionArguments = false) { - if ($oElement instanceof CSSList) { - foreach ($oElement->getContents() as $oContent) { - $this->allValues($oContent, $aResult, $sSearchString, $bSearchInFunctionArguments); - } - } else if ($oElement instanceof RuleSet) { - foreach ($oElement->getRules($sSearchString) as $oRule) { - $this->allValues($oRule, $aResult, $sSearchString, $bSearchInFunctionArguments); - } - } else if ($oElement instanceof Rule) { - $this->allValues($oElement->getValue(), $aResult, $sSearchString, $bSearchInFunctionArguments); - } else if ($oElement instanceof ValueList) { - if ($bSearchInFunctionArguments || !($oElement instanceof CSSFunction)) { - foreach ($oElement->getListComponents() as $mComponent) { - $this->allValues($mComponent, $aResult, $sSearchString, $bSearchInFunctionArguments); - } - } - } else { - //Non-List Value or String (CSS identifier) - $aResult[] = $oElement; - } - } - - protected function allSelectors(&$aResult, $sSpecificitySearch = null) { - foreach ($this->getAllDeclarationBlocks() as $oBlock) { - foreach ($oBlock->getSelectors() as $oSelector) { - if ($sSpecificitySearch === null) { - $aResult[] = $oSelector; - } else { - $sComparison = "\$bRes = {$oSelector->getSpecificity()} $sSpecificitySearch;"; - eval($sComparison); - if ($bRes) { - $aResult[] = $oSelector; - } - } - } - } - } - -} diff --git a/lib/Sabberworm/CSS/CSSList/Document.php b/lib/Sabberworm/CSS/CSSList/Document.php deleted file mode 100644 index 28ad5aa4..00000000 --- a/lib/Sabberworm/CSS/CSSList/Document.php +++ /dev/null @@ -1,87 +0,0 @@ -allDeclarationBlocks($aResult); - return $aResult; - } - - /** - * @deprecated use getAllDeclarationBlocks() - */ - public function getAllSelectors() { - return $this->getAllDeclarationBlocks(); - } - - /** - * Returns all RuleSet objects found recursively in the tree. - */ - public function getAllRuleSets() { - $aResult = array(); - $this->allRuleSets($aResult); - return $aResult; - } - - /** - * Returns all Value objects found recursively in the tree. - * @param (object|string) $mElement the CSSList or RuleSet to start the search from (defaults to the whole document). If a string is given, it is used as rule name filter (@see{RuleSet->getRules()}). - * @param (bool) $bSearchInFunctionArguments whether to also return Value objects used as Function arguments. - */ - public function getAllValues($mElement = null, $bSearchInFunctionArguments = false) { - $sSearchString = null; - if ($mElement === null) { - $mElement = $this; - } else if (is_string($mElement)) { - $sSearchString = $mElement; - $mElement = $this; - } - $aResult = array(); - $this->allValues($mElement, $aResult, $sSearchString, $bSearchInFunctionArguments); - return $aResult; - } - - /** - * Returns all Selector objects 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 $sSpecificitySearch An optional filter by specificity. May contain a comparison operator and a number or just a number (defaults to "=="). - * @example getSelectorsBySpecificity('>= 100') - */ - public function getSelectorsBySpecificity($sSpecificitySearch = null) { - if (is_numeric($sSpecificitySearch) || is_numeric($sSpecificitySearch[0])) { - $sSpecificitySearch = "== $sSpecificitySearch"; - } - $aResult = array(); - $this->allSelectors($aResult, $sSpecificitySearch); - return $aResult; - } - - /** - * Expands all shorthand properties to their long value - */ - public function expandShorthands() { - foreach ($this->getAllDeclarationBlocks() as $oDeclaration) { - $oDeclaration->expandShorthands(); - } - } - - /* - * Create shorthands properties whenever possible - */ - - public function createShorthands() { - foreach ($this->getAllDeclarationBlocks() as $oDeclaration) { - $oDeclaration->createShorthands(); - } - } - -} \ No newline at end of file diff --git a/lib/Sabberworm/CSS/CSSList/MediaQuery.php b/lib/Sabberworm/CSS/CSSList/MediaQuery.php deleted file mode 100644 index 270f3e60..00000000 --- a/lib/Sabberworm/CSS/CSSList/MediaQuery.php +++ /dev/null @@ -1,32 +0,0 @@ -sQuery = null; - } - - public function setQuery($sQuery) { - $this->sQuery = $sQuery; - } - - public function getQuery() { - return $this->sQuery; - } - - public function __toString() { - $sResult = "@media {$this->sQuery} {"; - $sResult .= parent::__toString(); - $sResult .= '}'; - return $sResult; - } - -} diff --git a/lib/Sabberworm/CSS/Property/Charset.php b/lib/Sabberworm/CSS/Property/Charset.php deleted file mode 100644 index 5ecdc1b6..00000000 --- a/lib/Sabberworm/CSS/Property/Charset.php +++ /dev/null @@ -1,32 +0,0 @@ -sCharset = $sCharset; - } - - public function setCharset($sCharset) { - $this->sCharset = $sCharset; - } - - public function getCharset() { - return $this->sCharset; - } - - public function __toString() { - return "@charset {$this->sCharset->__toString()};"; - } - -} \ No newline at end of file diff --git a/lib/Sabberworm/CSS/Property/Import.php b/lib/Sabberworm/CSS/Property/Import.php deleted file mode 100644 index 7a303471..00000000 --- a/lib/Sabberworm/CSS/Property/Import.php +++ /dev/null @@ -1,30 +0,0 @@ -oLocation = $oLocation; - $this->sMediaQuery = $sMediaQuery; - } - - public function setLocation($oLocation) { - $this->oLocation = $oLocation; - } - - public function getLocation() { - return $this->oLocation; - } - - public function __toString() { - return "@import ".$this->oLocation->__toString().($this->sMediaQuery === null ? '' : ' '.$this->sMediaQuery).';'; - } -} \ No newline at end of file diff --git a/lib/Sabberworm/CSS/Property/Selector.php b/lib/Sabberworm/CSS/Property/Selector.php deleted file mode 100644 index 1e4351a8..00000000 --- a/lib/Sabberworm/CSS/Property/Selector.php +++ /dev/null @@ -1,75 +0,0 @@ -\~]+)[\w]+ # elements - | - \:{1,2}( # pseudo-elements - after|before - |first-letter|first-line - |selection - ) - )/ix'; - - private $sSelector; - private $iSpecificity; - - public function __construct($sSelector, $bCalculateSpecificity = false) { - $this->setSelector($sSelector); - if ($bCalculateSpecificity) { - $this->getSpecificity(); - } - } - - public function getSelector() { - return $this->sSelector; - } - - public function setSelector($sSelector) { - $this->sSelector = trim($sSelector); - $this->iSpecificity = null; - } - - public function __toString() { - return $this->getSelector(); - } - - public function getSpecificity() { - if ($this->iSpecificity === null) { - $a = 0; - /// @todo should exclude \# as well as "#" - $aMatches; - $b = substr_count($this->sSelector, '#'); - $c = preg_match_all(self::NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX, $this->sSelector, $aMatches); - $d = preg_match_all(self::ELEMENTS_AND_PSEUDO_ELEMENTS_RX, $this->sSelector, $aMatches); - $this->iSpecificity = ($a * 1000) + ($b * 100) + ($c * 10) + $d; - } - return $this->iSpecificity; - } - -} \ No newline at end of file diff --git a/lib/Sabberworm/CSS/Rule/Rule.php b/lib/Sabberworm/CSS/Rule/Rule.php deleted file mode 100644 index dacc22a3..00000000 --- a/lib/Sabberworm/CSS/Rule/Rule.php +++ /dev/null @@ -1,142 +0,0 @@ -sRule = $sRule; - $this->mValue = null; - $this->bIsImportant = false; - } - - public function setRule($sRule) { - $this->sRule = $sRule; - } - - public function getRule() { - return $this->sRule; - } - - public function getValue() { - return $this->mValue; - } - - public function setValue($mValue) { - $this->mValue = $mValue; - } - - /** - * @deprecated Old-Style 2-dimensional array given. Retained for (some) backwards-compatibility. Use setValue() instead and wrapp the value inside a RuleValueList if necessary. - */ - public function setValues($aSpaceSeparatedValues) { - $oSpaceSeparatedList = null; - if (count($aSpaceSeparatedValues) > 1) { - $oSpaceSeparatedList = new RuleValueList(' '); - } - foreach ($aSpaceSeparatedValues as $aCommaSeparatedValues) { - $oCommaSeparatedList = null; - if (count($aCommaSeparatedValues) > 1) { - $oCommaSeparatedList = new RuleValueList(','); - } - foreach ($aCommaSeparatedValues as $mValue) { - if (!$oSpaceSeparatedList && !$oCommaSeparatedList) { - $this->mValue = $mValue; - return $mValue; - } - if ($oCommaSeparatedList) { - $oCommaSeparatedList->addListComponent($mValue); - } else { - $oSpaceSeparatedList->addListComponent($mValue); - } - } - if (!$oSpaceSeparatedList) { - $this->mValue = $oCommaSeparatedList; - return $oCommaSeparatedList; - } else { - $oSpaceSeparatedList->addListComponent($oCommaSeparatedList); - } - } - $this->mValue = $oSpaceSeparatedList; - return $oSpaceSeparatedList; - } - - /** - * @deprecated Old-Style 2-dimensional array returned. Retained for (some) backwards-compatibility. Use getValue() instead and check for the existance of a (nested set of) ValueList object(s). - */ - public function getValues() { - if (!$this->mValue instanceof RuleValueList) { - return array(array($this->mValue)); - } - if ($this->mValue->getListSeparator() === ',') { - return array($this->mValue->getListComponents()); - } - $aResult = array(); - foreach ($this->mValue->getListComponents() as $mValue) { - if (!$mValue instanceof RuleValueList || $mValue->getListSeparator() !== ',') { - $aResult[] = array($mValue); - continue; - } - if ($this->mValue->getListSeparator() === ' ' || count($aResult) === 0) { - $aResult[] = array(); - } - foreach ($mValue->getListComponents() as $mValue) { - $aResult[count($aResult) - 1][] = $mValue; - } - } - return $aResult; - } - - /** - * 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. - */ - public function addValue($mValue, $sType = ' ') { - if (!is_array($mValue)) { - $mValue = array($mValue); - } - if (!$this->mValue instanceof RuleValueList || $this->mValue->getListSeparator() !== $sType) { - $mCurrentValue = $this->mValue; - $this->mValue = new RuleValueList($sType); - if ($mCurrentValue) { - $this->mValue->addListComponent($mCurrentValue); - } - } - foreach ($mValue as $mValueItem) { - $this->mValue->addListComponent($mValueItem); - } - } - - public function setIsImportant($bIsImportant) { - $this->bIsImportant = $bIsImportant; - } - - public function getIsImportant() { - return $this->bIsImportant; - } - - public function __toString() { - $sResult = "{$this->sRule}: "; - if ($this->mValue instanceof Value) { //Can also be a ValueList - $sResult .= $this->mValue->__toString(); - } else { - $sResult .= $this->mValue; - } - if ($this->bIsImportant) { - $sResult .= ' !important'; - } - $sResult .= ';'; - return $sResult; - } - -} diff --git a/lib/Sabberworm/CSS/RuleSet/AtRule.php b/lib/Sabberworm/CSS/RuleSet/AtRule.php deleted file mode 100644 index 1287ebbc..00000000 --- a/lib/Sabberworm/CSS/RuleSet/AtRule.php +++ /dev/null @@ -1,28 +0,0 @@ -sType = $sType; - } - - public function getType() { - return $this->sType; - } - - public function __toString() { - $sResult = "@{$this->sType} {"; - $sResult .= parent::__toString(); - $sResult .= '}'; - return $sResult; - } - -} \ No newline at end of file diff --git a/lib/Sabberworm/CSS/RuleSet/DeclarationBlock.php b/lib/Sabberworm/CSS/RuleSet/DeclarationBlock.php deleted file mode 100644 index 2c70c855..00000000 --- a/lib/Sabberworm/CSS/RuleSet/DeclarationBlock.php +++ /dev/null @@ -1,585 +0,0 @@ -aSelectors = array(); - } - - public function setSelectors($mSelector) { - if (is_array($mSelector)) { - $this->aSelectors = $mSelector; - } else { - $this->aSelectors = explode(',', $mSelector); - } - foreach ($this->aSelectors as $iKey => $mSelector) { - if (!($mSelector instanceof Selector)) { - $this->aSelectors[$iKey] = new Selector($mSelector); - } - } - } - - /** - * @deprecated use getSelectors() - */ - public function getSelector() { - return $this->getSelectors(); - } - - /** - * @deprecated use setSelectors() - */ - public function setSelector($mSelector) { - $this->setSelectors($mSelector); - } - - public function getSelectors() { - return $this->aSelectors; - } - - /** - * Split shorthand declarations (e.g. +margin+ or +font+) into their constituent parts. - * */ - public function expandShorthands() { - // border must be expanded before dimensions - $this->expandBorderShorthand(); - $this->expandDimensionsShorthand(); - $this->expandFontShorthand(); - $this->expandBackgroundShorthand(); - $this->expandListStyleShorthand(); - } - - /** - * Create shorthand declarations (e.g. +margin+ or +font+) whenever possible. - * */ - public function createShorthands() { - $this->createBackgroundShorthand(); - $this->createDimensionsShorthand(); - // border must be shortened after dimensions - $this->createBorderShorthand(); - $this->createFontShorthand(); - $this->createListStyleShorthand(); - } - - /** - * Split shorthand border declarations (e.g. border: 1px red;) - * Additional splitting happens in expandDimensionsShorthand - * Multiple borders are not yet supported as of 3 - * */ - public function expandBorderShorthand() { - $aBorderRules = array( - 'border', 'border-left', 'border-right', 'border-top', 'border-bottom' - ); - $aBorderSizes = array( - 'thin', 'medium', 'thick' - ); - $aRules = $this->getRules(); - foreach ($aBorderRules as $sBorderRule) { - if (!isset($aRules[$sBorderRule])) - continue; - $oRule = $aRules[$sBorderRule]; - $mRuleValue = $oRule->getValue(); - $aValues = array(); - if (!$mRuleValue instanceof RuleValueList) { - $aValues[] = $mRuleValue; - } else { - $aValues = $mRuleValue->getListComponents(); - } - foreach ($aValues as $mValue) { - if ($mValue instanceof Value) { - $mNewValue = clone $mValue; - } else { - $mNewValue = $mValue; - } - if ($mValue instanceof Size) { - $sNewRuleName = $sBorderRule . "-width"; - } else if ($mValue instanceof Color) { - $sNewRuleName = $sBorderRule . "-color"; - } else { - if (in_array($mValue, $aBorderSizes)) { - $sNewRuleName = $sBorderRule . "-width"; - } else/* if(in_array($mValue, $aBorderStyles)) */ { - $sNewRuleName = $sBorderRule . "-style"; - } - } - $oNewRule = new Rule($sNewRuleName); - $oNewRule->setIsImportant($oRule->getIsImportant()); - $oNewRule->addValue(array($mNewValue)); - $this->addRule($oNewRule); - } - $this->removeRule($sBorderRule); - } - } - - /** - * Split shorthand dimensional declarations (e.g. margin: 0px auto;) - * into their constituent parts. - * Handles margin, padding, border-color, border-style and border-width. - * */ - public function expandDimensionsShorthand() { - $aExpansions = array( - 'margin' => 'margin-%s', - 'padding' => 'padding-%s', - 'border-color' => 'border-%s-color', - 'border-style' => 'border-%s-style', - 'border-width' => 'border-%s-width' - ); - $aRules = $this->getRules(); - foreach ($aExpansions as $sProperty => $sExpanded) { - if (!isset($aRules[$sProperty])) - continue; - $oRule = $aRules[$sProperty]; - $mRuleValue = $oRule->getValue(); - $aValues = array(); - if (!$mRuleValue instanceof RuleValueList) { - $aValues[] = $mRuleValue; - } else { - $aValues = $mRuleValue->getListComponents(); - } - $top = $right = $bottom = $left = null; - switch (count($aValues)) { - case 1: - $top = $right = $bottom = $left = $aValues[0]; - break; - case 2: - $top = $bottom = $aValues[0]; - $left = $right = $aValues[1]; - break; - case 3: - $top = $aValues[0]; - $left = $right = $aValues[1]; - $bottom = $aValues[2]; - break; - case 4: - $top = $aValues[0]; - $right = $aValues[1]; - $bottom = $aValues[2]; - $left = $aValues[3]; - break; - } - foreach (array('top', 'right', 'bottom', 'left') as $sPosition) { - $oNewRule = new Rule(sprintf($sExpanded, $sPosition)); - $oNewRule->setIsImportant($oRule->getIsImportant()); - $oNewRule->addValue(${$sPosition}); - $this->addRule($oNewRule); - } - $this->removeRule($sProperty); - } - } - - /** - * Convert shorthand font declarations - * (e.g. font: 300 italic 11px/14px verdana, helvetica, sans-serif;) - * into their constituent parts. - * */ - public function expandFontShorthand() { - $aRules = $this->getRules(); - if (!isset($aRules['font'])) - return; - $oRule = $aRules['font']; - // reset properties to 'normal' per http://www.w3.org/TR/21/fonts.html#font-shorthand - $aFontProperties = array( - 'font-style' => 'normal', - 'font-variant' => 'normal', - 'font-weight' => 'normal', - 'font-size' => 'normal', - 'line-height' => 'normal' - ); - $mRuleValue = $oRule->getValue(); - $aValues = array(); - if (!$mRuleValue instanceof RuleValueList) { - $aValues[] = $mRuleValue; - } else { - $aValues = $mRuleValue->getListComponents(); - } - foreach ($aValues as $mValue) { - if (!$mValue instanceof Value) { - $mValue = mb_strtolower($mValue); - } - if (in_array($mValue, array('normal', 'inherit'))) { - foreach (array('font-style', 'font-weight', 'font-variant') as $sProperty) { - if (!isset($aFontProperties[$sProperty])) { - $aFontProperties[$sProperty] = $mValue; - } - } - } else if (in_array($mValue, array('italic', 'oblique'))) { - $aFontProperties['font-style'] = $mValue; - } else if ($mValue == 'small-caps') { - $aFontProperties['font-variant'] = $mValue; - } else if ( - in_array($mValue, array('bold', 'bolder', 'lighter')) - || ($mValue instanceof Size - && in_array($mValue->getSize(), range(100, 900, 100))) - ) { - $aFontProperties['font-weight'] = $mValue; - } else if ($mValue instanceof RuleValueList && $mValue->getListSeparator() == '/') { - list($oSize, $oHeight) = $mValue->getListComponents(); - $aFontProperties['font-size'] = $oSize; - $aFontProperties['line-height'] = $oHeight; - } else if ($mValue instanceof Size && $mValue->getUnit() !== null) { - $aFontProperties['font-size'] = $mValue; - } else { - $aFontProperties['font-family'] = $mValue; - } - } - foreach ($aFontProperties as $sProperty => $mValue) { - $oNewRule = new Rule($sProperty); - $oNewRule->addValue($mValue); - $oNewRule->setIsImportant($oRule->getIsImportant()); - $this->addRule($oNewRule); - } - $this->removeRule('font'); - } - - /* - * Convert shorthand background declarations - * (e.g. background: url("chess.png") gray 50% repeat fixed;) - * into their constituent parts. - * @see http://www.w3.org/TR/21/colors.html#propdef-background - * */ - - public function expandBackgroundShorthand() { - $aRules = $this->getRules(); - if (!isset($aRules['background'])) - return; - $oRule = $aRules['background']; - $aBgProperties = array( - 'background-color' => array('transparent'), 'background-image' => array('none'), - 'background-repeat' => array('repeat'), 'background-attachment' => array('scroll'), - 'background-position' => array(new Size(0, '%'), new Size(0, '%')) - ); - $mRuleValue = $oRule->getValue(); - $aValues = array(); - if (!$mRuleValue instanceof RuleValueList) { - $aValues[] = $mRuleValue; - } else { - $aValues = $mRuleValue->getListComponents(); - } - if (count($aValues) == 1 && $aValues[0] == 'inherit') { - foreach ($aBgProperties as $sProperty => $mValue) { - $oNewRule = new Rule($sProperty); - $oNewRule->addValue('inherit'); - $oNewRule->setIsImportant($oRule->getIsImportant()); - $this->addRule($oNewRule); - } - $this->removeRule('background'); - return; - } - $iNumBgPos = 0; - foreach ($aValues as $mValue) { - if (!$mValue instanceof Value) { - $mValue = mb_strtolower($mValue); - } - if ($mValue instanceof URL) { - $aBgProperties['background-image'] = $mValue; - } else if ($mValue instanceof Color) { - $aBgProperties['background-color'] = $mValue; - } else if (in_array($mValue, array('scroll', 'fixed'))) { - $aBgProperties['background-attachment'] = $mValue; - } else if (in_array($mValue, array('repeat', 'no-repeat', 'repeat-x', 'repeat-y'))) { - $aBgProperties['background-repeat'] = $mValue; - } else if (in_array($mValue, array('left', 'center', 'right', 'top', 'bottom')) - || $mValue instanceof Size - ) { - if ($iNumBgPos == 0) { - $aBgProperties['background-position'][0] = $mValue; - $aBgProperties['background-position'][1] = 'center'; - } else { - $aBgProperties['background-position'][$iNumBgPos] = $mValue; - } - $iNumBgPos++; - } - } - foreach ($aBgProperties as $sProperty => $mValue) { - $oNewRule = new Rule($sProperty); - $oNewRule->setIsImportant($oRule->getIsImportant()); - $oNewRule->addValue($mValue); - $this->addRule($oNewRule); - } - $this->removeRule('background'); - } - - public function expandListStyleShorthand() { - $aListProperties = array( - 'list-style-type' => 'disc', - 'list-style-position' => 'outside', - 'list-style-image' => 'none' - ); - $aListStyleTypes = array( - 'none', 'disc', 'circle', 'square', 'decimal-leading-zero', 'decimal', - 'lower-roman', 'upper-roman', 'lower-greek', 'lower-alpha', 'lower-latin', - 'upper-alpha', 'upper-latin', 'hebrew', 'armenian', 'georgian', 'cjk-ideographic', - 'hiragana', 'hira-gana-iroha', 'katakana-iroha', 'katakana' - ); - $aListStylePositions = array( - 'inside', 'outside' - ); - $aRules = $this->getRules(); - if (!isset($aRules['list-style'])) - return; - $oRule = $aRules['list-style']; - $mRuleValue = $oRule->getValue(); - $aValues = array(); - if (!$mRuleValue instanceof RuleValueList) { - $aValues[] = $mRuleValue; - } else { - $aValues = $mRuleValue->getListComponents(); - } - if (count($aValues) == 1 && $aValues[0] == 'inherit') { - foreach ($aListProperties as $sProperty => $mValue) { - $oNewRule = new Rule($sProperty); - $oNewRule->addValue('inherit'); - $oNewRule->setIsImportant($oRule->getIsImportant()); - $this->addRule($oNewRule); - } - $this->removeRule('list-style'); - return; - } - foreach ($aValues as $mValue) { - if (!$mValue instanceof Value) { - $mValue = mb_strtolower($mValue); - } - if ($mValue instanceof Url) { - $aListProperties['list-style-image'] = $mValue; - } else if (in_array($mValue, $aListStyleTypes)) { - $aListProperties['list-style-types'] = $mValue; - } else if (in_array($mValue, $aListStylePositions)) { - $aListProperties['list-style-position'] = $mValue; - } - } - foreach ($aListProperties as $sProperty => $mValue) { - $oNewRule = new Rule($sProperty); - $oNewRule->setIsImportant($oRule->getIsImportant()); - $oNewRule->addValue($mValue); - $this->addRule($oNewRule); - } - $this->removeRule('list-style'); - } - - public function createShorthandProperties(array $aProperties, $sShorthand) { - $aRules = $this->getRules(); - $aNewValues = array(); - foreach ($aProperties as $sProperty) { - if (!isset($aRules[$sProperty])) - continue; - $oRule = $aRules[$sProperty]; - if (!$oRule->getIsImportant()) { - $mRuleValue = $oRule->getValue(); - $aValues = array(); - if (!$mRuleValue instanceof RuleValueList) { - $aValues[] = $mRuleValue; - } else { - $aValues = $mRuleValue->getListComponents(); - } - foreach ($aValues as $mValue) { - $aNewValues[] = $mValue; - } - $this->removeRule($sProperty); - } - } - if (count($aNewValues)) { - $oNewRule = new Rule($sShorthand); - foreach ($aNewValues as $mValue) { - $oNewRule->addValue($mValue); - } - $this->addRule($oNewRule); - } - } - - public function createBackgroundShorthand() { - $aProperties = array( - 'background-color', 'background-image', 'background-repeat', - 'background-position', 'background-attachment' - ); - $this->createShorthandProperties($aProperties, 'background'); - } - - public function createListStyleShorthand() { - $aProperties = array( - 'list-style-type', 'list-style-position', 'list-style-image' - ); - $this->createShorthandProperties($aProperties, 'list-style'); - } - - /** - * Combine border-color, border-style and border-width into border - * Should be run after create_dimensions_shorthand! - * */ - public function createBorderShorthand() { - $aProperties = array( - 'border-width', 'border-style', 'border-color' - ); - $this->createShorthandProperties($aProperties, 'border'); - } - - /* - * Looks for long format CSS dimensional properties - * (margin, padding, border-color, border-style and border-width) - * and converts them into shorthand CSS properties. - * */ - - public function createDimensionsShorthand() { - $aPositions = array('top', 'right', 'bottom', 'left'); - $aExpansions = array( - 'margin' => 'margin-%s', - 'padding' => 'padding-%s', - 'border-color' => 'border-%s-color', - 'border-style' => 'border-%s-style', - 'border-width' => 'border-%s-width' - ); - $aRules = $this->getRules(); - foreach ($aExpansions as $sProperty => $sExpanded) { - $aFoldable = array(); - foreach ($aRules as $sRuleName => $oRule) { - foreach ($aPositions as $sPosition) { - if ($sRuleName == sprintf($sExpanded, $sPosition)) { - $aFoldable[$sRuleName] = $oRule; - } - } - } - // All four dimensions must be present - if (count($aFoldable) == 4) { - $aValues = array(); - foreach ($aPositions as $sPosition) { - $oRule = $aRules[sprintf($sExpanded, $sPosition)]; - $mRuleValue = $oRule->getValue(); - $aRuleValues = array(); - if (!$mRuleValue instanceof RuleValueList) { - $aRuleValues[] = $mRuleValue; - } else { - $aRuleValues = $mRuleValue->getListComponents(); - } - $aValues[$sPosition] = $aRuleValues; - } - $oNewRule = new Rule($sProperty); - if ((string) $aValues['left'][0] == (string) $aValues['right'][0]) { - if ((string) $aValues['top'][0] == (string) $aValues['bottom'][0]) { - if ((string) $aValues['top'][0] == (string) $aValues['left'][0]) { - // All 4 sides are equal - $oNewRule->addValue($aValues['top']); - } else { - // Top and bottom are equal, left and right are equal - $oNewRule->addValue($aValues['top']); - $oNewRule->addValue($aValues['left']); - } - } else { - // Only left and right are equal - $oNewRule->addValue($aValues['top']); - $oNewRule->addValue($aValues['left']); - $oNewRule->addValue($aValues['bottom']); - } - } else { - // No sides are equal - $oNewRule->addValue($aValues['top']); - $oNewRule->addValue($aValues['left']); - $oNewRule->addValue($aValues['bottom']); - $oNewRule->addValue($aValues['right']); - } - $this->addRule($oNewRule); - foreach ($aPositions as $sPosition) { - $this->removeRule(sprintf($sExpanded, $sPosition)); - } - } - } - } - - /** - * Looks for long format CSS font properties (e.g. font-weight) and - * tries to convert them into a shorthand CSS font property. - * At least font-size AND font-family must be present in order to create a shorthand declaration. - * */ - public function createFontShorthand() { - $aFontProperties = array( - 'font-style', 'font-variant', 'font-weight', 'font-size', 'line-height', 'font-family' - ); - $aRules = $this->getRules(); - if (!isset($aRules['font-size']) || !isset($aRules['font-family'])) { - return; - } - $oNewRule = new Rule('font'); - foreach (array('font-style', 'font-variant', 'font-weight') as $sProperty) { - if (isset($aRules[$sProperty])) { - $oRule = $aRules[$sProperty]; - $mRuleValue = $oRule->getValue(); - $aValues = array(); - if (!$mRuleValue instanceof RuleValueList) { - $aValues[] = $mRuleValue; - } else { - $aValues = $mRuleValue->getListComponents(); - } - if ($aValues[0] !== 'normal') { - $oNewRule->addValue($aValues[0]); - } - } - } - // Get the font-size value - $oRule = $aRules['font-size']; - $mRuleValue = $oRule->getValue(); - $aFSValues = array(); - if (!$mRuleValue instanceof RuleValueList) { - $aFSValues[] = $mRuleValue; - } else { - $aFSValues = $mRuleValue->getListComponents(); - } - // But wait to know if we have line-height to add it - if (isset($aRules['line-height'])) { - $oRule = $aRules['line-height']; - $mRuleValue = $oRule->getValue(); - $aLHValues = array(); - if (!$mRuleValue instanceof RuleValueList) { - $aLHValues[] = $mRuleValue; - } else { - $aLHValues = $mRuleValue->getListComponents(); - } - if ($aLHValues[0] !== 'normal') { - $val = new RuleValueList('/'); - $val->addListComponent($aFSValues[0]); - $val->addListComponent($aLHValues[0]); - $oNewRule->addValue($val); - } - } else { - $oNewRule->addValue($aFSValues[0]); - } - $oRule = $aRules['font-family']; - $mRuleValue = $oRule->getValue(); - $aFFValues = array(); - if (!$mRuleValue instanceof RuleValueList) { - $aFFValues[] = $mRuleValue; - } else { - $aFFValues = $mRuleValue->getListComponents(); - } - $oFFValue = new RuleValueList(','); - $oFFValue->setListComponents($aFFValues); - $oNewRule->addValue($oFFValue); - - $this->addRule($oNewRule); - foreach ($aFontProperties as $sProperty) { - $this->removeRule($sProperty); - } - } - - public function __toString() { - $sResult = implode(', ', $this->aSelectors) . ' {'; - $sResult .= parent::__toString(); - $sResult .= '}' . "\n"; - return $sResult; - } - -} diff --git a/lib/Sabberworm/CSS/RuleSet/RuleSet.php b/lib/Sabberworm/CSS/RuleSet/RuleSet.php deleted file mode 100644 index bf8c9f55..00000000 --- a/lib/Sabberworm/CSS/RuleSet/RuleSet.php +++ /dev/null @@ -1,74 +0,0 @@ -aRules = array(); - } - - public function addRule(Rule $oRule) { - $this->aRules[$oRule->getRule()] = $oRule; - } - - /** - * Returns all rules matching the given pattern - * @param (null|string|Rule) $mRule 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 excluded. passing a Rule behaves like calling getRules($mRule->getRule()). - * @example $oRuleSet->getRules('font-') //returns an array of all rules either beginning with font- or matching font. - * @example $oRuleSet->getRules('font') //returns array('font' => $oRule) or array(). - */ - public function getRules($mRule = null) { - if ($mRule === null) { - return $this->aRules; - } - $aResult = array(); - if ($mRule instanceof Rule) { - $mRule = $mRule->getRule(); - } - if (strrpos($mRule, '-') === strlen($mRule) - strlen('-')) { - $sStart = substr($mRule, 0, -1); - foreach ($this->aRules as $oRule) { - if ($oRule->getRule() === $sStart || strpos($oRule->getRule(), $mRule) === 0) { - $aResult[$oRule->getRule()] = $this->aRules[$oRule->getRule()]; - } - } - } else if (isset($this->aRules[$mRule])) { - $aResult[$mRule] = $this->aRules[$mRule]; - } - return $aResult; - } - - public function removeRule($mRule) { - if ($mRule instanceof Rule) { - $mRule = $mRule->getRule(); - } - if (strrpos($mRule, '-') === strlen($mRule) - strlen('-')) { - $sStart = substr($mRule, 0, -1); - foreach ($this->aRules as $oRule) { - if ($oRule->getRule() === $sStart || strpos($oRule->getRule(), $mRule) === 0) { - unset($this->aRules[$oRule->getRule()]); - } - } - } else if (isset($this->aRules[$mRule])) { - unset($this->aRules[$mRule]); - } - } - - public function __toString() { - $sResult = ''; - foreach ($this->aRules as $oRule) { - $sResult .= $oRule->__toString(); - } - return $sResult; - } - -} diff --git a/lib/Sabberworm/CSS/Value/CSSFunction.php b/lib/Sabberworm/CSS/Value/CSSFunction.php deleted file mode 100644 index 061c2254..00000000 --- a/lib/Sabberworm/CSS/Value/CSSFunction.php +++ /dev/null @@ -1,31 +0,0 @@ -sName = $sName; - parent::__construct($aArguments); - } - - public function getName() { - return $this->sName; - } - - public function setName($sName) { - $this->sName = $sName; - } - - public function getArguments() { - return $this->aComponents; - } - - public function __toString() { - $aArguments = parent::__toString(); - return "{$this->sName}({$aArguments})"; - } - -} \ No newline at end of file diff --git a/lib/Sabberworm/CSS/Value/Color.php b/lib/Sabberworm/CSS/Value/Color.php deleted file mode 100644 index 274a942c..00000000 --- a/lib/Sabberworm/CSS/Value/Color.php +++ /dev/null @@ -1,24 +0,0 @@ -aComponents; - } - - public function setColor($aColor) { - $this->setName(implode('', array_keys($aColor))); - $this->aComponents = $aColor; - } - - public function getColorDescription() { - return $this->getName(); - } - -} \ No newline at end of file diff --git a/lib/Sabberworm/CSS/Value/PrimitiveValue.php b/lib/Sabberworm/CSS/Value/PrimitiveValue.php deleted file mode 100644 index 2e6e2ab2..00000000 --- a/lib/Sabberworm/CSS/Value/PrimitiveValue.php +++ /dev/null @@ -1,7 +0,0 @@ -fSize = floatval($fSize); - $this->sUnit = $sUnit; - $this->bIsColorComponent = $bIsColorComponent; - } - - public function setUnit($sUnit) { - $this->sUnit = $sUnit; - } - - public function getUnit() { - return $this->sUnit; - } - - public function setSize($fSize) { - $this->fSize = floatval($fSize); - } - - public function getSize() { - return $this->fSize; - } - - public function isColorComponent() { - return $this->bIsColorComponent; - } - - /** - * Returns whether the number stored in this Size really represents a size (as in a length of something on screen). - * @return false if the unit an angle, a duration, a frequency or the number is a component in a Color object. - */ - public function isSize() { - $aNonSizeUnits = array('deg', 'grad', 'rad', 'turns', 's', 'ms', 'Hz', 'kHz'); - if (in_array($this->sUnit, $aNonSizeUnits)) { - return false; - } - return !$this->isColorComponent(); - } - - public function isRelative() { - if ($this->sUnit === '%' || $this->sUnit === 'em' || $this->sUnit === 'ex') { - return true; - } - if ($this->sUnit === null && $this->fSize != 0) { - return true; - } - return false; - } - - public function __toString() { - return $this->fSize . ($this->sUnit === null ? '' : $this->sUnit); - } - -} \ No newline at end of file diff --git a/lib/Sabberworm/CSS/Value/String.php b/lib/Sabberworm/CSS/Value/String.php deleted file mode 100644 index 34e60c95..00000000 --- a/lib/Sabberworm/CSS/Value/String.php +++ /dev/null @@ -1,27 +0,0 @@ -sString = $sString; - } - - public function setString($sString) { - $this->sString = $sString; - } - - public function getString() { - return $this->sString; - } - - public function __toString() { - $sString = addslashes($this->sString); - $sString = str_replace("\n", '\A', $sString); - return '"' . $sString . '"'; - } - -} \ No newline at end of file diff --git a/lib/Sabberworm/CSS/Value/URL.php b/lib/Sabberworm/CSS/Value/URL.php deleted file mode 100644 index 167ded93..00000000 --- a/lib/Sabberworm/CSS/Value/URL.php +++ /dev/null @@ -1,26 +0,0 @@ -oURL = $oURL; - } - - public function setURL(String $oURL) { - $this->oURL = $oURL; - } - - public function getURL() { - return $this->oURL; - } - - public function __toString() { - return "url({$this->oURL->__toString()})"; - } - -} \ No newline at end of file diff --git a/lib/Sabberworm/CSS/Value/Value.php b/lib/Sabberworm/CSS/Value/Value.php deleted file mode 100644 index f6f95e36..00000000 --- a/lib/Sabberworm/CSS/Value/Value.php +++ /dev/null @@ -1,8 +0,0 @@ -getListSeparator() === $sSeparator) { - $aComponents = $aComponents->getListComponents(); - } else if (!is_array($aComponents)) { - $aComponents = array($aComponents); - } - $this->aComponents = $aComponents; - $this->sSeparator = $sSeparator; - } - - public function addListComponent($mComponent) { - $this->aComponents[] = $mComponent; - } - - public function getListComponents() { - return $this->aComponents; - } - - public function setListComponents($aComponents) { - $this->aComponents = $aComponents; - } - - public function getListSeparator() { - return $this->sSeparator; - } - - public function setListSeparator($sSeparator) { - $this->sSeparator = $sSeparator; - } - - function __toString() { - return implode($this->sSeparator, $this->aComponents); - } - -} diff --git a/tests/CSSDeclarationBlockTest.php b/tests/CSSDeclarationBlockTest.php new file mode 100644 index 00000000..0d311c7e --- /dev/null +++ b/tests/CSSDeclarationBlockTest.php @@ -0,0 +1,223 @@ +parse(); + foreach($oDoc->getAllDeclarationBlocks() as $oDeclaration) + { + $oDeclaration->expandBorderShorthand(); + } + $this->assertSame(trim((string)$oDoc), $sExpected); + } + public function expandBorderShorthandProvider() + { + return array( + array('body{ border: 2px solid rgb(0,0,0) }', 'body {border-width: 2px;border-style: solid;border-color: rgb(0,0,0);}'), + array('body{ border: none }', 'body {border-style: none;}'), + array('body{ border: 2px }', 'body {border-width: 2px;}'), + array('body{ border: rgb(255,0,0) }', 'body {border-color: rgb(255,0,0);}'), + array('body{ border: 1em solid }', 'body {border-width: 1em;border-style: solid;}'), + array('body{ margin: 1em; }', 'body {margin: 1em;}') + ); + } + + /** + * @dataProvider expandFontShorthandProvider + **/ + public function testExpandFontShorthand($sCss, $sExpected) + { + $oParser = new CSSParser($sCss); + $oDoc = $oParser->parse(); + foreach($oDoc->getAllDeclarationBlocks() as $oDeclaration) + { + $oDeclaration->expandFontShorthand(); + } + $this->assertSame(trim((string)$oDoc), $sExpected); + } + public function expandFontShorthandProvider() + { + return array( + array( + 'body{ margin: 1em; }', + 'body {margin: 1em;}' + ), + array( + 'body {font: 12px serif;}', + 'body {font-style: normal;font-variant: normal;font-weight: normal;font-size: 12px;line-height: normal;font-family: serif;}' + ), + array( + 'body {font: italic 12px serif;}', + 'body {font-style: italic;font-variant: normal;font-weight: normal;font-size: 12px;line-height: normal;font-family: serif;}' + ), + array( + 'body {font: italic bold 12px serif;}', + 'body {font-style: italic;font-variant: normal;font-weight: bold;font-size: 12px;line-height: normal;font-family: serif;}' + ), + array( + 'body {font: italic bold 12px/1.6 serif;}', + 'body {font-style: italic;font-variant: normal;font-weight: bold;font-size: 12px;line-height: 1.6;font-family: serif;}' + ), + array( + 'body {font: italic small-caps bold 12px/1.6 serif;}', + 'body {font-style: italic;font-variant: small-caps;font-weight: bold;font-size: 12px;line-height: 1.6;font-family: serif;}' + ), + ); + } + + /** + * @dataProvider expandBackgroundShorthandProvider + **/ + public function testExpandBackgroundShorthand($sCss, $sExpected) + { + $oParser = new CSSParser($sCss); + $oDoc = $oParser->parse(); + foreach($oDoc->getAllDeclarationBlocks() as $oDeclaration) + { + $oDeclaration->expandBackgroundShorthand(); + } + $this->assertSame(trim((string)$oDoc), $sExpected); + } + public function expandBackgroundShorthandProvider() + { + return array( + array('body {border: 1px;}', 'body {border: 1px;}'), + array('body {background: rgb(255,0,0);}','body {background-color: rgb(255,0,0);background-image: none;background-repeat: repeat;background-attachment: scroll;background-position: 0% 0%;}'), + array('body {background: rgb(255,0,0) url("foobar.png");}','body {background-color: rgb(255,0,0);background-image: url("foobar.png");background-repeat: repeat;background-attachment: scroll;background-position: 0% 0%;}'), + array('body {background: rgb(255,0,0) url("foobar.png") no-repeat;}','body {background-color: rgb(255,0,0);background-image: url("foobar.png");background-repeat: no-repeat;background-attachment: scroll;background-position: 0% 0%;}'), + array('body {background: rgb(255,0,0) url("foobar.png") no-repeat center;}','body {background-color: rgb(255,0,0);background-image: url("foobar.png");background-repeat: no-repeat;background-attachment: scroll;background-position: center center;}'), + array('body {background: rgb(255,0,0) url("foobar.png") no-repeat top left;}','body {background-color: rgb(255,0,0);background-image: url("foobar.png");background-repeat: no-repeat;background-attachment: scroll;background-position: top left;}'), + ); + } + + /** + * @dataProvider expandDimensionsShorthandProvider + **/ + public function testExpandDimensionsShorthand($sCss, $sExpected) + { + $oParser = new CSSParser($sCss); + $oDoc = $oParser->parse(); + foreach($oDoc->getAllDeclarationBlocks() as $oDeclaration) + { + $oDeclaration->expandDimensionsShorthand(); + } + $this->assertSame(trim((string)$oDoc), $sExpected); + } + public function expandDimensionsShorthandProvider() + { + return array( + array('body {border: 1px;}', 'body {border: 1px;}'), + array('body {margin-top: 1px;}', 'body {margin-top: 1px;}'), + array('body {margin: 1em;}','body {margin-top: 1em;margin-right: 1em;margin-bottom: 1em;margin-left: 1em;}'), + array('body {margin: 1em 2em;}','body {margin-top: 1em;margin-right: 2em;margin-bottom: 1em;margin-left: 2em;}'), + array('body {margin: 1em 2em 3em;}','body {margin-top: 1em;margin-right: 2em;margin-bottom: 3em;margin-left: 2em;}'), + ); + } + + /** + * @dataProvider createBorderShorthandProvider + **/ + public function testCreateBorderShorthand($sCss, $sExpected) + { + $oParser = new CSSParser($sCss); + $oDoc = $oParser->parse(); + foreach($oDoc->getAllDeclarationBlocks() as $oDeclaration) + { + $oDeclaration->createBorderShorthand(); + } + $this->assertSame(trim((string)$oDoc), $sExpected); + } + public function createBorderShorthandProvider() + { + return array( + array('body {border-width: 2px;border-style: solid;border-color: rgb(0,0,0);}', 'body {border: 2px solid rgb(0,0,0);}'), + array('body {border-style: none;}', 'body {border: none;}'), + array('body {border-width: 1em;border-style: solid;}', 'body {border: 1em solid;}'), + array('body {margin: 1em;}', 'body {margin: 1em;}') + ); + } + + /** + * @dataProvider createFontShorthandProvider + **/ + public function testCreateFontShorthand($sCss, $sExpected) + { + $oParser = new CSSParser($sCss); + $oDoc = $oParser->parse(); + foreach($oDoc->getAllDeclarationBlocks() as $oDeclaration) + { + $oDeclaration->createFontShorthand(); + } + $this->assertSame(trim((string)$oDoc), $sExpected); + } + public function createFontShorthandProvider() + { + return array( + array('body {font-size: 12px; font-family: serif}', 'body {font: 12px serif;}'), + array('body {font-size: 12px; font-family: serif; font-style: italic;}', 'body {font: italic 12px serif;}'), + array('body {font-size: 12px; font-family: serif; font-style: italic; font-weight: bold;}', 'body {font: italic bold 12px serif;}'), + array('body {font-size: 12px; font-family: serif; font-style: italic; font-weight: bold; line-height: 1.6;}', 'body {font: italic bold 12px/1.6 serif;}'), + array('body {font-size: 12px; font-family: serif; font-style: italic; font-weight: bold; line-height: 1.6; font-variant: small-caps;}', 'body {font: italic small-caps bold 12px/1.6 serif;}'), + array('body {margin: 1em;}', 'body {margin: 1em;}') + ); + } + + /** + * @dataProvider createDimensionsShorthandProvider + **/ + public function testCreateDimensionsShorthand($sCss, $sExpected) + { + $oParser = new CSSParser($sCss); + $oDoc = $oParser->parse(); + foreach($oDoc->getAllDeclarationBlocks() as $oDeclaration) + { + $oDeclaration->createDimensionsShorthand(); + } + $this->assertSame(trim((string)$oDoc), $sExpected); + } + public function createDimensionsShorthandProvider() + { + return array( + array('body {border: 1px;}', 'body {border: 1px;}'), + array('body {margin-top: 1px;}', 'body {margin-top: 1px;}'), + array('body {margin-top: 1em; margin-right: 1em; margin-bottom: 1em; margin-left: 1em;}','body {margin: 1em;}'), + array('body {margin-top: 1em; margin-right: 2em; margin-bottom: 1em; margin-left: 2em;}','body {margin: 1em 2em;}'), + array('body {margin-top: 1em; margin-right: 2em; margin-bottom: 3em; margin-left: 2em;}','body {margin: 1em 2em 3em;}'), + ); + } + + /** + * @dataProvider createBackgroundShorthandProvider + **/ + public function testCreateBackgroundShorthand($sCss, $sExpected) + { + $oParser = new CSSParser($sCss); + $oDoc = $oParser->parse(); + foreach($oDoc->getAllDeclarationBlocks() as $oDeclaration) + { + $oDeclaration->createBackgroundShorthand(); + } + $this->assertSame(trim((string)$oDoc), $sExpected); + } + public function createBackgroundShorthandProvider() + { + return array( + array('body {border: 1px;}', 'body {border: 1px;}'), + array('body {background-color: rgb(255,0,0);}', 'body {background: rgb(255,0,0);}'), + array('body {background-color: rgb(255,0,0);background-image: url(foobar.png);}', 'body {background: rgb(255,0,0) url("foobar.png");}'), + array('body {background-color: rgb(255,0,0);background-image: url(foobar.png);background-repeat: no-repeat;}', 'body {background: rgb(255,0,0) url("foobar.png") no-repeat;}'), + array('body {background-color: rgb(255,0,0);background-image: url(foobar.png);background-repeat: no-repeat;}', 'body {background: rgb(255,0,0) url("foobar.png") no-repeat;}'), + array('body {background-color: rgb(255,0,0);background-image: url(foobar.png);background-repeat: no-repeat;background-position: center;}', 'body {background: rgb(255,0,0) url("foobar.png") no-repeat center;}'), + array('body {background-color: rgb(255,0,0);background-image: url(foobar.png);background-repeat: no-repeat;background-position: top left;}', 'body {background: rgb(255,0,0) url("foobar.png") no-repeat top left;}'), + ); + } + +} diff --git a/tests/Sabberworm/CSS/ParserTest.php b/tests/CSSParserTests.php similarity index 53% rename from tests/Sabberworm/CSS/ParserTest.php rename to tests/CSSParserTests.php index a5ecae67..d42027b7 100644 --- a/tests/Sabberworm/CSS/ParserTest.php +++ b/tests/CSSParserTests.php @@ -1,119 +1,116 @@ assertNotEquals('', $oParser->parse()->__toString()); - } catch (\Exception $e) { + } catch(Exception $e) { $this->fail($e); } } closedir($rHandle); } } - + /** - * @depends testFiles - */ + * @depends testCssFiles + */ function testColorParsing() { $oDoc = $this->parsedStructureForFile('colortest'); - foreach ($oDoc->getAllRuleSets() as $oRuleSet) { - if (!$oRuleSet instanceof DeclarationBlock) { + foreach($oDoc->getAllRuleSets() as $oRuleSet) { + if(!$oRuleSet instanceof CSSDeclarationBlock) { continue; } $sSelector = $oRuleSet->getSelectors(); $sSelector = $sSelector[0]->getSelector(); - if ($sSelector == '#mine') { + if($sSelector == '#mine') { $aColorRule = $oRuleSet->getRules('color'); $aValues = $aColorRule['color']->getValues(); $this->assertSame('red', $aValues[0][0]); $aColorRule = $oRuleSet->getRules('background-'); $aValues = $aColorRule['background-color']->getValues(); - $this->assertEquals(array('r' => new Size(35.0, null, true), 'g' => new Size(35.0, null, true), 'b' => new Size(35.0, null, true)), $aValues[0][0]->getColor()); + $this->assertEquals(array('r' => new CSSSize(35.0, null, true), 'g' => new CSSSize(35.0, null, true), 'b' => new CSSSize(35.0, null, true)), $aValues[0][0]->getColor()); $aColorRule = $oRuleSet->getRules('border-color'); $aValues = $aColorRule['border-color']->getValues(); - $this->assertEquals(array('r' => new Size(10.0, null, true), 'g' => new Size(100.0, null, true), 'b' => new Size(230.0, null, true), 'a' => new Size(0.3, null, true)), $aValues[0][0]->getColor()); + $this->assertEquals(array('r' => new CSSSize(10.0, null, true), 'g' => new CSSSize(100.0, null, true), 'b' => new CSSSize(230.0, null, true), 'a' => new CSSSize(0.3, null, true)), $aValues[0][0]->getColor()); $aColorRule = $oRuleSet->getRules('outline-color'); $aValues = $aColorRule['outline-color']->getValues(); - $this->assertEquals(array('r' => new Size(34.0, null, true), 'g' => new Size(34.0, null, true), 'b' => new Size(34.0, null, true)), $aValues[0][0]->getColor()); + $this->assertEquals(array('r' => new CSSSize(34.0, null, true), 'g' => new CSSSize(34.0, null, true), 'b' => new CSSSize(34.0, null, true)), $aValues[0][0]->getColor()); } } - foreach ($oDoc->getAllValues('background-') as $oColor) { - if ($oColor->getColorDescription() === 'hsl') { - $this->assertEquals(array('h' => new Size(220.0, null, true), 's' => new Size(10.0, null, true), 'l' => new Size(220.0, null, true)), $oColor->getColor()); + foreach($oDoc->getAllValues('background-') as $oColor) { + if($oColor->getColorDescription() === 'hsl') { + $this->assertEquals(array('h' => new CSSSize(220.0, null, true), 's' => new CSSSize(10.0, null, true), 'l' => new CSSSize(220.0, null, true)), $oColor->getColor()); } } - foreach ($oDoc->getAllValues('color') as $sColor) { + foreach($oDoc->getAllValues('color') as $sColor) { $this->assertSame('red', $sColor); } } - + function testUnicodeParsing() { $oDoc = $this->parsedStructureForFile('unicode'); - foreach ($oDoc->getAllDeclarationBlocks() as $oRuleSet) { + foreach($oDoc->getAllDeclarationBlocks() as $oRuleSet) { $sSelector = $oRuleSet->getSelectors(); $sSelector = $sSelector[0]->getSelector(); - if (substr($sSelector, 0, strlen('.test-')) !== '.test-') { + if(substr($sSelector, 0, strlen('.test-')) !== '.test-') { continue; } $aContentRules = $oRuleSet->getRules('content'); $aContents = $aContentRules['content']->getValues(); - $sString = $aContents[0][0]->__toString(); - if ($sSelector == '.test-1') { - $this->assertSame('" "', $sString); + $sCssString = $aContents[0][0]->__toString(); + if($sSelector == '.test-1') { + $this->assertSame('" "', $sCssString); } - if ($sSelector == '.test-2') { - $this->assertSame('"é"', $sString); + if($sSelector == '.test-2') { + $this->assertSame('"é"', $sCssString); } - if ($sSelector == '.test-3') { - $this->assertSame('" "', $sString); + if($sSelector == '.test-3') { + $this->assertSame('" "', $sCssString); } - if ($sSelector == '.test-4') { - $this->assertSame('"𝄞"', $sString); + if($sSelector == '.test-4') { + $this->assertSame('"𝄞"', $sCssString); } - if ($sSelector == '.test-5') { - $this->assertSame('"水"', $sString); + if($sSelector == '.test-5') { + $this->assertSame('"水"', $sCssString); } - if ($sSelector == '.test-6') { - $this->assertSame('"¥"', $sString); + if($sSelector == '.test-6') { + $this->assertSame('"¥"', $sCssString); } - if ($sSelector == '.test-7') { - $this->assertSame('"\A"', $sString); + if($sSelector == '.test-7') { + $this->assertSame('"\A"', $sCssString); } - if ($sSelector == '.test-8') { - $this->assertSame('"\"\""', $sString); + if($sSelector == '.test-8') { + $this->assertSame('"\"\""', $sCssString); } - if ($sSelector == '.test-9') { - $this->assertSame('"\"\\\'"', $sString); + if($sSelector == '.test-9') { + $this->assertSame('"\"\\\'"', $sCssString); } - if ($sSelector == '.test-10') { - $this->assertSame('"\\\'\\\\"', $sString); + if($sSelector == '.test-10') { + $this->assertSame('"\\\'\\\\"', $sCssString); } - if ($sSelector == '.test-11') { - $this->assertSame('"test"', $sString); + if($sSelector == '.test-11') { + $this->assertSame('"test"', $sCssString); } } } @@ -123,60 +120,60 @@ function testSpecificity() { $oDeclarationBlock = $oDoc->getAllDeclarationBlocks(); $oDeclarationBlock = $oDeclarationBlock[0]; $aSelectors = $oDeclarationBlock->getSelectors(); - foreach ($aSelectors as $oSelector) { - switch ($oSelector->getSelector()) { + foreach($aSelectors as $oSelector) { + switch($oSelector->getSelector()) { case "#test .help": $this->assertSame(110, $oSelector->getSpecificity()); - break; + break; case "#file": $this->assertSame(100, $oSelector->getSpecificity()); - break; + break; case ".help:hover": $this->assertSame(20, $oSelector->getSpecificity()); - break; + break; case "ol li::before": $this->assertSame(3, $oSelector->getSpecificity()); - break; + break; case "li.green": $this->assertSame(11, $oSelector->getSpecificity()); - break; + break; default: - $this->fail("specificity: untested selector " . $oSelector->getSelector()); + $this->fail("specificity: untested selector ".$oSelector->getSelector()); } } - $this->assertEquals(array(new Selector('#test .help', true)), $oDoc->getSelectorsBySpecificity('> 100')); + $this->assertEquals(array(new CSSSelector('#test .help', true)), $oDoc->getSelectorsBySpecificity('> 100')); } function testManipulation() { $oDoc = $this->parsedStructureForFile('atrules'); - $this->assertSame('@charset "utf-8";@font-face {font-family: "CrassRoots";src: url("../media/cr.ttf");}html, body {font-size: 1.6em;}' . "\n", $oDoc->__toString()); - foreach ($oDoc->getAllDeclarationBlocks() as $oBlock) { - foreach ($oBlock->getSelectors() as $oSelector) { + $this->assertSame('@charset "utf-8";@font-face {font-family: "CrassRoots";src: url("../media/cr.ttf");}html, body {font-size: 1.6em;}'."\n", $oDoc->__toString()); + foreach($oDoc->getAllDeclarationBlocks() as $oBlock) { + foreach($oBlock->getSelectors() as $oSelector) { //Loop over all selector parts (the comma-separated strings in a selector) and prepend the id - $oSelector->setSelector('#my_id ' . $oSelector->getSelector()); + $oSelector->setSelector('#my_id '.$oSelector->getSelector()); } } - $this->assertSame('@charset "utf-8";@font-face {font-family: "CrassRoots";src: url("../media/cr.ttf");}#my_id html, #my_id body {font-size: 1.6em;}' . "\n", $oDoc->__toString()); + $this->assertSame('@charset "utf-8";@font-face {font-family: "CrassRoots";src: url("../media/cr.ttf");}#my_id html, #my_id body {font-size: 1.6em;}'."\n", $oDoc->__toString()); $oDoc = $this->parsedStructureForFile('values'); $this->assertSame('#header {margin: 10px 2em 1cm 2%;font-family: Verdana,Helvetica,"Gill Sans",sans-serif;font-size: 10px;color: red !important;} -body {color: green;font: 75% "Lucida Grande","Trebuchet MS",Verdana,sans-serif;}' . "\n", $oDoc->__toString()); - foreach ($oDoc->getAllRuleSets() as $oRuleSet) { +body {color: green;font: 75% "Lucida Grande","Trebuchet MS",Verdana,sans-serif;}'."\n", $oDoc->__toString()); + foreach($oDoc->getAllRuleSets() as $oRuleSet) { $oRuleSet->removeRule('font-'); } $this->assertSame('#header {margin: 10px 2em 1cm 2%;color: red !important;} -body {color: green;}' . "\n", $oDoc->__toString()); +body {color: green;}'."\n", $oDoc->__toString()); } function testSlashedValues() { $oDoc = $this->parsedStructureForFile('slashed'); - $this->assertSame('.test {font: 12px/1.5 Verdana,Arial,sans-serif;border-radius: 5px 10px 5px 10px/10px 5px 10px 5px;}' . "\n", $oDoc->__toString()); - foreach ($oDoc->getAllValues(null) as $mValue) { - if ($mValue instanceof Size && $mValue->isSize() && !$mValue->isRelative()) { - $mValue->setSize($mValue->getSize() * 3); + $this->assertSame('.test {font: 12px/1.5 Verdana,Arial,sans-serif;border-radius: 5px 10px 5px 10px/10px 5px 10px 5px;}'."\n", $oDoc->__toString()); + foreach($oDoc->getAllValues(null) as $mValue) { + if($mValue instanceof CSSSize && $mValue->isSize() && !$mValue->isRelative()) { + $mValue->setSize($mValue->getSize()*3); } } - foreach ($oDoc->getAllDeclarationBlocks() as $oBlock) { + foreach($oDoc->getAllDeclarationBlocks() as $oBlock) { $oRule = $oBlock->getRules('font'); $oRule = $oRule['font']; $oSpaceList = $oRule->getValue(); @@ -196,7 +193,7 @@ function testSlashedValues() { $this->assertEquals(' ', $oSpaceList1->getListSeparator()); $this->assertEquals(' ', $oSpaceList2->getListSeparator()); } - $this->assertSame('.test {font: 36px/1.5 Verdana,Arial,sans-serif;border-radius: 15px 30px 15px 30px/30px 15px 30px 15px;}' . "\n", $oDoc->__toString()); + $this->assertSame('.test {font: 36px/1.5 Verdana,Arial,sans-serif;border-radius: 15px 30px 15px 30px/30px 15px 30px 15px;}'."\n", $oDoc->__toString()); } function testFunctionSyntax() { @@ -205,81 +202,81 @@ function testFunctionSyntax() { .collapser::before, .collapser::-moz-before, .collapser::-webkit-before {content: "»";font-size: 1.2em;margin-right: 0.2em;-moz-transition-property: -moz-transform;-moz-transition-duration: 0.2s;-moz-transform-origin: center 60%;} .collapser.expanded::before, .collapser.expanded::-moz-before, .collapser.expanded::-webkit-before {-moz-transform: rotate(90deg);} .collapser + * {height: 0;overflow: hidden;-moz-transition-property: height;-moz-transition-duration: 0.3s;} -.collapser.expanded + * {height: auto;}' . "\n"; +.collapser.expanded + * {height: auto;}'."\n"; $this->assertSame($sExpected, $oDoc->__toString()); - foreach ($oDoc->getAllValues(null, true) as $mValue) { - if ($mValue instanceof Size && $mValue->isSize()) { - $mValue->setSize($mValue->getSize() * 3); + foreach($oDoc->getAllValues(null, true) as $mValue) { + if($mValue instanceof CSSSize && $mValue->isSize()) { + $mValue->setSize($mValue->getSize()*3); } } $sExpected = str_replace(array('1.2em', '0.2em', '60%'), array('3.6em', '0.6em', '180%'), $sExpected); $this->assertSame($sExpected, $oDoc->__toString()); - - foreach ($oDoc->getAllValues(null, true) as $mValue) { - if ($mValue instanceof Size && !$mValue->isRelative() && !$mValue->isColorComponent()) { - $mValue->setSize($mValue->getSize() * 2); + + foreach($oDoc->getAllValues(null, true) as $mValue) { + if($mValue instanceof CSSSize && !$mValue->isRelative() && !$mValue->isColorComponent()) { + $mValue->setSize($mValue->getSize()*2); } } $sExpected = str_replace(array('0.2s', '0.3s', '90deg'), array('0.4s', '0.6s', '180deg'), $sExpected); $this->assertSame($sExpected, $oDoc->__toString()); } - function testExpandShorthands() { + function testExpandShorthands() { $oDoc = $this->parsedStructureForFile('expand-shorthands'); - $sExpected = 'body {font: italic 500 14px/1.618 "Trebuchet MS",Georgia,serif;border: 2px solid rgb(255,0,255);background: rgb(204,204,204) url("/images/foo.png") no-repeat left top;margin: 1em !important;padding: 2px 6px 3px;}' . "\n"; + $sExpected = 'body {font: italic 500 14px/1.618 "Trebuchet MS",Georgia,serif;border: 2px solid rgb(255,0,255);background: rgb(204,204,204) url("/images/foo.png") no-repeat left top;margin: 1em !important;padding: 2px 6px 3px;}'."\n"; $this->assertSame($sExpected, $oDoc->__toString()); - $oDoc->expandShorthands(); - $sExpected = 'body {margin-top: 1em !important;margin-right: 1em !important;margin-bottom: 1em !important;margin-left: 1em !important;padding-top: 2px;padding-right: 6px;padding-bottom: 3px;padding-left: 6px;border-top-color: rgb(255,0,255);border-right-color: rgb(255,0,255);border-bottom-color: rgb(255,0,255);border-left-color: rgb(255,0,255);border-top-style: solid;border-right-style: solid;border-bottom-style: solid;border-left-style: solid;border-top-width: 2px;border-right-width: 2px;border-bottom-width: 2px;border-left-width: 2px;font-style: italic;font-variant: normal;font-weight: 500;font-size: 14px;line-height: 1.618;font-family: "Trebuchet MS",Georgia,serif;background-color: rgb(204,204,204);background-image: url("/images/foo.png");background-repeat: no-repeat;background-attachment: scroll;background-position: left top;}' . "\n"; + $oDoc->expandShorthands(); + $sExpected = 'body {margin-top: 1em !important;margin-right: 1em !important;margin-bottom: 1em !important;margin-left: 1em !important;padding-top: 2px;padding-right: 6px;padding-bottom: 3px;padding-left: 6px;border-top-color: rgb(255,0,255);border-right-color: rgb(255,0,255);border-bottom-color: rgb(255,0,255);border-left-color: rgb(255,0,255);border-top-style: solid;border-right-style: solid;border-bottom-style: solid;border-left-style: solid;border-top-width: 2px;border-right-width: 2px;border-bottom-width: 2px;border-left-width: 2px;font-style: italic;font-variant: normal;font-weight: 500;font-size: 14px;line-height: 1.618;font-family: "Trebuchet MS",Georgia,serif;background-color: rgb(204,204,204);background-image: url("/images/foo.png");background-repeat: no-repeat;background-attachment: scroll;background-position: left top;}'."\n"; $this->assertSame($sExpected, $oDoc->__toString()); - } - - function testCreateShorthands() { + } + + function testCreateShorthands() { $oDoc = $this->parsedStructureForFile('create-shorthands'); - $sExpected = 'body {font-size: 2em;font-family: Helvetica,Arial,sans-serif;font-weight: bold;border-width: 2px;border-color: rgb(153,153,153);border-style: dotted;background-color: rgb(255,255,255);background-image: url("foobar.png");background-repeat: repeat-y;margin-top: 2px;margin-right: 3px;margin-bottom: 4px;margin-left: 5px;}' . "\n"; + $sExpected = 'body {font-size: 2em;font-family: Helvetica,Arial,sans-serif;font-weight: bold;border-width: 2px;border-color: rgb(153,153,153);border-style: dotted;background-color: rgb(255,255,255);background-image: url("foobar.png");background-repeat: repeat-y;margin-top: 2px;margin-right: 3px;margin-bottom: 4px;margin-left: 5px;}'."\n"; $this->assertSame($sExpected, $oDoc->__toString()); - $oDoc->createShorthands(); - $sExpected = 'body {background: rgb(255,255,255) url("foobar.png") repeat-y;margin: 2px 5px 4px 3px;border: 2px dotted rgb(153,153,153);font: bold 2em Helvetica,Arial,sans-serif;}' . "\n"; + $oDoc->createShorthands(); + $sExpected = 'body {background: rgb(255,255,255) url("foobar.png") repeat-y;margin: 2px 5px 4px 3px;border: 2px dotted rgb(153,153,153);font: bold 2em Helvetica,Arial,sans-serif;}'."\n"; $this->assertSame($sExpected, $oDoc->__toString()); - } - + } + function testPrefixedGradient() { $oDoc = $this->parsedStructureForFile('webkit'); - $sExpected = '.test {background: -webkit-linear-gradient(top right,white,black);}' . "\n"; + $sExpected = '.test {background: -webkit-linear-gradient(top right,white,black);}'."\n"; $this->assertSame($sExpected, $oDoc->__toString()); } function testListValueRemoval() { $oDoc = $this->parsedStructureForFile('atrules'); - foreach ($oDoc->getContents() as $oItem) { - if ($oItem instanceof AtRule) { + foreach($oDoc->getContents() as $oItem) { + if($oItem instanceof CSSAtRule) { $oDoc->remove($oItem); break; } } - $this->assertSame('@charset "utf-8";html, body {font-size: 1.6em;}' . "\n", $oDoc->__toString()); - + $this->assertSame('@charset "utf-8";html, body {font-size: 1.6em;}'."\n", $oDoc->__toString()); + $oDoc = $this->parsedStructureForFile('nested'); - foreach ($oDoc->getAllDeclarationBlocks() as $oBlock) { + foreach($oDoc->getAllDeclarationBlocks() as $oBlock) { $oDoc->removeDeclarationBlockBySelector($oBlock, false); break; } $this->assertSame('html {some-other: -test(val1);} @media screen {html {some: -test(val2);} -}#unrelated {other: yes;}' . "\n", $oDoc->__toString()); - +}#unrelated {other: yes;}'."\n", $oDoc->__toString()); + $oDoc = $this->parsedStructureForFile('nested'); - foreach ($oDoc->getAllDeclarationBlocks() as $oBlock) { + foreach($oDoc->getAllDeclarationBlocks() as $oBlock) { $oDoc->removeDeclarationBlockBySelector($oBlock, true); break; } $this->assertSame('@media screen {html {some: -test(val2);} -}#unrelated {other: yes;}' . "\n", $oDoc->__toString()); +}#unrelated {other: yes;}'."\n", $oDoc->__toString()); } function parsedStructureForFile($sFileName) { - $sFile = dirname(__FILE__) . '/../../files' . DIRECTORY_SEPARATOR . "$sFileName.css"; - $oParser = new Parser(file_get_contents($sFile)); + $sFile = dirname(__FILE__).DIRECTORY_SEPARATOR.'files'.DIRECTORY_SEPARATOR."$sFileName.css"; + $oParser = new CSSParser(file_get_contents($sFile)); return $oParser->parse(); } diff --git a/tests/Sabberworm/CSS/RuleSet/DeclarationBlockTest.php b/tests/Sabberworm/CSS/RuleSet/DeclarationBlockTest.php deleted file mode 100644 index 62be03ef..00000000 --- a/tests/Sabberworm/CSS/RuleSet/DeclarationBlockTest.php +++ /dev/null @@ -1,208 +0,0 @@ -parse(); - foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) { - $oDeclaration->expandBorderShorthand(); - } - $this->assertSame(trim((string) $oDoc), $sExpected); - } - - public function expandBorderShorthandProvider() { - return array( - array('body{ border: 2px solid rgb(0,0,0) }', 'body {border-width: 2px;border-style: solid;border-color: rgb(0,0,0);}'), - array('body{ border: none }', 'body {border-style: none;}'), - array('body{ border: 2px }', 'body {border-width: 2px;}'), - array('body{ border: rgb(255,0,0) }', 'body {border-color: rgb(255,0,0);}'), - array('body{ border: 1em solid }', 'body {border-width: 1em;border-style: solid;}'), - array('body{ margin: 1em; }', 'body {margin: 1em;}') - ); - } - - /** - * @dataProvider expandFontShorthandProvider - * */ - public function testExpandFontShorthand($sCss, $sExpected) { - $oParser = new Parser($sCss); - $oDoc = $oParser->parse(); - foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) { - $oDeclaration->expandFontShorthand(); - } - $this->assertSame(trim((string) $oDoc), $sExpected); - } - - public function expandFontShorthandProvider() { - return array( - array( - 'body{ margin: 1em; }', - 'body {margin: 1em;}' - ), - array( - 'body {font: 12px serif;}', - 'body {font-style: normal;font-variant: normal;font-weight: normal;font-size: 12px;line-height: normal;font-family: serif;}' - ), - array( - 'body {font: italic 12px serif;}', - 'body {font-style: italic;font-variant: normal;font-weight: normal;font-size: 12px;line-height: normal;font-family: serif;}' - ), - array( - 'body {font: italic bold 12px serif;}', - 'body {font-style: italic;font-variant: normal;font-weight: bold;font-size: 12px;line-height: normal;font-family: serif;}' - ), - array( - 'body {font: italic bold 12px/1.6 serif;}', - 'body {font-style: italic;font-variant: normal;font-weight: bold;font-size: 12px;line-height: 1.6;font-family: serif;}' - ), - array( - 'body {font: italic small-caps bold 12px/1.6 serif;}', - 'body {font-style: italic;font-variant: small-caps;font-weight: bold;font-size: 12px;line-height: 1.6;font-family: serif;}' - ), - ); - } - - /** - * @dataProvider expandBackgroundShorthandProvider - * */ - public function testExpandBackgroundShorthand($sCss, $sExpected) { - $oParser = new Parser($sCss); - $oDoc = $oParser->parse(); - foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) { - $oDeclaration->expandBackgroundShorthand(); - } - $this->assertSame(trim((string) $oDoc), $sExpected); - } - - public function expandBackgroundShorthandProvider() { - return array( - array('body {border: 1px;}', 'body {border: 1px;}'), - array('body {background: rgb(255,0,0);}', 'body {background-color: rgb(255,0,0);background-image: none;background-repeat: repeat;background-attachment: scroll;background-position: 0% 0%;}'), - array('body {background: rgb(255,0,0) url("foobar.png");}', 'body {background-color: rgb(255,0,0);background-image: url("foobar.png");background-repeat: repeat;background-attachment: scroll;background-position: 0% 0%;}'), - array('body {background: rgb(255,0,0) url("foobar.png") no-repeat;}', 'body {background-color: rgb(255,0,0);background-image: url("foobar.png");background-repeat: no-repeat;background-attachment: scroll;background-position: 0% 0%;}'), - array('body {background: rgb(255,0,0) url("foobar.png") no-repeat center;}', 'body {background-color: rgb(255,0,0);background-image: url("foobar.png");background-repeat: no-repeat;background-attachment: scroll;background-position: center center;}'), - array('body {background: rgb(255,0,0) url("foobar.png") no-repeat top left;}', 'body {background-color: rgb(255,0,0);background-image: url("foobar.png");background-repeat: no-repeat;background-attachment: scroll;background-position: top left;}'), - ); - } - - /** - * @dataProvider expandDimensionsShorthandProvider - * */ - public function testExpandDimensionsShorthand($sCss, $sExpected) { - $oParser = new Parser($sCss); - $oDoc = $oParser->parse(); - foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) { - $oDeclaration->expandDimensionsShorthand(); - } - $this->assertSame(trim((string) $oDoc), $sExpected); - } - - public function expandDimensionsShorthandProvider() { - return array( - array('body {border: 1px;}', 'body {border: 1px;}'), - array('body {margin-top: 1px;}', 'body {margin-top: 1px;}'), - array('body {margin: 1em;}', 'body {margin-top: 1em;margin-right: 1em;margin-bottom: 1em;margin-left: 1em;}'), - array('body {margin: 1em 2em;}', 'body {margin-top: 1em;margin-right: 2em;margin-bottom: 1em;margin-left: 2em;}'), - array('body {margin: 1em 2em 3em;}', 'body {margin-top: 1em;margin-right: 2em;margin-bottom: 3em;margin-left: 2em;}'), - ); - } - - /** - * @dataProvider createBorderShorthandProvider - * */ - public function testCreateBorderShorthand($sCss, $sExpected) { - $oParser = new Parser($sCss); - $oDoc = $oParser->parse(); - foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) { - $oDeclaration->createBorderShorthand(); - } - $this->assertSame(trim((string) $oDoc), $sExpected); - } - - public function createBorderShorthandProvider() { - return array( - array('body {border-width: 2px;border-style: solid;border-color: rgb(0,0,0);}', 'body {border: 2px solid rgb(0,0,0);}'), - array('body {border-style: none;}', 'body {border: none;}'), - array('body {border-width: 1em;border-style: solid;}', 'body {border: 1em solid;}'), - array('body {margin: 1em;}', 'body {margin: 1em;}') - ); - } - - /** - * @dataProvider createFontShorthandProvider - * */ - public function testCreateFontShorthand($sCss, $sExpected) { - $oParser = new Parser($sCss); - $oDoc = $oParser->parse(); - foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) { - $oDeclaration->createFontShorthand(); - } - $this->assertSame(trim((string) $oDoc), $sExpected); - } - - public function createFontShorthandProvider() { - return array( - array('body {font-size: 12px; font-family: serif}', 'body {font: 12px serif;}'), - array('body {font-size: 12px; font-family: serif; font-style: italic;}', 'body {font: italic 12px serif;}'), - array('body {font-size: 12px; font-family: serif; font-style: italic; font-weight: bold;}', 'body {font: italic bold 12px serif;}'), - array('body {font-size: 12px; font-family: serif; font-style: italic; font-weight: bold; line-height: 1.6;}', 'body {font: italic bold 12px/1.6 serif;}'), - array('body {font-size: 12px; font-family: serif; font-style: italic; font-weight: bold; line-height: 1.6; font-variant: small-caps;}', 'body {font: italic small-caps bold 12px/1.6 serif;}'), - array('body {margin: 1em;}', 'body {margin: 1em;}') - ); - } - - /** - * @dataProvider createDimensionsShorthandProvider - * */ - public function testCreateDimensionsShorthand($sCss, $sExpected) { - $oParser = new Parser($sCss); - $oDoc = $oParser->parse(); - foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) { - $oDeclaration->createDimensionsShorthand(); - } - $this->assertSame(trim((string) $oDoc), $sExpected); - } - - public function createDimensionsShorthandProvider() { - return array( - array('body {border: 1px;}', 'body {border: 1px;}'), - array('body {margin-top: 1px;}', 'body {margin-top: 1px;}'), - array('body {margin-top: 1em; margin-right: 1em; margin-bottom: 1em; margin-left: 1em;}', 'body {margin: 1em;}'), - array('body {margin-top: 1em; margin-right: 2em; margin-bottom: 1em; margin-left: 2em;}', 'body {margin: 1em 2em;}'), - array('body {margin-top: 1em; margin-right: 2em; margin-bottom: 3em; margin-left: 2em;}', 'body {margin: 1em 2em 3em;}'), - ); - } - - /** - * @dataProvider createBackgroundShorthandProvider - * */ - public function testCreateBackgroundShorthand($sCss, $sExpected) { - $oParser = new Parser($sCss); - $oDoc = $oParser->parse(); - foreach ($oDoc->getAllDeclarationBlocks() as $oDeclaration) { - $oDeclaration->createBackgroundShorthand(); - } - $this->assertSame(trim((string) $oDoc), $sExpected); - } - - public function createBackgroundShorthandProvider() { - return array( - array('body {border: 1px;}', 'body {border: 1px;}'), - array('body {background-color: rgb(255,0,0);}', 'body {background: rgb(255,0,0);}'), - array('body {background-color: rgb(255,0,0);background-image: url(foobar.png);}', 'body {background: rgb(255,0,0) url("foobar.png");}'), - array('body {background-color: rgb(255,0,0);background-image: url(foobar.png);background-repeat: no-repeat;}', 'body {background: rgb(255,0,0) url("foobar.png") no-repeat;}'), - array('body {background-color: rgb(255,0,0);background-image: url(foobar.png);background-repeat: no-repeat;}', 'body {background: rgb(255,0,0) url("foobar.png") no-repeat;}'), - array('body {background-color: rgb(255,0,0);background-image: url(foobar.png);background-repeat: no-repeat;background-position: center;}', 'body {background: rgb(255,0,0) url("foobar.png") no-repeat center;}'), - array('body {background-color: rgb(255,0,0);background-image: url(foobar.png);background-repeat: no-repeat;background-position: top left;}', 'body {background: rgb(255,0,0) url("foobar.png") no-repeat top left;}'), - ); - } - -} diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index 7c4de814..00000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,10 +0,0 @@ -parse();