/** * Merge multiple Css RuleSets by cascading according to the Css 3 cascading rules * (http://www.w3.org/TR/REC-CSS2/cascade.html#cascading-order). * * Cascading: * If a Css\StyleRule object has its +specificity+ defined, that specificity is * used in the cascade calculations. * * If no specificity is explicitly set and the Css\StyleRule has *one* selector, * the specificity is calculated using that selector. * * If no selectors or multiple selectors are present, the specificity is * treated as 0. * * * @param array $rules An array of Css\StyleRule objects * @return ju1ius\Css\StyleRule The merged ju1ius\Css\StyleRule * **/ public static function merge(array $rules) { if (1 === count($rules)) { if (!$rules[0] instanceof StyleRule) { throw new \InvalidArgumentException('You must provide an array of ju1ius\\Css\\StyleRule objects'); } return clone $rules[0]; } // Internal storage of Css properties that we will keep $aProperties = array(); foreach ($rules as $rule) { if (!$rule instanceof StyleRule) { throw new \InvalidArgumentException('You must provide an array of ju1ius\\Css\\StyleRule objects'); } $styleDeclaration = $rule->getStyleDeclaration(); $selectorList = $rule->getSelectorList(); $specificity = 0; // $styleDeclaration->expandShorthands(); if (1 === count($selectorList)) { $specificity = $selectorList[0]->getSpecificity(); } // foreach ($styleDeclaration->getAppliedProperties() as $name => $property) { // Add the property to the list to be folded per // http://www.w3.org/TR/css3-cascade/#cascading $override = false; $isImportant = $property->getIsImportant(); if (isset($aProperties[$name])) { $oldProp = $aProperties[$name]; // properties have same weight so we consider specificity if ($isImportant === $oldProp['property']->getIsImportant()) { if ($specificity >= $oldProp['specificity']) { $override = true; } } else { if ($isImportant) { $override = true; } } } else { $override = true; } if ($override) { $aProperties[$name] = array('property' => Object::getClone($property), 'specificity' => $specificity); } } } $merged = new StyleDeclaration(); foreach ($aProperties as $name => $details) { $merged->append($details['property']); } $merged->createShorthands(); return new StyleRule(new SelectorList(), $merged); }
private function _createShorthandProperties(array $aProperties, $sShorthand, $bSafe = false) { if ($bSafe) { $bCanProceed = $this->_safeCleanup($aProperties, $sShorthand); } else { $bCanProceed = $this->_destructiveCleanup($aProperties, $sShorthand); } if (!$bCanProceed) { return; } // Now we collapse the rules $aNewValues = array('normal' => array(), 'important' => array()); $aOldProperties = array('normal' => array(), 'important' => array()); foreach ($aProperties as $sProperty) { $aProperties = $this->styleDeclaration->getProperties($sProperty); foreach ($aProperties as $iPos => $oProperty) { $aValues = $oProperty->getValueList()->getItems(); $sDest = $oProperty->getIsImportant() ? 'important' : 'normal'; $aOldProperties[$sDest][] = $iPos; foreach ($aValues as $mValue) { $aNewValues[$sDest][] = Object::getClone($mValue); } } } $iImportantCount = count($aNewValues['important']); $iNormalCount = count($aNewValues['normal']); // Merge important values only if no normal values are present if ($iNormalCount) { $this->_mergeValues($sShorthand, $aNewValues['normal'], $aOldProperties['normal'], false); } else { if ($iImportantCount) { $this->_mergeValues($sShorthand, $aNewValues['important'], $aOldProperties['important'], true); } } }
private function _expandBackgroundShorthand($iPos, $oProperty) { /*{{{*/ $oValueList = $oProperty->getValueList(); // Get a normalized array if ($oValueList->getSeparator() === ',' && count($oValueList) > 1) { // we have multiple layers $aValueList = $oValueList->getItems(); } else { // we have only one value or a space separated list of values $aValueList = array($oValueList->getItems()); } $iNumLayers = count($aValueList); $aUnfoldedResults = array(); // background-color only allowed on final layer; $color = null; foreach ($aValueList as $iLayerIndex => $aValues) { // if we have multiple layers, get the values for this layer if ($aValues instanceof PropertyValueList) { $aValues = $aValues->getItems(); } else { if (!is_array($aValues)) { $aValues = array($aValues); } } $aBgProperties = array(); $iNumBgPos = 0; $iNumBoxValues = 0; foreach ($aValues as $mValue) { $mValue = Object::getClone($mValue); if ($mValue instanceof Value\Url || $mValue instanceof Value\Func || $mValue == "none") { $aBgProperties['background-image'] = $mValue; } else { if ($mValue instanceof PropertyValueList) { // bg-pos bg-pos? / bg-size bg-size? $oBgPosValues = $mValue->getFirst(); if ($oBgPosValues instanceof PropertyValueList) { $aBgPosValues = $oBgPosValues->getItems(); } else { $aBgPosValues = array($oBgPosValues); } $bgpos_valuelist = new PropertyValueList(array($aBgPosValues[0], 'center'), ' '); if (count($aBgPosValues) > 1) { $bgpos_valuelist->replace(1, $aBgPosValues[1]); } $aBgProperties['background-position'] = $bgpos_valuelist; // $oBgSizeValues = $mValue->getLast(); if ($oBgSizeValues instanceof PropertyValueList) { $aBgSizeValues = $oBgSizeValues->getItems(); } else { $aBgSizeValues = array($oBgSizeValues); } $bgsize_valuelist = new PropertyValueList(array($aBgSizeValues[0], $aBgSizeValues[0]), ' '); if (count($aBgSizeValues) > 1) { $bgsize_valuelist->replace(1, $aBgSizeValues[1]); } $aBgProperties['background-size'] = $bgsize_valuelist; } else { if (in_array($mValue, array('left', 'center', 'right', 'top', 'bottom')) || $mValue instanceof Value\Dimension) { //if ($mValue instanceof Value\Dimension) $mValue = clone $mValue; if ($iNumBgPos === 0) { $aBgProperties['background-position'] = new PropertyValueList(array($mValue, 'center'), ' '); } else { $aBgProperties['background-position']->replace(1, $mValue); } $iNumBgPos++; } else { if (in_array($mValue, array('repeat', 'no-repeat', 'repeat-x', 'repeat-y', 'space', 'round'))) { $aBgProperties['background-repeat'] = $mValue; } else { if (in_array($mValue, array('scroll', 'fixed', 'local'))) { $aBgProperties['background-attachment'] = $mValue; } else { if (in_array($mValue, array('border-box', 'padding-box', 'content-box'))) { if ($iNumBoxValues === 0) { $aBgProperties['background-origin'] = $mValue; $aBgProperties['background-clip'] = $mValue; } else { $aBgProperties['background-clip'] = $mValue; } $iNumBoxValues++; } else { if ($mValue instanceof Value\Color) { if ($iLayerIndex == $iNumLayers - 1) { $color = $mValue; } else { if (empty($aBgProperties)) { $aBgProperties['background-image'] = "none"; } } } } } } } } } } $aUnfoldedResults[] = $aBgProperties; } if ($color) { $aUnfoldedResults[$iNumLayers - 1]['background-color'] = $color; } $aFoldedResults = array(); foreach ($aUnfoldedResults as $i => $result) { foreach ($result as $propname => $propval) { $aFoldedResults[$propname][$i] = $propval; } } foreach ($aFoldedResults as $propname => $aValues) { if ($this->_canAddShorthandExpansion($oProperty, $propname)) { $separator = count($aValues) === 0 ? ' ' : ','; $oValueList = new PropertyValueList($aValues, $separator); $oNewProp = new Property($propname, $oValueList); $oNewProp->setIsImportant($oProperty->getIsImportant()); $this->styleDeclaration->insertAfter($oNewProp, $oProperty); } } $this->styleDeclaration->remove($oProperty); }