From 8ca6cc32b2c789a3149861159ad258f2cb9491e3 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 14:39:39 +0200 Subject: Adding upstream version 2.11.4. Signed-off-by: Daniel Baumann --- .../lessphp/lib/Less/Visitor/extendFinder.php | 109 +++++ library/vendor/lessphp/lib/Less/Visitor/import.php | 137 +++++++ .../lessphp/lib/Less/Visitor/joinSelector.php | 68 ++++ .../lessphp/lib/Less/Visitor/processExtends.php | 441 +++++++++++++++++++++ library/vendor/lessphp/lib/Less/Visitor/toCSS.php | 280 +++++++++++++ 5 files changed, 1035 insertions(+) create mode 100644 library/vendor/lessphp/lib/Less/Visitor/extendFinder.php create mode 100644 library/vendor/lessphp/lib/Less/Visitor/import.php create mode 100644 library/vendor/lessphp/lib/Less/Visitor/joinSelector.php create mode 100644 library/vendor/lessphp/lib/Less/Visitor/processExtends.php create mode 100644 library/vendor/lessphp/lib/Less/Visitor/toCSS.php (limited to 'library/vendor/lessphp/lib/Less/Visitor') diff --git a/library/vendor/lessphp/lib/Less/Visitor/extendFinder.php b/library/vendor/lessphp/lib/Less/Visitor/extendFinder.php new file mode 100644 index 0000000..7d5d3fd --- /dev/null +++ b/library/vendor/lessphp/lib/Less/Visitor/extendFinder.php @@ -0,0 +1,109 @@ +contexts = array(); + $this->allExtendsStack = array( array() ); + parent::__construct(); + } + + /** + * @param Less_Tree_Ruleset $root + */ + public function run( $root ) { + $root = $this->visitObj( $root ); + $root->allExtends =& $this->allExtendsStack[0]; + return $root; + } + + public function visitRule( $ruleNode, &$visitDeeper ) { + $visitDeeper = false; + } + + public function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ) { + $visitDeeper = false; + } + + public function visitRuleset( $rulesetNode ) { + if ( $rulesetNode->root ) { + return; + } + + $allSelectorsExtendList = array(); + + // get &:extend(.a); rules which apply to all selectors in this ruleset + if ( $rulesetNode->rules ) { + foreach ( $rulesetNode->rules as $rule ) { + if ( $rule instanceof Less_Tree_Extend ) { + $allSelectorsExtendList[] = $rule; + $rulesetNode->extendOnEveryPath = true; + } + } + } + + // now find every selector and apply the extends that apply to all extends + // and the ones which apply to an individual extend + foreach ( $rulesetNode->paths as $selectorPath ) { + $selector = end( $selectorPath ); // $selectorPath[ count($selectorPath)-1]; + + $j = 0; + foreach ( $selector->extendList as $extend ) { + $this->allExtendsStackPush( $rulesetNode, $selectorPath, $extend, $j ); + } + foreach ( $allSelectorsExtendList as $extend ) { + $this->allExtendsStackPush( $rulesetNode, $selectorPath, $extend, $j ); + } + } + + $this->contexts[] = $rulesetNode->selectors; + } + + public function allExtendsStackPush( $rulesetNode, $selectorPath, $extend, &$j ) { + $this->foundExtends = true; + $extend = clone $extend; + $extend->findSelfSelectors( $selectorPath ); + $extend->ruleset = $rulesetNode; + if ( $j === 0 ) { + $extend->firstExtendOnThisSelectorPath = true; + } + + $end_key = count( $this->allExtendsStack ) - 1; + $this->allExtendsStack[$end_key][] = $extend; + $j++; + } + + public function visitRulesetOut( $rulesetNode ) { + if ( !is_object( $rulesetNode ) || !$rulesetNode->root ) { + array_pop( $this->contexts ); + } + } + + public function visitMedia( $mediaNode ) { + $mediaNode->allExtends = array(); + $this->allExtendsStack[] =& $mediaNode->allExtends; + } + + public function visitMediaOut() { + array_pop( $this->allExtendsStack ); + } + + public function visitDirective( $directiveNode ) { + $directiveNode->allExtends = array(); + $this->allExtendsStack[] =& $directiveNode->allExtends; + } + + public function visitDirectiveOut() { + array_pop( $this->allExtendsStack ); + } +} diff --git a/library/vendor/lessphp/lib/Less/Visitor/import.php b/library/vendor/lessphp/lib/Less/Visitor/import.php new file mode 100644 index 0000000..7af96eb --- /dev/null +++ b/library/vendor/lessphp/lib/Less/Visitor/import.php @@ -0,0 +1,137 @@ +env = $evalEnv; + $this->importCount = 0; + parent::__construct(); + } + + + function run( $root ){ + $root = $this->visitObj($root); + $this->isFinished = true; + + //if( $this->importCount === 0) { + // $this->_finish(); + //} + } + + function visitImport($importNode, &$visitDeeper ){ + $importVisitor = $this; + $inlineCSS = $importNode->options['inline']; + + if( !$importNode->css || $inlineCSS ){ + $evaldImportNode = $importNode->compileForImport($this->env); + + if( $evaldImportNode && (!$evaldImportNode->css || $inlineCSS) ){ + $importNode = $evaldImportNode; + $this->importCount++; + $env = clone $this->env; + + if( (isset($importNode->options['multiple']) && $importNode->options['multiple']) ){ + $env->importMultiple = true; + } + + //get path & uri + $path_and_uri = null; + if( is_callable(Less_Parser::$options['import_callback']) ){ + $path_and_uri = call_user_func(Less_Parser::$options['import_callback'],$importNode); + } + + if( !$path_and_uri ){ + $path_and_uri = $importNode->PathAndUri(); + } + + if( $path_and_uri ){ + list($full_path, $uri) = $path_and_uri; + }else{ + $full_path = $uri = $importNode->getPath(); + } + + + //import once + if( $importNode->skip( $full_path, $env) ){ + return array(); + } + + if( $importNode->options['inline'] ){ + //todo needs to reference css file not import + //$contents = new Less_Tree_Anonymous($importNode->root, 0, array('filename'=>$importNode->importedFilename), true ); + + Less_Parser::AddParsedFile($full_path); + $contents = new Less_Tree_Anonymous( file_get_contents($full_path), 0, array(), true ); + + if( $importNode->features ){ + return new Less_Tree_Media( array($contents), $importNode->features->value ); + } + + return array( $contents ); + } + + + // css ? + if( $importNode->css ){ + $features = ( $importNode->features ? $importNode->features->compile($env) : null ); + return new Less_Tree_Import( $importNode->compilePath( $env), $features, $importNode->options, $this->index); + } + + return $importNode->ParseImport( $full_path, $uri, $env ); + } + + } + + $visitDeeper = false; + return $importNode; + } + + + function visitRule( $ruleNode, &$visitDeeper ){ + $visitDeeper = false; + return $ruleNode; + } + + function visitDirective($directiveNode, $visitArgs){ + array_unshift($this->env->frames,$directiveNode); + return $directiveNode; + } + + function visitDirectiveOut($directiveNode) { + array_shift($this->env->frames); + } + + function visitMixinDefinition($mixinDefinitionNode, $visitArgs) { + array_unshift($this->env->frames,$mixinDefinitionNode); + return $mixinDefinitionNode; + } + + function visitMixinDefinitionOut($mixinDefinitionNode) { + array_shift($this->env->frames); + } + + function visitRuleset($rulesetNode, $visitArgs) { + array_unshift($this->env->frames,$rulesetNode); + return $rulesetNode; + } + + function visitRulesetOut($rulesetNode) { + array_shift($this->env->frames); + } + + function visitMedia($mediaNode, $visitArgs) { + array_unshift($this->env->frames, $mediaNode->ruleset); + return $mediaNode; + } + + function visitMediaOut($mediaNode) { + array_shift($this->env->frames); + } + +} +*/ diff --git a/library/vendor/lessphp/lib/Less/Visitor/joinSelector.php b/library/vendor/lessphp/lib/Less/Visitor/joinSelector.php new file mode 100644 index 0000000..bb08ece --- /dev/null +++ b/library/vendor/lessphp/lib/Less/Visitor/joinSelector.php @@ -0,0 +1,68 @@ +visitObj( $root ); + } + + public function visitRule( $ruleNode, &$visitDeeper ) { + $visitDeeper = false; + } + + public function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ) { + $visitDeeper = false; + } + + public function visitRuleset( $rulesetNode ) { + $paths = array(); + + if ( !$rulesetNode->root ) { + $selectors = array(); + + if ( $rulesetNode->selectors && $rulesetNode->selectors ) { + foreach ( $rulesetNode->selectors as $selector ) { + if ( $selector->getIsOutput() ) { + $selectors[] = $selector; + } + } + } + + if ( !$selectors ) { + $rulesetNode->selectors = null; + $rulesetNode->rules = null; + } else { + $context = end( $this->contexts ); // $context = $this->contexts[ count($this->contexts) - 1]; + $paths = $rulesetNode->joinSelectors( $context, $selectors ); + } + + $rulesetNode->paths = $paths; + } + + $this->contexts[] = $paths; // different from less.js. Placed after joinSelectors() so that $this->contexts will get correct $paths + } + + public function visitRulesetOut() { + array_pop( $this->contexts ); + } + + public function visitMedia( $mediaNode ) { + $context = end( $this->contexts ); // $context = $this->contexts[ count($this->contexts) - 1]; + + if ( !count( $context ) || ( is_object( $context[0] ) && $context[0]->multiMedia ) ) { + $mediaNode->rules[0]->root = true; + } + } + +} diff --git a/library/vendor/lessphp/lib/Less/Visitor/processExtends.php b/library/vendor/lessphp/lib/Less/Visitor/processExtends.php new file mode 100644 index 0000000..b626a48 --- /dev/null +++ b/library/vendor/lessphp/lib/Less/Visitor/processExtends.php @@ -0,0 +1,441 @@ +run( $root ); + if ( !$extendFinder->foundExtends ) { + return $root; + } + + $root->allExtends = $this->doExtendChaining( $root->allExtends, $root->allExtends ); + + $this->allExtendsStack = array(); + $this->allExtendsStack[] = &$root->allExtends; + + return $this->visitObj( $root ); + } + + private function doExtendChaining( $extendsList, $extendsListTarget, $iterationCount = 0 ) { + // + // chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting + // the selector we would do normally, but we are also adding an extend with the same target selector + // this means this new extend can then go and alter other extends + // + // this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors + // this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if + // we look at each selector at a time, as is done in visitRuleset + + $extendsToAdd = array(); + + // loop through comparing every extend with every target extend. + // a target extend is the one on the ruleset we are looking at copy/edit/pasting in place + // e.g. .a:extend(.b) {} and .b:extend(.c) {} then the first extend extends the second one + // and the second is the target. + // the separation into two lists allows us to process a subset of chains with a bigger set, as is the + // case when processing media queries + for ( $extendIndex = 0, $extendsList_len = count( $extendsList ); $extendIndex < $extendsList_len; $extendIndex++ ) { + for ( $targetExtendIndex = 0; $targetExtendIndex < count( $extendsListTarget ); $targetExtendIndex++ ) { + + $extend = $extendsList[$extendIndex]; + $targetExtend = $extendsListTarget[$targetExtendIndex]; + + // Optimisation: Explicit reference,  + if ( \array_key_exists( $targetExtend->object_id, $extend->parent_ids ) ) { + // ignore circular references + continue; + } + + // find a match in the target extends self selector (the bit before :extend) + $selectorPath = array( $targetExtend->selfSelectors[0] ); + $matches = $this->findMatch( $extend, $selectorPath ); + + if ( $matches ) { + + // we found a match, so for each self selector.. + foreach ( $extend->selfSelectors as $selfSelector ) { + + // process the extend as usual + $newSelector = $this->extendSelector( $matches, $selectorPath, $selfSelector ); + + // but now we create a new extend from it + $newExtend = new Less_Tree_Extend( $targetExtend->selector, $targetExtend->option, 0 ); + $newExtend->selfSelectors = $newSelector; + + // add the extend onto the list of extends for that selector + end( $newSelector )->extendList = array( $newExtend ); + // $newSelector[ count($newSelector)-1]->extendList = array($newExtend); + + // record that we need to add it. + $extendsToAdd[] = $newExtend; + $newExtend->ruleset = $targetExtend->ruleset; + + // remember its parents for circular references + $newExtend->parent_ids = array_merge( $newExtend->parent_ids, $targetExtend->parent_ids, $extend->parent_ids ); + + // only process the selector once.. if we have :extend(.a,.b) then multiple + // extends will look at the same selector path, so when extending + // we know that any others will be duplicates in terms of what is added to the css + if ( $targetExtend->firstExtendOnThisSelectorPath ) { + $newExtend->firstExtendOnThisSelectorPath = true; + $targetExtend->ruleset->paths[] = $newSelector; + } + } + } + } + } + + if ( $extendsToAdd ) { + // try to detect circular references to stop a stack overflow. + // may no longer be needed. $this->extendChainCount++; + if ( $iterationCount > 100 ) { + + try{ + $selectorOne = $extendsToAdd[0]->selfSelectors[0]->toCSS(); + $selectorTwo = $extendsToAdd[0]->selector->toCSS(); + }catch ( Exception $e ) { + $selectorOne = "{unable to calculate}"; + $selectorTwo = "{unable to calculate}"; + } + + throw new Less_Exception_Parser( "extend circular reference detected. One of the circular extends is currently:" . $selectorOne . ":extend(" . $selectorTwo . ")" ); + } + + // now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e... + $extendsToAdd = $this->doExtendChaining( $extendsToAdd, $extendsListTarget, $iterationCount + 1 ); + } + + return array_merge( $extendsList, $extendsToAdd ); + } + + protected function visitRule( $ruleNode, &$visitDeeper ) { + $visitDeeper = false; + } + + protected function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ) { + $visitDeeper = false; + } + + protected function visitSelector( $selectorNode, &$visitDeeper ) { + $visitDeeper = false; + } + + protected function visitRuleset( $rulesetNode ) { + if ( $rulesetNode->root ) { + return; + } + + $allExtends = end( $this->allExtendsStack ); + $paths_len = count( $rulesetNode->paths ); + + // look at each selector path in the ruleset, find any extend matches and then copy, find and replace + foreach ( $allExtends as $allExtend ) { + for ( $pathIndex = 0; $pathIndex < $paths_len; $pathIndex++ ) { + + // extending extends happens initially, before the main pass + if ( isset( $rulesetNode->extendOnEveryPath ) && $rulesetNode->extendOnEveryPath ) { + continue; + } + + $selectorPath = $rulesetNode->paths[$pathIndex]; + + if ( end( $selectorPath )->extendList ) { + continue; + } + + $this->ExtendMatch( $rulesetNode, $allExtend, $selectorPath ); + + } + } + } + + private function ExtendMatch( $rulesetNode, $extend, $selectorPath ) { + $matches = $this->findMatch( $extend, $selectorPath ); + + if ( $matches ) { + foreach ( $extend->selfSelectors as $selfSelector ) { + $rulesetNode->paths[] = $this->extendSelector( $matches, $selectorPath, $selfSelector ); + } + } + } + + private function findMatch( $extend, $haystackSelectorPath ) { + if ( !$this->HasMatches( $extend, $haystackSelectorPath ) ) { + return false; + } + + // + // look through the haystack selector path to try and find the needle - extend.selector + // returns an array of selector matches that can then be replaced + // + $needleElements = $extend->selector->elements; + $potentialMatches = array(); + $potentialMatches_len = 0; + $potentialMatch = null; + $matches = array(); + + // loop through the haystack elements + $haystack_path_len = count( $haystackSelectorPath ); + for ( $haystackSelectorIndex = 0; $haystackSelectorIndex < $haystack_path_len; $haystackSelectorIndex++ ) { + $hackstackSelector = $haystackSelectorPath[$haystackSelectorIndex]; + + $haystack_elements_len = count( $hackstackSelector->elements ); + for ( $hackstackElementIndex = 0; $hackstackElementIndex < $haystack_elements_len; $hackstackElementIndex++ ) { + + $haystackElement = $hackstackSelector->elements[$hackstackElementIndex]; + + // if we allow elements before our match we can add a potential match every time. otherwise only at the first element. + if ( $extend->allowBefore || ( $haystackSelectorIndex === 0 && $hackstackElementIndex === 0 ) ) { + $potentialMatches[] = array( 'pathIndex' => $haystackSelectorIndex, 'index' => $hackstackElementIndex, 'matched' => 0, 'initialCombinator' => $haystackElement->combinator ); + $potentialMatches_len++; + } + + for ( $i = 0; $i < $potentialMatches_len; $i++ ) { + + $potentialMatch = &$potentialMatches[$i]; + $potentialMatch = $this->PotentialMatch( $potentialMatch, $needleElements, $haystackElement, $hackstackElementIndex ); + + // if we are still valid and have finished, test whether we have elements after and whether these are allowed + if ( $potentialMatch && $potentialMatch['matched'] === $extend->selector->elements_len ) { + $potentialMatch['finished'] = true; + + if ( !$extend->allowAfter && ( $hackstackElementIndex + 1 < $haystack_elements_len || $haystackSelectorIndex + 1 < $haystack_path_len ) ) { + $potentialMatch = null; + } + } + + // if null we remove, if not, we are still valid, so either push as a valid match or continue + if ( $potentialMatch ) { + if ( $potentialMatch['finished'] ) { + $potentialMatch['length'] = $extend->selector->elements_len; + $potentialMatch['endPathIndex'] = $haystackSelectorIndex; + $potentialMatch['endPathElementIndex'] = $hackstackElementIndex + 1; // index after end of match + $potentialMatches = array(); // we don't allow matches to overlap, so start matching again + $potentialMatches_len = 0; + $matches[] = $potentialMatch; + } + continue; + } + + array_splice( $potentialMatches, $i, 1 ); + $potentialMatches_len--; + $i--; + } + } + } + + return $matches; + } + + // Before going through all the nested loops, lets check to see if a match is possible + // Reduces Bootstrap 3.1 compile time from ~6.5s to ~5.6s + private function HasMatches( $extend, $haystackSelectorPath ) { + if ( !$extend->selector->cacheable ) { + return true; + } + + $first_el = $extend->selector->_oelements[0]; + + foreach ( $haystackSelectorPath as $hackstackSelector ) { + if ( !$hackstackSelector->cacheable ) { + return true; + } + + // Optimisation: Explicit reference,  + if ( \array_key_exists( $first_el, $hackstackSelector->_oelements_assoc ) ) { + return true; + } + } + + return false; + } + + /** + * @param integer $hackstackElementIndex + */ + private function PotentialMatch( $potentialMatch, $needleElements, $haystackElement, $hackstackElementIndex ) { + if ( $potentialMatch['matched'] > 0 ) { + + // selectors add " " onto the first element. When we use & it joins the selectors together, but if we don't + // then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to work out + // what the resulting combinator will be + $targetCombinator = $haystackElement->combinator; + if ( $targetCombinator === '' && $hackstackElementIndex === 0 ) { + $targetCombinator = ' '; + } + + if ( $needleElements[ $potentialMatch['matched'] ]->combinator !== $targetCombinator ) { + return null; + } + } + + // if we don't match, null our match to indicate failure + if ( !$this->isElementValuesEqual( $needleElements[$potentialMatch['matched'] ]->value, $haystackElement->value ) ) { + return null; + } + + $potentialMatch['finished'] = false; + $potentialMatch['matched']++; + + return $potentialMatch; + } + + private function isElementValuesEqual( $elementValue1, $elementValue2 ) { + if ( $elementValue1 === $elementValue2 ) { + return true; + } + + if ( is_string( $elementValue1 ) || is_string( $elementValue2 ) ) { + return false; + } + + if ( $elementValue1 instanceof Less_Tree_Attribute ) { + return $this->isAttributeValuesEqual( $elementValue1, $elementValue2 ); + } + + $elementValue1 = $elementValue1->value; + if ( $elementValue1 instanceof Less_Tree_Selector ) { + return $this->isSelectorValuesEqual( $elementValue1, $elementValue2 ); + } + + return false; + } + + /** + * @param Less_Tree_Selector $elementValue1 + */ + private function isSelectorValuesEqual( $elementValue1, $elementValue2 ) { + $elementValue2 = $elementValue2->value; + if ( !( $elementValue2 instanceof Less_Tree_Selector ) || $elementValue1->elements_len !== $elementValue2->elements_len ) { + return false; + } + + for ( $i = 0; $i < $elementValue1->elements_len; $i++ ) { + + if ( $elementValue1->elements[$i]->combinator !== $elementValue2->elements[$i]->combinator ) { + if ( $i !== 0 || ( $elementValue1->elements[$i]->combinator || ' ' ) !== ( $elementValue2->elements[$i]->combinator || ' ' ) ) { + return false; + } + } + + if ( !$this->isElementValuesEqual( $elementValue1->elements[$i]->value, $elementValue2->elements[$i]->value ) ) { + return false; + } + } + + return true; + } + + /** + * @param Less_Tree_Attribute $elementValue1 + */ + private function isAttributeValuesEqual( $elementValue1, $elementValue2 ) { + if ( $elementValue1->op !== $elementValue2->op || $elementValue1->key !== $elementValue2->key ) { + return false; + } + + if ( !$elementValue1->value || !$elementValue2->value ) { + if ( $elementValue1->value || $elementValue2->value ) { + return false; + } + return true; + } + + $elementValue1 = ( $elementValue1->value->value ?: $elementValue1->value ); + $elementValue2 = ( $elementValue2->value->value ?: $elementValue2->value ); + + return $elementValue1 === $elementValue2; + } + + private function extendSelector( $matches, $selectorPath, $replacementSelector ) { + // for a set of matches, replace each match with the replacement selector + + $currentSelectorPathIndex = 0; + $currentSelectorPathElementIndex = 0; + $path = array(); + $selectorPath_len = count( $selectorPath ); + + for ( $matchIndex = 0, $matches_len = count( $matches ); $matchIndex < $matches_len; $matchIndex++ ) { + + $match = $matches[$matchIndex]; + $selector = $selectorPath[ $match['pathIndex'] ]; + + $firstElement = new Less_Tree_Element( + $match['initialCombinator'], + $replacementSelector->elements[0]->value, + $replacementSelector->elements[0]->index, + $replacementSelector->elements[0]->currentFileInfo + ); + + if ( $match['pathIndex'] > $currentSelectorPathIndex && $currentSelectorPathElementIndex > 0 ) { + $last_path = end( $path ); + $last_path->elements = array_merge( $last_path->elements, array_slice( $selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex ) ); + $currentSelectorPathElementIndex = 0; + $currentSelectorPathIndex++; + } + + $newElements = array_merge( + array_slice( $selector->elements, $currentSelectorPathElementIndex, ( $match['index'] - $currentSelectorPathElementIndex ) ), // last parameter of array_slice is different than the last parameter of javascript's slice + array( $firstElement ), + array_slice( $replacementSelector->elements, 1 ) + ); + + if ( $currentSelectorPathIndex === $match['pathIndex'] && $matchIndex > 0 ) { + $last_key = count( $path ) - 1; + $path[$last_key]->elements = array_merge( $path[$last_key]->elements, $newElements ); + } else { + $path = array_merge( $path, array_slice( $selectorPath, $currentSelectorPathIndex, $match['pathIndex'] ) ); + $path[] = new Less_Tree_Selector( $newElements ); + } + + $currentSelectorPathIndex = $match['endPathIndex']; + $currentSelectorPathElementIndex = $match['endPathElementIndex']; + if ( $currentSelectorPathElementIndex >= count( $selectorPath[$currentSelectorPathIndex]->elements ) ) { + $currentSelectorPathElementIndex = 0; + $currentSelectorPathIndex++; + } + } + + if ( $currentSelectorPathIndex < $selectorPath_len && $currentSelectorPathElementIndex > 0 ) { + $last_path = end( $path ); + $last_path->elements = array_merge( $last_path->elements, array_slice( $selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex ) ); + $currentSelectorPathIndex++; + } + + $slice_len = $selectorPath_len - $currentSelectorPathIndex; + $path = array_merge( $path, array_slice( $selectorPath, $currentSelectorPathIndex, $slice_len ) ); + + return $path; + } + + protected function visitMedia( $mediaNode ) { + $newAllExtends = array_merge( $mediaNode->allExtends, end( $this->allExtendsStack ) ); + $this->allExtendsStack[] = $this->doExtendChaining( $newAllExtends, $mediaNode->allExtends ); + } + + protected function visitMediaOut() { + array_pop( $this->allExtendsStack ); + } + + protected function visitDirective( $directiveNode ) { + $newAllExtends = array_merge( $directiveNode->allExtends, end( $this->allExtendsStack ) ); + $this->allExtendsStack[] = $this->doExtendChaining( $newAllExtends, $directiveNode->allExtends ); + } + + protected function visitDirectiveOut() { + array_pop( $this->allExtendsStack ); + } + +} diff --git a/library/vendor/lessphp/lib/Less/Visitor/toCSS.php b/library/vendor/lessphp/lib/Less/Visitor/toCSS.php new file mode 100644 index 0000000..e90f211 --- /dev/null +++ b/library/vendor/lessphp/lib/Less/Visitor/toCSS.php @@ -0,0 +1,280 @@ +visitObj( $root ); + } + + public function visitRule( $ruleNode ) { + if ( $ruleNode->variable ) { + return array(); + } + return $ruleNode; + } + + public function visitMixinDefinition( $mixinNode ) { + // mixin definitions do not get eval'd - this means they keep state + // so we have to clear that state here so it isn't used if toCSS is called twice + $mixinNode->frames = array(); + return array(); + } + + public function visitExtend() { + return array(); + } + + public function visitComment( $commentNode ) { + if ( $commentNode->isSilent() ) { + return array(); + } + return $commentNode; + } + + public function visitMedia( $mediaNode, &$visitDeeper ) { + $mediaNode->accept( $this ); + $visitDeeper = false; + + if ( !$mediaNode->rules ) { + return array(); + } + return $mediaNode; + } + + public function visitDirective( $directiveNode ) { + if ( isset( $directiveNode->currentFileInfo['reference'] ) && ( !property_exists( $directiveNode, 'isReferenced' ) || !$directiveNode->isReferenced ) ) { + return array(); + } + if ( $directiveNode->name === '@charset' ) { + // Only output the debug info together with subsequent @charset definitions + // a comment (or @media statement) before the actual @charset directive would + // be considered illegal css as it has to be on the first line + if ( isset( $this->charset ) && $this->charset ) { + + // if( $directiveNode->debugInfo ){ + // $comment = new Less_Tree_Comment('/* ' . str_replace("\n",'',$directiveNode->toCSS())." */\n"); + // $comment->debugInfo = $directiveNode->debugInfo; + // return $this->visit($comment); + //} + + return array(); + } + $this->charset = true; + } + return $directiveNode; + } + + public function checkPropertiesInRoot( $rulesetNode ) { + if ( !$rulesetNode->firstRoot ) { + return; + } + + foreach ( $rulesetNode->rules as $ruleNode ) { + if ( $ruleNode instanceof Less_Tree_Rule && !$ruleNode->variable ) { + $msg = "properties must be inside selector blocks, they cannot be in the root. Index ".$ruleNode->index.( $ruleNode->currentFileInfo ? ( ' Filename: '.$ruleNode->currentFileInfo['filename'] ) : null ); + throw new Less_Exception_Compiler( $msg ); + } + } + } + + public function visitRuleset( $rulesetNode, &$visitDeeper ) { + $visitDeeper = false; + + $this->checkPropertiesInRoot( $rulesetNode ); + + if ( $rulesetNode->root ) { + return $this->visitRulesetRoot( $rulesetNode ); + } + + $rulesets = array(); + $rulesetNode->paths = $this->visitRulesetPaths( $rulesetNode ); + + // Compile rules and rulesets + $nodeRuleCnt = $rulesetNode->rules ? count( $rulesetNode->rules ) : 0; + for ( $i = 0; $i < $nodeRuleCnt; ) { + $rule = $rulesetNode->rules[$i]; + + if ( property_exists( $rule, 'rules' ) ) { + // visit because we are moving them out from being a child + $rulesets[] = $this->visitObj( $rule ); + array_splice( $rulesetNode->rules, $i, 1 ); + $nodeRuleCnt--; + continue; + } + $i++; + } + + // accept the visitor to remove rules and refactor itself + // then we can decide now whether we want it or not + if ( $nodeRuleCnt > 0 ) { + $rulesetNode->accept( $this ); + + if ( $rulesetNode->rules ) { + + if ( count( $rulesetNode->rules ) > 1 ) { + $this->_mergeRules( $rulesetNode->rules ); + $this->_removeDuplicateRules( $rulesetNode->rules ); + } + + // now decide whether we keep the ruleset + if ( $rulesetNode->paths ) { + // array_unshift($rulesets, $rulesetNode); + array_splice( $rulesets, 0, 0, array( $rulesetNode ) ); + } + } + + } + + if ( count( $rulesets ) === 1 ) { + return $rulesets[0]; + } + return $rulesets; + } + + /** + * Helper function for visitiRuleset + * + * return array|Less_Tree_Ruleset + */ + private function visitRulesetRoot( $rulesetNode ) { + $rulesetNode->accept( $this ); + if ( $rulesetNode->firstRoot || $rulesetNode->rules ) { + return $rulesetNode; + } + return array(); + } + + /** + * Helper function for visitRuleset() + * + * @return array + */ + private function visitRulesetPaths( $rulesetNode ) { + $paths = array(); + foreach ( $rulesetNode->paths as $p ) { + if ( $p[0]->elements[0]->combinator === ' ' ) { + $p[0]->elements[0]->combinator = ''; + } + + foreach ( $p as $pi ) { + if ( $pi->getIsReferenced() && $pi->getIsOutput() ) { + $paths[] = $p; + break; + } + } + } + + return $paths; + } + + protected function _removeDuplicateRules( &$rules ) { + // remove duplicates + $ruleCache = array(); + for ( $i = count( $rules ) - 1; $i >= 0; $i-- ) { + $rule = $rules[$i]; + if ( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_NameValue ) { + + if ( !isset( $ruleCache[$rule->name] ) ) { + $ruleCache[$rule->name] = $rule; + } else { + $ruleList =& $ruleCache[$rule->name]; + + if ( $ruleList instanceof Less_Tree_Rule || $ruleList instanceof Less_Tree_NameValue ) { + $ruleList = $ruleCache[$rule->name] = array( $ruleCache[$rule->name]->toCSS() ); + } + + $ruleCSS = $rule->toCSS(); + if ( array_search( $ruleCSS, $ruleList ) !== false ) { + array_splice( $rules, $i, 1 ); + } else { + $ruleList[] = $ruleCSS; + } + } + } + } + } + + protected function _mergeRules( &$rules ) { + $groups = array(); + + // obj($rules); + + $rules_len = count( $rules ); + for ( $i = 0; $i < $rules_len; $i++ ) { + $rule = $rules[$i]; + + if ( ( $rule instanceof Less_Tree_Rule ) && $rule->merge ) { + + $key = $rule->name; + if ( $rule->important ) { + $key .= ',!'; + } + + if ( !isset( $groups[$key] ) ) { + $groups[$key] = array(); + } else { + array_splice( $rules, $i--, 1 ); + $rules_len--; + } + + $groups[$key][] = $rule; + } + } + + foreach ( $groups as $parts ) { + + if ( count( $parts ) > 1 ) { + $rule = $parts[0]; + $spacedGroups = array(); + $lastSpacedGroup = array(); + $parts_mapped = array(); + foreach ( $parts as $p ) { + if ( $p->merge === '+' ) { + if ( $lastSpacedGroup ) { + $spacedGroups[] = self::toExpression( $lastSpacedGroup ); + } + $lastSpacedGroup = array(); + } + $lastSpacedGroup[] = $p; + } + + $spacedGroups[] = self::toExpression( $lastSpacedGroup ); + $rule->value = self::toValue( $spacedGroups ); + } + } + + } + + public static function toExpression( $values ) { + $mapped = array(); + foreach ( $values as $p ) { + $mapped[] = $p->value; + } + return new Less_Tree_Expression( $mapped ); + } + + public static function toValue( $values ) { + // return new Less_Tree_Value($values); ?? + + $mapped = array(); + foreach ( $values as $p ) { + $mapped[] = $p; + } + return new Less_Tree_Value( $mapped ); + } +} -- cgit v1.2.3