Skip to content

Commit

Permalink
Overhaul of ValueList handling. A CSSRule now can have only one value…
Browse files Browse the repository at this point in the history
… but this value may be a CSSRuleValueList which has a separator and may contain multiple values, which, each, in turn can also be CSSValueLists.

Closes #14 as fixed
References #17
Also adds support for function(key=value) as in msie filter properties (issue #1).
  • Loading branch information
sabberworm committed Aug 30, 2011
1 parent eaf5136 commit 797e8d6
Show file tree
Hide file tree
Showing 10 changed files with 226 additions and 72 deletions.
74 changes: 55 additions & 19 deletions CSSParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ private function parseIdentifier($bAllowFunctions = true) {
}
if($bAllowFunctions && $this->comes('(')) {
$this->consume('(');
$sResult = new CSSFunction($sResult, $this->parseValue());
$sResult = new CSSFunction($sResult, $this->parseValue(array('=', ',')));
$this->consume(')');
}
return $sResult;
Expand Down Expand Up @@ -211,11 +211,8 @@ private function parseRule() {
$oRule = new CSSRule($this->parseIdentifier());
$this->consumeWhiteSpace();
$this->consume(':');
$this->consumeWhiteSpace();
while(!($this->comes('}') || $this->comes(';') || $this->comes('!'))) {
$oRule->addValue($this->parseValue());
$this->consumeWhiteSpace();
}
$oValue = $this->parseValue(self::listDelimiterForRule($oRule->getRule()));
$oRule->setValue($oValue);
if($this->comes('!')) {
$this->consume('!');
$this->consumeWhiteSpace();
Expand All @@ -230,17 +227,60 @@ private function parseRule() {
}
return $oRule;
}

private function parseValue() {
$aResult = array();
do {
$aResult[] = $this->parseSingleValue();
} while($this->comes(',') && is_string($this->consume(',')));

return $aResult;

private function parseValue($aListDelimiters) {
$aStack = array();
$this->consumeWhiteSpace();
while(!($this->comes('}') || $this->comes(';') || $this->comes('!') || $this->comes(')'))) {
if(count($aStack) > 0) {
$bFoundDelimiter = false;
foreach($aListDelimiters as $sDelimiter) {
if($this->comes($sDelimiter)) {
array_push($aStack, $this->consume($sDelimiter));
$this->consumeWhiteSpace();
$bFoundDelimiter = true;
break;
}
}
if(!$bFoundDelimiter) {
//Whitespace was the list delimiter
array_push($aStack, ' ');
}
}
array_push($aStack, $this->parsePrimitiveValue());
$this->consumeWhiteSpace();
}
foreach($aListDelimiters as $sDelimiter) {
if(count($aStack) === 1) {
return $aStack[0];
}
$iStartPosition = null;
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]) {
break;
}
$iLength++;
}
$oList = new CSSRuleValueList($sDelimiter);
for($i=$iStartPosition-1;$i-$iStartPosition+1<$iLength*2;$i+=2) {
$oList->addListComponent($aStack[$i]);
}
array_splice($aStack, $iStartPosition-1, $iLength*2-1, array($oList));
}
}
return $aStack[0];
}

private function parseSingleValue() {
private static function listDelimiterForRule($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)))) {
Expand All @@ -255,10 +295,6 @@ private function parseSingleValue() {
$oValue = $this->parseIdentifier();
}
$this->consumeWhiteSpace();
if($this->comes('/')) {
$this->consume('/');
$oValue = new CSSSlashedValue($oValue, $this->parseSingleValue());
}
return $oValue;
}

Expand Down
6 changes: 1 addition & 5 deletions lib/CSSList.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,7 @@ protected function allValues($oElement, &$aResult, $sSearchString = null, $bSear
$this->allValues($oRule, $aResult, $sSearchString, $bSearchInFunctionArguments);
}
} else if($oElement instanceof CSSRule) {
foreach($oElement->getValues() as $aValues) {
foreach($aValues as $mValue) {
$this->allValues($mValue, $aResult, $sSearchString, $bSearchInFunctionArguments);
}
}
$this->allValues($oElement->getValue(), $aResult, $sSearchString, $bSearchInFunctionArguments);
} else if($oElement instanceof CSSValueList) {
if($bSearchInFunctionArguments || !($oElement instanceof CSSFunction)) {
foreach($oElement->getListComponents() as $mComponent) {
Expand Down
110 changes: 94 additions & 16 deletions lib/CSSRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,50 +6,128 @@
*/
class CSSRule {
private $sRule;
private $aValues;
private $mValue;
private $bIsImportant;

public function __construct($sRule) {
$this->sRule = $sRule;
$this->mValue = null;
$this->bIsImportant = false;
}

public function setRule($sRule) {
$this->sRule = $sRule;
$this->sRule = $sRule;
}

public function getRule() {
return $this->sRule;
return $this->sRule;
}

public function addValue($mValue) {
$this->aValues[] = $mValue;

public function getValue() {
return $this->mValue;
}

public function setValue($mValue) {
$this->mValue = $mValue;
}

public function setValues($aValues) {
$this->aValues = $aValues;
/**
* @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() {
return $this->aValues;
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;
$this->bIsImportant = $bIsImportant;
}

public function getIsImportant() {
return $this->bIsImportant;
return $this->bIsImportant;
}

public function __toString() {
$sResult = "{$this->sRule}: ";
foreach($this->aValues as $aValues) {
$sResult .= implode(', ', $aValues).' ';
if($this->mValue instanceof CSSValue) { //Can also be a CSSValueList
$sResult .= $this->mValue->__toString();
} else {
$sResult .= $this->mValue;
}
if($this->bIsImportant) {
$sResult .= '!important';
} else {
$sResult = substr($sResult, 0, -1);
$sResult .= ' !important';
}
$sResult .= ';';
return $sResult;
Expand Down
17 changes: 11 additions & 6 deletions lib/CSSRuleSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ public function expandDimensionsShorthand()
}
}

/*
/**
* Convert shorthand font declarations
* (e.g. <tt>font: 300 italic 11px/14px verdana, helvetica, sans-serif;</tt>)
* into their constituent parts.
Expand Down Expand Up @@ -310,10 +310,11 @@ public function expandFontShorthand()
){
$aFontProperties['font-weight'] = $aValues;
}
else if($mValue instanceof CSSSlashedValue)
else if($mValue instanceof CSSRuleValueList && $mValue->getListSeparator() === '/')
{
$aFontProperties['font-size'] = array($mValue->getValue1());
$aFontProperties['line-height'] = array($mValue->getValue2());
list($oSize, $oHeight) = $mValue->getListComponents();
$aFontProperties['font-size'] = $oSize;
$aFontProperties['line-height'] = $oHeight;
}
else if($mValue instanceof CSSSize && $mValue->getUnit() !== null)
{
Expand Down Expand Up @@ -601,7 +602,9 @@ public function createFontShorthand()
$aLHValues = $aRules['line-height']->getValues();
if($aLHValues[0][0] !== 'normal')
{
$val = new CSSSlashedValue($aFSValues[0][0], $aLHValues[0][0]);
$val = new CSSRuleValueList('/');
$val->addListComponent($aFSValues[0][0]);
$val->addListComponent($aLHValues[0][0]);
$oNewRule->addValue(array($val));
}
}
Expand All @@ -611,7 +614,9 @@ public function createFontShorthand()
}

$aFFValues = $aRules['font-family']->getValues();
$oNewRule->addValue($aFFValues[0]);
$oFFValue = new CSSRuleValueList(',');
$oFFValue->setListComponents($aFFValues[0]);
$oNewRule->addValue($oFFValue);

$this->addRule($oNewRule);
foreach ($aFontProperties as $sProperty)
Expand Down
40 changes: 29 additions & 11 deletions lib/CSSValueList.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,43 @@ abstract class CSSValueList extends CSSValue {
protected $sSeparator;

public function __construct($aComponents = array(), $sSeparator = ',') {
if($aComponents instanceof CSSValueList && $aComponents->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 CSSSlashedValue extends CSSValueList {
public function __construct($oValue1, $oValue2) {
parent::__construct(array($oValue1, $oValue2), '/');
}

public function getValue1() {
return $this->aComponents[0];
}

public function getValue2() {
return $this->aComponents[1];
class CSSRuleValueList extends CSSValueList {
public function __construct($sSeparator = ',') {
parent::__construct(array(), $sSeparator);
}
}

Expand All @@ -47,6 +56,10 @@ public function getName() {
return $this->sName;
}

public function setName($sName) {
$this->sName = $sName;
}

public function getArguments() {
return $this->aComponents;
}
Expand All @@ -65,6 +78,11 @@ public function __construct($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();
Expand Down
Loading

0 comments on commit 797e8d6

Please sign in to comment.