Beispiel #1
0
function loop_resolve_list($list_text)
{
    // Resolve the list of items for iteration.
    // Either a generator function or a plain list.
    $items = array();
    $list_text = Crush::$process->functions->apply($list_text);
    $generator_func_patt = Regex::make('~(?<func>range|color-range) {{parens}}~ix');
    if (preg_match($generator_func_patt, $list_text, $m)) {
        $func = strtolower($m['func']);
        $args = Functions::parseArgs($m['parens_content']);
        switch ($func) {
            case 'range':
                $items = call_user_func_array('range', $args);
                break;
            default:
                $func = str_replace('-', '_', $func);
                if (function_exists("CssCrush\\loop_{$func}")) {
                    $items = call_user_func_array("CssCrush\\loop_{$func}", $args);
                }
        }
    } else {
        $items = Util::splitDelimList($list_text);
    }
    return $items;
}
Beispiel #2
0
 public function __invoke($args)
 {
     $handler = $this->handler;
     $tokens = Crush::$process->tokens;
     $splat_arg_patt = Regex::make('~#\\((?<fallback>{{ ident }})?\\)~');
     switch ($this->type) {
         case 'alias':
             return $handler($args);
         case 'callback':
             $template = new Template($handler($args));
             return $template($args);
         case 'splat':
             $handler = $tokens->restore($handler, 's');
             if ($args) {
                 $list = array();
                 foreach ($args as $arg) {
                     $list[] = SelectorAlias::wrap($tokens->capture(preg_replace($splat_arg_patt, $arg, $handler), 's'));
                 }
                 $handler = implode(',', $list);
             } else {
                 $handler = $tokens->capture(preg_replace_callback($splat_arg_patt, function ($m) {
                     return $m['fallback'];
                 }, $handler), 's');
             }
             return SelectorAlias::wrap($handler);
     }
 }
Beispiel #3
0
function svg_capture($process)
{
    $process->string->pregReplaceCallback(Regex::make('~@svg\\s+(?<name>{{ ident }})\\s*{{ block }}~iS'), function ($m) {
        Crush::$process->misc->svg_defs[strtolower($m['name'])] = new Template($m['block_content']);
        return '';
    });
}
Beispiel #4
0
 public function expand()
 {
     static $grouping_patt, $expand, $expandSelector;
     if (!$grouping_patt) {
         $grouping_patt = Regex::make('~\\:any{{ parens }}~iS');
         $expand = function ($selector_string) use($grouping_patt) {
             if (preg_match($grouping_patt, $selector_string, $m, PREG_OFFSET_CAPTURE)) {
                 list($full_match, $full_match_offset) = $m[0];
                 $before = substr($selector_string, 0, $full_match_offset);
                 $after = substr($selector_string, strlen($full_match) + $full_match_offset);
                 $selectors = array();
                 // Allowing empty strings for more expansion possibilities.
                 foreach (Util::splitDelimList($m['parens_content'][0], array('allow_empty_strings' => true)) as $segment) {
                     if ($selector = trim("{$before}{$segment}{$after}")) {
                         $selectors[$selector] = true;
                     }
                 }
                 return $selectors;
             }
             return false;
         };
         $expandSelector = function ($selector_string) use($expand) {
             if ($running_stack = $expand($selector_string)) {
                 $flattened_stack = array();
                 do {
                     $loop_stack = array();
                     foreach ($running_stack as $selector => $bool) {
                         $selectors = $expand($selector);
                         if (!$selectors) {
                             $flattened_stack += array($selector => true);
                         } else {
                             $loop_stack += $selectors;
                         }
                     }
                     $running_stack = $loop_stack;
                 } while ($loop_stack);
                 return $flattened_stack;
             }
             return array($selector_string => true);
         };
     }
     $expanded_set = array();
     foreach ($this->store as $original_selector) {
         if (stripos($original_selector->value, ':any(') !== false) {
             foreach ($expandSelector($original_selector->value) as $selector_string => $bool) {
                 $new = new Selector($selector_string);
                 $expanded_set[$new->readableValue] = $new;
             }
         } else {
             $expanded_set[$original_selector->readableValue] = $original_selector;
         }
     }
     $this->store = $expanded_set;
 }
Beispiel #5
0
function hsl2hex(Rule $rule)
{
    $hsl_patt = Regex::make('~{{ LB }}hsl({{ parens }})~i');
    foreach ($rule->declarations->filter(array('skip' => false)) as $declaration) {
        if (isset($declaration->functions['hsl'])) {
            $declaration->value = preg_replace_callback($hsl_patt, function ($m) {
                $color = new Color($m[0]);
                return $color->getHex();
            }, $declaration->value);
        }
    }
}
Beispiel #6
0
/**
 * Remove the 'at' keyword from -x-radial-gradient() for legacy implementations.
 */
function postalias_fix_radial_gradients($declaration_copies)
{
    // Create new paren tokens based on the first prefixed declaration.
    // Replace the new syntax with the legacy syntax.
    static $fn_patt;
    if (!$fn_patt) {
        $fn_patt = Regex::make('~{{ LB }}{{ vendor }}(?:repeating-)?radial-gradient{{ parens }}~iS');
    }
    $original_parens = array();
    $replacement_parens = array();
    foreach (Regex::matchAll($fn_patt, $declaration_copies[0]->value) as $m) {
        $original_parens[] = $m['parens'][0];
        $replacement_parens[] = preg_replace('~\\bat +(top|left|bottom|right|center)\\b~i', '$1', $m['parens'][0]);
    }
    foreach ($declaration_copies as $prefixed_copy) {
        $prefixed_copy->value = str_replace($original_parens, $replacement_parens, $prefixed_copy->value);
    }
}
Beispiel #7
0
function color_capture($process)
{
    $captured_keywords = $process->string->captureDirectives('color', array('singles' => true));
    if ($captured_keywords) {
        $native_keywords = Color::getKeywords();
        $custom_keywords = array();
        $process->colorKeywords = $native_keywords;
        foreach ($captured_keywords as $key => $value) {
            $value = $process->functions->apply($value);
            if (!isset($native_keywords[$key]) && ($rgba = Color::parse($value))) {
                $custom_keywords[] = $key;
                $process->stat['colors'][$key] = new Color($rgba);
                $process->colorKeywords[$key] = $rgba;
            }
        }
        if ($custom_keywords) {
            $GLOBALS['CSSCRUSH_COLOR_PATT'] = Regex::make('~{{ LB }}(?<color_keyword>' . implode('|', $custom_keywords) . '){{ RB }}~iS');
        }
    }
}
Beispiel #8
0
 public function captureUrls($str, $add_padding = false)
 {
     $count = preg_match_all(Regex::make('~@import \\s+ (?<import>{{s_token}}) | {{LB}} (?<func>url|data-uri) {{parens}}~ixS'), $str, $m, PREG_OFFSET_CAPTURE);
     while ($count--) {
         list($full_text, $full_offset) = $m[0][$count];
         list($import_text, $import_offset) = $m['import'][$count];
         // @import directive.
         if ($import_offset !== -1) {
             $label = $this->add(new Url(trim($import_text)));
             $str = str_replace($import_text, $add_padding ? str_pad($label, strlen($import_text)) : $label, $str);
         } else {
             $func_name = strtolower($m['func'][$count][0]);
             $url = new Url(trim($m['parens_content'][$count][0]));
             $url->convertToData = 'data-uri' === $func_name;
             $label = $this->add($url);
             $str = substr_replace($str, $add_padding ? Tokens::pad($label, $full_text) : $label, $full_offset, strlen($full_text));
         }
     }
     return $str;
 }
Beispiel #9
0
function ease(Rule $rule)
{
    static $find, $replace, $easing_properties;
    if (!$find) {
        $easings = array('ease-in-out-back' => 'cubic-bezier(.680,-0.550,.265,1.550)', 'ease-in-out-circ' => 'cubic-bezier(.785,.135,.150,.860)', 'ease-in-out-expo' => 'cubic-bezier(1,0,0,1)', 'ease-in-out-sine' => 'cubic-bezier(.445,.050,.550,.950)', 'ease-in-out-quint' => 'cubic-bezier(.860,0,.070,1)', 'ease-in-out-quart' => 'cubic-bezier(.770,0,.175,1)', 'ease-in-out-cubic' => 'cubic-bezier(.645,.045,.355,1)', 'ease-in-out-quad' => 'cubic-bezier(.455,.030,.515,.955)', 'ease-out-back' => 'cubic-bezier(.175,.885,.320,1.275)', 'ease-out-circ' => 'cubic-bezier(.075,.820,.165,1)', 'ease-out-expo' => 'cubic-bezier(.190,1,.220,1)', 'ease-out-sine' => 'cubic-bezier(.390,.575,.565,1)', 'ease-out-quint' => 'cubic-bezier(.230,1,.320,1)', 'ease-out-quart' => 'cubic-bezier(.165,.840,.440,1)', 'ease-out-cubic' => 'cubic-bezier(.215,.610,.355,1)', 'ease-out-quad' => 'cubic-bezier(.250,.460,.450,.940)', 'ease-in-back' => 'cubic-bezier(.600,-0.280,.735,.045)', 'ease-in-circ' => 'cubic-bezier(.600,.040,.980,.335)', 'ease-in-expo' => 'cubic-bezier(.950,.050,.795,.035)', 'ease-in-sine' => 'cubic-bezier(.470,0,.745,.715)', 'ease-in-quint' => 'cubic-bezier(.755,.050,.855,.060)', 'ease-in-quart' => 'cubic-bezier(.895,.030,.685,.220)', 'ease-in-cubic' => 'cubic-bezier(.550,.055,.675,.190)', 'ease-in-quad' => 'cubic-bezier(.550,.085,.680,.530)');
        $easing_properties = array('transition' => true, 'transition-timing-function' => true);
        foreach ($easings as $property => $value) {
            $patt = Regex::make("~{{ LB }}{$property}{{ RB }}~i");
            $find[] = $patt;
            $replace[] = $value;
        }
    }
    if (!array_intersect_key($rule->declarations->canonicalProperties, $easing_properties)) {
        return;
    }
    foreach ($rule->declarations->filter(array('skip' => false)) as $declaration) {
        if (isset($easing_properties[$declaration->canonicalProperty])) {
            $declaration->value = preg_replace($find, $replace, $declaration->value);
        }
    }
}
Beispiel #10
0
 public static function call($message, $context = null)
 {
     $process = Crush::$process;
     $mixable = null;
     $message = trim($message);
     // Test for mixin or abstract rule. e.g:
     //   named-mixin( 50px, rgba(0,0,0,0), left 100% )
     //   abstract-rule
     if (preg_match(Regex::make('~^(?<name>{{ident}}) {{parens}}?~xS'), $message, $message_match)) {
         $name = $message_match['name'];
         if (isset($process->mixins[$name])) {
             $mixable = $process->mixins[$name];
         } elseif (isset($process->references[$name])) {
             $mixable = $process->references[$name];
         }
     }
     // If no mixin or abstract rule matched, look for matching selector
     if (!$mixable) {
         $selector_test = Selector::makeReadable($message);
         if (isset($process->references[$selector_test])) {
             $mixable = $process->references[$selector_test];
         }
     }
     // Avoid infinite recursion.
     if (!$mixable || $mixable === $context) {
         return false;
     } elseif ($mixable instanceof Mixin) {
         $args = array();
         $raw_args = isset($message_match['parens_content']) ? trim($message_match['parens_content']) : null;
         if ($raw_args) {
             $args = Util::splitDelimList($raw_args);
         }
         return DeclarationList::parse($mixable->template->__invoke($args), array('flatten' => true, 'context' => $mixable));
     } elseif ($mixable instanceof Rule) {
         return $mixable->declarations->store;
     }
 }
Beispiel #11
0
 public static function colorSplit($str)
 {
     if ($test = Color::test($str)) {
         $color = $test['value'];
         $type = $test['type'];
     } else {
         return false;
     }
     // If non-alpha color return early.
     if (!in_array($type, array('hsla', 'rgba'))) {
         return array($color, 1);
     }
     // Strip all whitespace.
     $color = preg_replace('~\\s+~', '', $color);
     // Extract alpha component if one is matched.
     $opacity = 1;
     if (preg_match(Regex::make('~^(rgb|hsl)a\\(({{number}}%?,{{number}}%?,{{number}}%?),({{number}})\\)$~i'), $color, $m)) {
         $opacity = floatval($m[3]);
         $color = "{$m['1']}({$m['2']})";
     }
     // Return color value and alpha component seperated.
     return array($color, $opacity);
 }
Beispiel #12
0
 public static function makePattern($functionNames)
 {
     $idents = array();
     $nonIdents = array();
     foreach ($functionNames as $functionName) {
         if (preg_match(Regex::$patt->ident, $functionName[0])) {
             $idents[] = preg_quote($functionName);
         } else {
             $nonIdents[] = preg_quote($functionName);
         }
     }
     if ($idents) {
         $idents = '{{ LB }}-?(?<function>' . implode('|', $idents) . ')';
     }
     if ($nonIdents) {
         $nonIdents = '(?<simple_function>' . implode('|', $nonIdents) . ')';
     }
     if ($idents && $nonIdents) {
         $patt = "(?:{$idents}|{$nonIdents})";
     } elseif ($idents) {
         $patt = $idents;
     } elseif ($nonIdents) {
         $patt = $nonIdents;
     }
     return Regex::make("~{$patt}\\(~iS");
 }
Beispiel #13
0
function rem(Rule $rule)
{
    static $rem_patt, $px_patt, $font_props, $modes;
    if (!$modes) {
        $rem_patt = Regex::make('~{{LB}}({{number}})rem{{RB}}~iS');
        $px_patt = Regex::make('~{{LB}}({{number}})px{{RB}}~iS');
        $font_props = array('font' => true, 'font-size' => true, 'line-height' => true);
        $modes = array('rem-fallback', 'px-fallback', 'convert');
    }
    // Determine which properties are touched; all, or just font related.
    $just_font_props = !Crush::$process->settings->get('rem-all', false);
    if ($just_font_props && !array_intersect_key($rule->declarations->canonicalProperties, $font_props)) {
        return;
    }
    // Determine what conversion mode we're using.
    $mode = Crush::$process->settings->get('rem-mode', $modes[0]);
    // Determine the default base em-size, to my knowledge always 16px.
    $base = Crush::$process->settings->get('rem-base', 16);
    // Select the length match pattern depending on mode.
    $length_patt = $mode === 'rem-fallback' ? $rem_patt : $px_patt;
    $new_set = array();
    $rule_updated = false;
    foreach ($rule->declarations as $declaration) {
        if ($declaration->skip || $just_font_props && !isset($font_props[$declaration->canonicalProperty]) || !preg_match_all($length_patt, $declaration->value, $m)) {
            $new_set[] = $declaration;
            continue;
        }
        // Value has matching length components.
        $find = $m[0];
        $replace = array();
        $numbers = $m[1];
        switch ($mode) {
            // Converting a rem value to px.
            case 'rem-fallback':
                foreach ($numbers as $num) {
                    $replace[] = round(floatval($num) * $base, 5) . 'px';
                }
                break;
                // Converting a px value to rem.
            // Converting a px value to rem.
            case 'convert':
            case 'px-fallback':
                foreach ($numbers as $num) {
                    $replace[] = round(floatval($num) / $base, 5) . 'rem';
                }
                break;
        }
        $converted_value = str_replace($find, $replace, $declaration->value);
        if ($mode === 'convert') {
            $declaration->value = $converted_value;
            $new_set[] = $declaration;
        } else {
            $clone = clone $declaration;
            $clone->value = $converted_value;
            $rule_updated = true;
            if ($mode === 'px-fallback') {
                $new_set[] = $declaration;
                $new_set[] = $clone;
            } else {
                $new_set[] = $clone;
                $new_set[] = $declaration;
            }
        }
    }
    if ($rule_updated) {
        $rule->declarations->reset($new_set);
    }
}
Beispiel #14
0
 protected function minifyColors()
 {
     static $keywords_patt, $functions_patt;
     $minified_keywords = Color::getMinifyableKeywords();
     if (!$keywords_patt) {
         $keywords_patt = '~(?<![\\w-\\.#])(' . implode('|', array_keys($minified_keywords)) . ')(?![\\w-\\.#\\]])~iS';
         $functions_patt = Regex::make('~{{ LB }}(rgb|hsl)\\(([^\\)]{5,})\\)~iS');
     }
     $this->string->pregReplaceCallback($keywords_patt, function ($m) use($minified_keywords) {
         return $minified_keywords[strtolower($m[0])];
     });
     $this->string->pregReplaceCallback($functions_patt, function ($m) {
         $args = Functions::parseArgs(trim($m[2]));
         if (stripos($m[1], 'hsl') === 0) {
             $args = Color::cssHslToRgb($args);
         }
         return Color::rgbToHex($args);
     });
 }
Beispiel #15
0
 protected function addMarkers(&$str)
 {
     $process = $this->process;
     $currentFileIndex = count($process->sources) - 1;
     static $patt;
     if (!$patt) {
         $patt = Regex::make('~
             (?:^|(?<=[;{}]))
             (?<before>
                 (?: \\s | {{c_token}} )*
             )
             (?<selector>
                 (?:
                     # Some @-rules are treated like standard rule blocks.
                     @(?: (?i)page|abstract|font-face(?-i) ) {{RB}} [^{]*
                     |
                     [^@;{}]+
                 )
             )
             \\{
         ~xS');
     }
     $count = preg_match_all($patt, $str, $matches, PREG_OFFSET_CAPTURE);
     while ($count--) {
         $selectorOffset = $matches['selector'][$count][1];
         $line = 0;
         $before = substr($str, 0, $selectorOffset);
         if ($selectorOffset) {
             $line = substr_count($before, "\n");
         }
         $pointData = array($currentFileIndex, $line);
         // Source maps require column index too.
         if ($process->generateMap) {
             $pointData[] = strlen($before) - strrpos($before, "\n") - 1;
         }
         // Splice in marker token (packing point_data into string is more memory efficient).
         $str = substr_replace($str, $process->tokens->add(implode(',', $pointData), 't'), $selectorOffset, 0);
     }
 }
Beispiel #16
0
 public function aliasFunctions($vendor_context = null)
 {
     $function_aliases =& Crush::$process->aliases['functions'];
     $function_alias_groups =& Crush::$process->aliases['function_groups'];
     // The new modified set of declarations.
     $new_set = array();
     $rule_updated = false;
     // Shim in aliased functions.
     foreach ($this->store as $declaration) {
         // No functions, bail.
         if (!$declaration->functions || $declaration->skip) {
             $new_set[] = $declaration;
             continue;
         }
         // Get list of functions used in declaration that are alias-able, bail if none.
         $intersect = array_intersect_key($declaration->functions, $function_aliases);
         if (!$intersect) {
             $new_set[] = $declaration;
             continue;
         }
         // Keep record of which groups have been applied.
         $processed_groups = array();
         foreach (array_keys($intersect) as $fn_name) {
             // Store for all the duplicated declarations.
             $prefixed_copies = array();
             // Grouped function aliases.
             if ($function_aliases[$fn_name][0] === '.') {
                 $group_id = $function_aliases[$fn_name];
                 // If this group has been applied we can skip over.
                 if (isset($processed_groups[$group_id])) {
                     continue;
                 }
                 // Mark group as applied.
                 $processed_groups[$group_id] = true;
                 $groups =& $function_alias_groups[$group_id];
                 foreach ($groups as $group_key => $replacements) {
                     // If the declaration is vendor specific only create aliases for the same vendor.
                     if ($declaration->vendor && $group_key !== $declaration->vendor || $vendor_context && $group_key !== $vendor_context) {
                         continue;
                     }
                     $copy = clone $declaration;
                     // Make swaps.
                     $copy->value = preg_replace($replacements['find'], $replacements['replace'], $copy->value);
                     $prefixed_copies[] = $copy;
                     $rule_updated = true;
                 }
                 // Post fixes.
                 if (isset(PostAliasFix::$functions[$group_id])) {
                     call_user_func(PostAliasFix::$functions[$group_id], $prefixed_copies, $group_id);
                 }
             } else {
                 foreach ($function_aliases[$fn_name] as $fn_alias) {
                     // If the declaration is vendor specific only create aliases for the same vendor.
                     if ($declaration->vendor) {
                         preg_match(Regex::$patt->vendorPrefix, $fn_alias, $m);
                         if ($m[1] !== $declaration->vendor || $vendor_context && $m[1] !== $vendor_context) {
                             continue;
                         }
                     }
                     $copy = clone $declaration;
                     // Make swaps.
                     $copy->value = preg_replace(Regex::make("~{{ LB }}{$fn_name}(?=\\()~iS"), $fn_alias, $copy->value);
                     $prefixed_copies[] = $copy;
                     $rule_updated = true;
                 }
                 // Post fixes.
                 if (isset(PostAliasFix::$functions[$fn_name])) {
                     call_user_func(PostAliasFix::$functions[$fn_name], $prefixed_copies, $fn_name);
                 }
             }
             $new_set = array_merge($new_set, $prefixed_copies);
         }
         $new_set[] = $declaration;
     }
     // Re-assign if any updates have been made.
     if ($rule_updated) {
         $this->reset($new_set);
     }
 }
Beispiel #17
0
 public function captureDirectives($directive, $parse_options = array())
 {
     if (is_array($directive)) {
         $directive = '(?:' . implode('|', $directive) . ')';
     }
     $parse_options += array('keyed' => true, 'lowercase_keys' => true, 'ignore_directives' => true, 'singles' => false, 'flatten' => false);
     if ($parse_options['singles']) {
         $patt = Regex::make('~@(?i)' . $directive . '(?-i)(?:\\s*{{ block }}|\\s+(?<name>{{ ident }})\\s+(?<value>[^;]+)\\s*;)~S');
     } else {
         $patt = Regex::make('~@(?i)' . $directive . '(?-i)\\s*{{ block }}~S');
     }
     $captured_directives = array();
     $this->pregReplaceCallback($patt, function ($m) use(&$captured_directives, $parse_options) {
         if (isset($m['name'])) {
             $name = $parse_options['lowercase_keys'] ? strtolower($m['name']) : $m['name'];
             $captured_directives[$name] = $m['value'];
         } else {
             $captured_directives = DeclarationList::parse($m['block_content'], $parse_options) + $captured_directives;
         }
         return '';
     });
     return $captured_directives;
 }
Beispiel #18
0
 public static function makePattern($functionNames)
 {
     $idents = array();
     $nonIdents = array();
     foreach ($functionNames as $functionName) {
         if (preg_match(Regex::$patt->ident, $functionName[0])) {
             $idents[] = preg_quote($functionName);
         } else {
             $nonIdents[] = preg_quote($functionName);
         }
     }
     $flatList = '';
     if (!$idents) {
         $flatList = implode('|', $nonIdents);
     } else {
         $idents = '{{ LB }}(?:' . implode('|', $idents) . ')';
         $flatList = $nonIdents ? '(?:' . implode('|', $nonIdents) . "|{$idents})" : $idents;
     }
     return Regex::make("~(?<function>{$flatList})\\(~iS");
 }
Beispiel #19
0
 public static function init()
 {
     self::$patt = $patt = new \stdClass();
     self::$classes = $classes = new \stdClass();
     // CSS type classes.
     $classes->ident = '[a-zA-Z0-9_-]+';
     $classes->number = '[+-]?\\d*\\.?\\d+';
     $classes->percentage = $classes->number . '%';
     $classes->length_unit = '(?i)(?:e[mx]|c[hm]|rem|v[hwm]|in|p[tcx])(?-i)';
     $classes->length = $classes->number . $classes->length_unit;
     $classes->color_hex = '#[[:xdigit:]]{3}(?:[[:xdigit:]]{3})?';
     // Tokens.
     $classes->token_id = '[0-9a-z]+';
     $classes->c_token = '\\?c' . $classes->token_id . '\\?';
     // Comments.
     $classes->s_token = '\\?s' . $classes->token_id . '\\?';
     // Strings.
     $classes->r_token = '\\?r' . $classes->token_id . '\\?';
     // Rules.
     $classes->u_token = '\\?u' . $classes->token_id . '\\?';
     // URLs.
     $classes->t_token = '\\?t' . $classes->token_id . '\\?';
     // Traces.
     $classes->a_token = '\\?a(' . $classes->token_id . ')\\?';
     // Args.
     // Boundries.
     $classes->LB = '(?<![\\w-])';
     // Left ident boundry.
     $classes->RB = '(?![\\w-])';
     // Right ident boundry.
     // Recursive block matching.
     $classes->block = '(?<block>\\{\\s*(?<block_content>(?:(?>[^{}]+)|(?&block))*)\\})';
     $classes->parens = '(?<parens>\\(\\s*(?<parens_content>(?:(?>[^()]+)|(?&parens))*)\\))';
     // Misc.
     $classes->vendor = '-[a-zA-Z]+-';
     $classes->hex = '[[:xdigit:]]';
     $classes->newline = '(\\r\\n?|\\n)';
     // Create standalone class patterns, add classes as class swaps.
     foreach ($classes as $name => $class) {
         $patt->{$name} = '~' . $class . '~S';
     }
     // Rooted classes.
     $patt->rooted_ident = '~^' . $classes->ident . '$~';
     $patt->rooted_number = '~^' . $classes->number . '$~';
     // @-rules.
     $patt->import = Regex::make('~@import \\s+ ({{u_token}}) \\s? ([^;]*);~ixS');
     $patt->charset = Regex::make('~@charset \\s+ ({{s_token}}) \\s*;~ixS');
     $patt->mixin = Regex::make('~@mixin \\s+ (?<name>{{ident}}) \\s* {{block}}~ixS');
     $patt->fragmentCapture = Regex::make('~@fragment \\s+ (?<name>{{ident}}) \\s* {{block}}~ixS');
     $patt->fragmentInvoke = Regex::make('~@fragment \\s+ (?<name>{{ident}}) {{parens}}? \\s* ;~ixS');
     $patt->abstract = Regex::make('~^@abstract \\s+ (?<name>{{ident}})~ixS');
     // Functions.
     $patt->functionTest = Regex::make('~{{ LB }} (?<func_name>{{ ident }}) \\(~xS');
     $patt->thisFunction = Functions::makePattern(array('this'));
     // Strings and comments.
     $patt->string = '~(\'|")(?:\\\\\\1|[^\\1])*?\\1~xS';
     $patt->commentAndString = '~
         # Quoted string (to EOF if unmatched).
         (\'|")(?:\\\\\\1|[^\\1])*?(?:\\1|$)
         |
         # Block comment (to EOF if unmatched).
         /\\*(?:[^*]*\\*+(?:[^/*][^*]*\\*+)*/|.*)
     ~xsS';
     // Misc.
     $patt->vendorPrefix = '~^-([a-z]+)-([a-z-]+)~iS';
     $patt->ruleDirective = '~^(?:(@include)|(@extends?)|(@name))[\\s]+~iS';
     $patt->argListSplit = '~\\s*[,\\s]\\s*~S';
     $patt->cruftyHex = Regex::make('~\\#({{hex}})\\1({{hex}})\\2({{hex}})\\3~S');
     $patt->token = Regex::make('~^ \\? (?<type>[a-zA-Z]) {{token_id}} \\? $~xS');
 }
Beispiel #20
0
 public static function parseAliasesFile($file)
 {
     if (!($tree = Util::parseIni($file, true))) {
         return false;
     }
     $regex = Regex::$patt;
     // Some alias groups need further parsing to unpack useful information into the tree.
     foreach ($tree as $section => $items) {
         if ($section === 'declarations') {
             $store = array();
             foreach ($items as $prop_val => $aliases) {
                 list($prop, $value) = array_map('trim', explode(':', $prop_val));
                 foreach ($aliases as &$alias) {
                     list($p, $v) = explode(':', $alias);
                     $vendor = null;
                     // Try to detect the vendor from property and value in turn.
                     if (preg_match($regex->vendorPrefix, $p, $m) || preg_match($regex->vendorPrefix, $v, $m)) {
                         $vendor = $m[1];
                     }
                     $alias = array($p, $v, $vendor);
                 }
                 $store[$prop][$value] = $aliases;
             }
             $tree['declarations'] = $store;
         } elseif (strpos($section, 'functions.') === 0) {
             $group = substr($section, strlen('functions'));
             $vendor_grouped_aliases = array();
             foreach ($items as $func_name => $aliases) {
                 // Assign group name to the aliasable function.
                 $tree['functions'][$func_name] = $group;
                 foreach ($aliases as $alias_func) {
                     // Only supporting vendor prefixed aliases, for now.
                     if (preg_match($regex->vendorPrefix, $alias_func, $m)) {
                         // We'll cache the function matching regex here.
                         $vendor_grouped_aliases[$m[1]]['find'][] = Regex::make("~{{ LB }}{$func_name}(?=\\()~iS");
                         $vendor_grouped_aliases[$m[1]]['replace'][] = $alias_func;
                     }
                 }
             }
             $tree['function_groups'][$group] = $vendor_grouped_aliases;
             unset($tree[$section]);
         }
     }
     $tree += self::$config->bareAliases;
     // Persisting dummy aliases for testing purposes.
     $tree['properties']['foo'] = $tree['at-rules']['foo'] = $tree['functions']['foo'] = array('-webkit-foo', '-moz-foo', '-ms-foo');
     return $tree;
 }
Beispiel #21
0
function create_svg_radial_gradient($input)
{
    static $position_keywords, $origin_patt;
    if (!$position_keywords) {
        $position_keywords = array('at top' => array('50%', '0%'), 'at right' => array('100%', '50%'), 'at bottom' => array('50%', '100%'), 'at left' => array('0%', '50%'), 'at center' => array('50%', '50%'), 'at top right' => array('100%', '0%'), 'at top left' => array('0%', '0%'), 'at bottom right' => array('100%', '100%'), 'at bottom left' => array('0%', '100%'));
        $position_keywords['at right top'] = $position_keywords['at top right'];
        $position_keywords['at left top'] = $position_keywords['at top left'];
        $position_keywords['at right bottom'] = $position_keywords['at bottom right'];
        $position_keywords['at left bottom'] = $position_keywords['at bottom left'];
        $origin_patt = Regex::make('~^({{number}}%?) +({{number}}%?)$~');
    }
    $args = Functions::parseArgs($input);
    // Default origin,
    $position = $position_keywords['at center'];
    // Parse origin coordinates from the first argument if it's an origin.
    $first_arg = $args[0];
    $first_arg_is_position = false;
    // Try to parse an origin value.
    if (preg_match($origin_patt, $first_arg, $m)) {
        $position = array($m[1], $m[2]);
        $first_arg_is_position = true;
    } elseif (isset($position_keywords[$first_arg])) {
        $position = $position_keywords[$first_arg];
        $first_arg_is_position = true;
    }
    // Shift off the first argument if it has been recognised as an origin.
    if ($first_arg_is_position) {
        array_shift($args);
    }
    // The remaining arguments are treated as color stops.
    // - Capture their color values and if specified color offset percentages.
    // - Only percentages are supported as SVG gradients to accept other length values
    //   for color stop offsets.
    $color_stops = parse_gradient_color_stops($args);
    // Create the gradient markup with a unique id.
    $uid = ++$GLOBALS['CSSCRUSH_SVG_GRADIENT_UID'];
    $gradient_id = "rg{$uid}";
    $gradient = "<radialGradient id=\"{$gradient_id}\" gradientUnits=\"userSpaceOnUse\"";
    $gradient .= " cx=\"{$position[0]}\" cy=\"{$position[1]}\" r=\"100%\">";
    $gradient .= $color_stops;
    $gradient .= '</radialGradient>';
    return array($gradient_id => $gradient);
}