Skip to content

Commit 797e8d6

Browse files
committed
Overhaul of ValueList handling. A CSSRule now can have only one value 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 MyIntervals#14 as fixed References MyIntervals#17 Also adds support for function(key=value) as in msie filter properties (issue MyIntervals#1).
1 parent eaf5136 commit 797e8d6

File tree

10 files changed

+226
-72
lines changed

10 files changed

+226
-72
lines changed

CSSParser.php

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ private function parseIdentifier($bAllowFunctions = true) {
109109
}
110110
if($bAllowFunctions && $this->comes('(')) {
111111
$this->consume('(');
112-
$sResult = new CSSFunction($sResult, $this->parseValue());
112+
$sResult = new CSSFunction($sResult, $this->parseValue(array('=', ',')));
113113
$this->consume(')');
114114
}
115115
return $sResult;
@@ -211,11 +211,8 @@ private function parseRule() {
211211
$oRule = new CSSRule($this->parseIdentifier());
212212
$this->consumeWhiteSpace();
213213
$this->consume(':');
214-
$this->consumeWhiteSpace();
215-
while(!($this->comes('}') || $this->comes(';') || $this->comes('!'))) {
216-
$oRule->addValue($this->parseValue());
217-
$this->consumeWhiteSpace();
218-
}
214+
$oValue = $this->parseValue(self::listDelimiterForRule($oRule->getRule()));
215+
$oRule->setValue($oValue);
219216
if($this->comes('!')) {
220217
$this->consume('!');
221218
$this->consumeWhiteSpace();
@@ -230,17 +227,60 @@ private function parseRule() {
230227
}
231228
return $oRule;
232229
}
233-
234-
private function parseValue() {
235-
$aResult = array();
236-
do {
237-
$aResult[] = $this->parseSingleValue();
238-
} while($this->comes(',') && is_string($this->consume(',')));
239-
240-
return $aResult;
230+
231+
private function parseValue($aListDelimiters) {
232+
$aStack = array();
233+
$this->consumeWhiteSpace();
234+
while(!($this->comes('}') || $this->comes(';') || $this->comes('!') || $this->comes(')'))) {
235+
if(count($aStack) > 0) {
236+
$bFoundDelimiter = false;
237+
foreach($aListDelimiters as $sDelimiter) {
238+
if($this->comes($sDelimiter)) {
239+
array_push($aStack, $this->consume($sDelimiter));
240+
$this->consumeWhiteSpace();
241+
$bFoundDelimiter = true;
242+
break;
243+
}
244+
}
245+
if(!$bFoundDelimiter) {
246+
//Whitespace was the list delimiter
247+
array_push($aStack, ' ');
248+
}
249+
}
250+
array_push($aStack, $this->parsePrimitiveValue());
251+
$this->consumeWhiteSpace();
252+
}
253+
foreach($aListDelimiters as $sDelimiter) {
254+
if(count($aStack) === 1) {
255+
return $aStack[0];
256+
}
257+
$iStartPosition = null;
258+
while(($iStartPosition = array_search($sDelimiter, $aStack, true)) !== false) {
259+
$iLength = 2; //Number of elements to be joined
260+
for($i=$iStartPosition+2;$i<count($aStack);$i+=2) {
261+
if($sDelimiter !== $aStack[$i]) {
262+
break;
263+
}
264+
$iLength++;
265+
}
266+
$oList = new CSSRuleValueList($sDelimiter);
267+
for($i=$iStartPosition-1;$i-$iStartPosition+1<$iLength*2;$i+=2) {
268+
$oList->addListComponent($aStack[$i]);
269+
}
270+
array_splice($aStack, $iStartPosition-1, $iLength*2-1, array($oList));
271+
}
272+
}
273+
return $aStack[0];
241274
}
242275

243-
private function parseSingleValue() {
276+
private static function listDelimiterForRule($sRule) {
277+
if(preg_match('/^font($|-)/', $sRule)) {
278+
return array(',', '/', ' ');
279+
}
280+
return array(',', ' ', '/');
281+
}
282+
283+
private function parsePrimitiveValue() {
244284
$oValue = null;
245285
$this->consumeWhiteSpace();
246286
if(is_numeric($this->peek()) || (($this->comes('-') || $this->comes('.')) && is_numeric($this->peek(1, 1)))) {
@@ -255,10 +295,6 @@ private function parseSingleValue() {
255295
$oValue = $this->parseIdentifier();
256296
}
257297
$this->consumeWhiteSpace();
258-
if($this->comes('/')) {
259-
$this->consume('/');
260-
$oValue = new CSSSlashedValue($oValue, $this->parseSingleValue());
261-
}
262298
return $oValue;
263299
}
264300

lib/CSSList.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,7 @@ protected function allValues($oElement, &$aResult, $sSearchString = null, $bSear
5757
$this->allValues($oRule, $aResult, $sSearchString, $bSearchInFunctionArguments);
5858
}
5959
} else if($oElement instanceof CSSRule) {
60-
foreach($oElement->getValues() as $aValues) {
61-
foreach($aValues as $mValue) {
62-
$this->allValues($mValue, $aResult, $sSearchString, $bSearchInFunctionArguments);
63-
}
64-
}
60+
$this->allValues($oElement->getValue(), $aResult, $sSearchString, $bSearchInFunctionArguments);
6561
} else if($oElement instanceof CSSValueList) {
6662
if($bSearchInFunctionArguments || !($oElement instanceof CSSFunction)) {
6763
foreach($oElement->getListComponents() as $mComponent) {

lib/CSSRule.php

Lines changed: 94 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,50 +6,128 @@
66
*/
77
class CSSRule {
88
private $sRule;
9-
private $aValues;
9+
private $mValue;
1010
private $bIsImportant;
1111

1212
public function __construct($sRule) {
1313
$this->sRule = $sRule;
14+
$this->mValue = null;
1415
$this->bIsImportant = false;
1516
}
1617

1718
public function setRule($sRule) {
18-
$this->sRule = $sRule;
19+
$this->sRule = $sRule;
1920
}
2021

2122
public function getRule() {
22-
return $this->sRule;
23+
return $this->sRule;
2324
}
24-
25-
public function addValue($mValue) {
26-
$this->aValues[] = $mValue;
25+
26+
public function getValue() {
27+
return $this->mValue;
28+
}
29+
30+
public function setValue($mValue) {
31+
$this->mValue = $mValue;
2732
}
2833

29-
public function setValues($aValues) {
30-
$this->aValues = $aValues;
34+
/**
35+
* @deprecated Old-Style 2-dimensional array given. Retained for (some) backwards-compatibility. Use setValue() instead and wrapp the value inside a CSSRuleValueList if necessary.
36+
*/
37+
public function setValues($aSpaceSeparatedValues) {
38+
$oSpaceSeparatedList = null;
39+
if(count($aSpaceSeparatedValues) > 1) {
40+
$oSpaceSeparatedList = new CSSRuleValueList(' ');
41+
}
42+
foreach($aSpaceSeparatedValues as $aCommaSeparatedValues) {
43+
$oCommaSeparatedList = null;
44+
if(count($aCommaSeparatedValues) > 1) {
45+
$oCommaSeparatedList = new CSSRuleValueList(',');
46+
}
47+
foreach($aCommaSeparatedValues as $mValue) {
48+
if(!$oSpaceSeparatedList && !$oCommaSeparatedList) {
49+
$this->mValue = $mValue;
50+
return $mValue;
51+
}
52+
if($oCommaSeparatedList) {
53+
$oCommaSeparatedList->addListComponent($mValue);
54+
} else {
55+
$oSpaceSeparatedList->addListComponent($mValue);
56+
}
57+
}
58+
if(!$oSpaceSeparatedList) {
59+
$this->mValue = $oCommaSeparatedList;
60+
return $oCommaSeparatedList;
61+
} else {
62+
$oSpaceSeparatedList->addListComponent($oCommaSeparatedList);
63+
}
64+
}
65+
$this->mValue = $oSpaceSeparatedList;
66+
return $oSpaceSeparatedList;
3167
}
3268

69+
/**
70+
* @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).
71+
*/
3372
public function getValues() {
34-
return $this->aValues;
73+
if(!$this->mValue instanceof CSSRuleValueList) {
74+
return array(array($this->mValue));
75+
}
76+
if($this->mValue->getListSeparator() === ',') {
77+
return array($this->mValue->getListComponents());
78+
}
79+
$aResult = array();
80+
foreach($this->mValue->getListComponents() as $mValue) {
81+
if(!$mValue instanceof CSSRuleValueList || $mValue->getListSeparator() !== ',') {
82+
$aResult[] = array($mValue);
83+
continue;
84+
}
85+
if($this->mValue->getListSeparator() === ' ' || count($aResult) === 0) {
86+
$aResult[] = array();
87+
}
88+
foreach($mValue->getListComponents() as $mValue) {
89+
$aResult[count($aResult)-1][] = $mValue;
90+
}
91+
}
92+
return $aResult;
93+
}
94+
95+
/**
96+
* 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.
97+
*/
98+
public function addValue($mValue, $sType = ' ') {
99+
if(!is_array($mValue)) {
100+
$mValue = array($mValue);
101+
}
102+
if(!$this->mValue instanceof CSSRuleValueList || $this->mValue->getListSeparator() !== $sType) {
103+
$mCurrentValue = $this->mValue;
104+
$this->mValue = new CSSRuleValueList($sType);
105+
if($mCurrentValue) {
106+
$this->mValue->addListComponent($mCurrentValue);
107+
}
108+
}
109+
foreach($mValue as $mValueItem) {
110+
$this->mValue->addListComponent($mValueItem);
111+
}
35112
}
36113

37114
public function setIsImportant($bIsImportant) {
38-
$this->bIsImportant = $bIsImportant;
115+
$this->bIsImportant = $bIsImportant;
39116
}
40117

41118
public function getIsImportant() {
42-
return $this->bIsImportant;
119+
return $this->bIsImportant;
43120
}
121+
44122
public function __toString() {
45123
$sResult = "{$this->sRule}: ";
46-
foreach($this->aValues as $aValues) {
47-
$sResult .= implode(', ', $aValues).' ';
124+
if($this->mValue instanceof CSSValue) { //Can also be a CSSValueList
125+
$sResult .= $this->mValue->__toString();
126+
} else {
127+
$sResult .= $this->mValue;
48128
}
49129
if($this->bIsImportant) {
50-
$sResult .= '!important';
51-
} else {
52-
$sResult = substr($sResult, 0, -1);
130+
$sResult .= ' !important';
53131
}
54132
$sResult .= ';';
55133
return $sResult;

lib/CSSRuleSet.php

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ public function expandDimensionsShorthand()
263263
}
264264
}
265265

266-
/*
266+
/**
267267
* Convert shorthand font declarations
268268
* (e.g. <tt>font: 300 italic 11px/14px verdana, helvetica, sans-serif;</tt>)
269269
* into their constituent parts.
@@ -310,10 +310,11 @@ public function expandFontShorthand()
310310
){
311311
$aFontProperties['font-weight'] = $aValues;
312312
}
313-
else if($mValue instanceof CSSSlashedValue)
313+
else if($mValue instanceof CSSRuleValueList && $mValue->getListSeparator() === '/')
314314
{
315-
$aFontProperties['font-size'] = array($mValue->getValue1());
316-
$aFontProperties['line-height'] = array($mValue->getValue2());
315+
list($oSize, $oHeight) = $mValue->getListComponents();
316+
$aFontProperties['font-size'] = $oSize;
317+
$aFontProperties['line-height'] = $oHeight;
317318
}
318319
else if($mValue instanceof CSSSize && $mValue->getUnit() !== null)
319320
{
@@ -601,7 +602,9 @@ public function createFontShorthand()
601602
$aLHValues = $aRules['line-height']->getValues();
602603
if($aLHValues[0][0] !== 'normal')
603604
{
604-
$val = new CSSSlashedValue($aFSValues[0][0], $aLHValues[0][0]);
605+
$val = new CSSRuleValueList('/');
606+
$val->addListComponent($aFSValues[0][0]);
607+
$val->addListComponent($aLHValues[0][0]);
605608
$oNewRule->addValue(array($val));
606609
}
607610
}
@@ -611,7 +614,9 @@ public function createFontShorthand()
611614
}
612615

613616
$aFFValues = $aRules['font-family']->getValues();
614-
$oNewRule->addValue($aFFValues[0]);
617+
$oFFValue = new CSSRuleValueList(',');
618+
$oFFValue->setListComponents($aFFValues[0]);
619+
$oNewRule->addValue($oFFValue);
615620

616621
$this->addRule($oNewRule);
617622
foreach ($aFontProperties as $sProperty)

lib/CSSValueList.php

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,43 @@ abstract class CSSValueList extends CSSValue {
55
protected $sSeparator;
66

77
public function __construct($aComponents = array(), $sSeparator = ',') {
8+
if($aComponents instanceof CSSValueList && $aComponents->getListSeparator() === $sSeparator) {
9+
$aComponents = $aComponents->getListComponents();
10+
} else if(!is_array($aComponents)) {
11+
$aComponents = array($aComponents);
12+
}
813
$this->aComponents = $aComponents;
914
$this->sSeparator = $sSeparator;
1015
}
1116

17+
public function addListComponent($mComponent) {
18+
$this->aComponents[] = $mComponent;
19+
}
20+
1221
public function getListComponents() {
1322
return $this->aComponents;
1423
}
24+
25+
public function setListComponents($aComponents) {
26+
$this->aComponents = $aComponents;
27+
}
1528

1629
public function getListSeparator() {
1730
return $this->sSeparator;
1831
}
1932

33+
public function setListSeparator($sSeparator) {
34+
$this->sSeparator = $sSeparator;
35+
}
36+
2037
function __toString() {
2138
return implode($this->sSeparator, $this->aComponents);
2239
}
2340
}
2441

25-
class CSSSlashedValue extends CSSValueList {
26-
public function __construct($oValue1, $oValue2) {
27-
parent::__construct(array($oValue1, $oValue2), '/');
28-
}
29-
30-
public function getValue1() {
31-
return $this->aComponents[0];
32-
}
33-
34-
public function getValue2() {
35-
return $this->aComponents[1];
42+
class CSSRuleValueList extends CSSValueList {
43+
public function __construct($sSeparator = ',') {
44+
parent::__construct(array(), $sSeparator);
3645
}
3746
}
3847

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

59+
public function setName($sName) {
60+
$this->sName = $sName;
61+
}
62+
5063
public function getArguments() {
5164
return $this->aComponents;
5265
}
@@ -65,6 +78,11 @@ public function __construct($aColor) {
6578
public function getColor() {
6679
return $this->aComponents;
6780
}
81+
82+
public function setColor($aColor) {
83+
$this->setName(implode('', array_keys($aColor)));
84+
$this->aComponents = $aColor;
85+
}
6886

6987
public function getColorDescription() {
7088
return $this->getName();

0 commit comments

Comments
 (0)