正则表达式匹配scss函数/ mixin

时间:2017-05-31 07:11:09

标签: regex twitter-bootstrap

我正在尝试匹配SCSS字符串中使用的函数或mixin,所以我可能会将其删除但是我遇到了一些麻烦。

对于那些不熟悉SCSS的人来说,这是我想要匹配的事情的一个例子(来自bootstrap 4)。

@mixin _assert-ascending($map, $map-name) {
  $prev-key: null;
  $prev-num: null;
    @each $key, $num in $map {
     @if $prev-num == null {
  // Do nothing
    } @else if not comparable($prev-num, $num) {
      @warn "Potentially invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} whose unit makes it incomparable to #{$prev-num}, the value of the previous key '#{$prev-key}' !";
    } @else if $prev-num >= $num {
  @warn "Invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} which isn't greater than #{$prev-num}, the value of the previous key '#{$prev-key}' !";
 }
  $prev-key: $key;
  $prev-num: $num;
 }
}

一个小功能:

@function str-replace($string, $search, $replace: "") {
  $index: str-index($string, $search);
  @if $index {
    @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
  }
  @return $string;
}

到目前为止,我有以下正则表达式:

@(function|mixin)\s?[[:print:]]+\n?([^\}]+)

然而,它只匹配它找到的第一个}使它失败的原因,这是因为它需要找到结束大括号的最后一次出现。

我的想法是,可以调整能够匹配函数定义的正则表达式,但是使用我的Google foo找不到好的正则表达式!

提前致谢!

3 个答案:

答案 0 :(得分:0)

我不建议使用正则表达式,因为正则表达式无法处理递归,在这种情况下您可能需要。

For Instance:

@mixin test {
  body {
  }
}

此处包含两个»级别«范围({{ }}),因此您的正则表达式应该能够在打开和关闭时计算括号,以匹配mixin或函数的结尾。但是使用正则表达式是不可能的。

这个正则表达式

/@mixin(.|\s)*\}/gm

将匹配整个mixin,但如果输入是这样的:

@mixin foo { … }

body { … }

它将匹配最后}的所有内容,其中包括正文的样式定义。这是因为正则表达式无法知道哪个}关闭了mixin。

看看this答案,它解释了或多或少相同的事情,但基于匹配的html元素。

相反,您应该使用解析器将整个样式表解析为语法树,而不是删除不需要的函数,而不是再次将其写入字符串。

答案 1 :(得分:0)

事实上,正如@philipp所说,正则表达式无法像编译器那样取代语法分析。

但是这里有一个sed命令,它有点难看,但可以解决问题:

sed -r -e ':a' -e 'N' -e '$!ba' -e 's/\n//g' -e 's/}\s*@(function|mixin)/}\n@\1/g' -e 's/^@(function|mixin)\s*str-replace(\s|\()+.*}$//gm' <your file>

  • -e ':a' -e 'N' -e '$!ba' -e 's/\n//g':读取循环中的所有文件并删除新行(有关详细信息,请参阅https://stackoverflow.com/a/1252191/7990687
  • -e 's/}\s*@(function|mixin)/}\n@\1/g':将每个@mixin@function语句设为新行的开头,并将前一行}作为前一行的最后一个字符
  • 's/^@(function|mixin)\s*str-replace(\s|\()+.*}$//gm':删除与@function str-replace@mixin str-replace声明相对应的行

但是它会导致输出松动缩进,所以在此之后你必须重新加注它。

我在一个文件上尝试过,我复制/粘贴了你提供的示例代码多次,所以你必须在你的文件上尝试它,因为可能会出现正则表达式匹配的元素多于想要的情况。如果是这种情况,请向我们提供一个测试文件,以尝试解决这些问题。

答案 2 :(得分:0)

经过多次头痛后,这是我的问题的答案!

源需要逐行拆分并读取,保持打开/关闭括号的计数以确定索引何时为0。

$pattern = '/(?<remove>@(function|mixin)\s?[\w-]+[$,:"\'()\s\w\d]+)/';
$subject = file_get_contents('vendor/twbs/bootstrap/scss/_variables.scss'); // just a regular SCSS file containing what I've already posted.
$lines = explode("\n",$subject);
$total_lines = count($lines);

foreach($lines as $line_no=>$line) {
  if(preg_match($pattern,$line,$matches)) {
    $match = $matches['remove'];
    $counter = 0;
    $open_braces = $closed_braces = 0;
      for($i=$line_no;$i<$total_lines;$i++) {
        $current = $lines[$i];
        $open_braces = substr_count($current,"{");
        $closed_braces = substr_count($current,"}");
        $counter += ($open_braces - $closed_braces);

        if($counter==0) {
          $start = $line_no;
          $end = $i;
           foreach(range($start,$end) as $a) {
             unset($lines[$a]);
           } // end foreach(range)
        break; // break out of this if!
       } // end for loop
      } // end preg_match
     } // endforeach

我们有一个$lines数组,没有任何函数或mixins。

有一种更优雅的方式可以做到这一点,但我没有时间或愿意为SCSS编写AST解析器

然而,这可以非常容易地适应制作被黑的一个!