regexcsssedminifyyui-compressor

sed regular expression to fix css minification


I'm currently working on a minification task as post commit hook. I'm using the current version of yui-compressor for CSS-minification.

Bad thing about the current version of yui-compressor: It breaks certain CSS3-rules that need whitespaces to function properly. (calc(10px + 10px))

To fix the issue i wrote a regular expression that should replace every occurence of calc(...) after compression.

my solution so far is the following RegEx:

Match: /calc\((.*?)([\/\+\-\*])(.*?)\)/g

Replace: calc(\1 \2 \3)

I used two online tools to validate my regular expression:

https://regex101.com/

https://regexr.com/

It also works in PHP. But as soon as i use "sed" only the last occurence per line is being replaced:

Compressed CSS: (before replace with regular expression)

.test{width:calc(1px+1px)}.test2{left:calc(4%+140px)}.test3{width:calc(1px+1px)}

.test{width:calc(1px-1px)}.test2{left:calc(4%-140px)}.test3{width:calc(1px-1px)}

.test{width:calc(1px*1px)}.test2{left:calc(4%*140px)}.test3{width:calc(1px*1px)}

.test{width:calc(1px/1px)}.test2{left:calc(4%/140px)}.test3{width:calc(1px/1px)}

CSS after regular expression: (and correct result)

.test{width:calc(1px + 1px)}.test2{left:calc(4% + 140px)}.test3{width:calc(1px + 1px)}

.test{width:calc(1px - 1px)}.test2{left:calc(4% - 140px)}.test3{width:calc(1px - 1px)}

.test{width:calc(1px * 1px)}.test2{left:calc(4% * 140px)}.test3{width:calc(1px * 1px)}

.test{width:calc(1px / 1px)}.test2{left:calc(4% / 140px)}.test3{width:calc(1px / 1px)}

sed in debian 8 - (loading the same rules from a file):

sed -r "s/calc\((.*?)([\/\+\-\*])(.*?)\)/calc(\1 \2 \3)/g" style.css

prints the following:

.test{width:calc(1px+1px)}.test2{left:calc(4%+140px)}.test3{width:calc(1px + 1px)}
.test{width:calc(1px-1px)}.test2{left:calc(4%-140px)}.test3{width:calc(1px-1px)}
.test{width:calc(1px*1px)}.test2{left:calc(4%*140px)}.test3{width:calc(1px * 1px)}
.test{width:calc(1px/1px)}.test2{left:calc(4%/140px)}.test3{width:calc(1px / 1px)}

It doesn't seem to work with sed. Does anyone have a clue what the heck is going on?

Thanks in advance!


Solution

  • You are trying to use PCRE non-greedy repetition .*?, but sed supports only POSIX BRE and ERE, which don't define the non-greedy extension.

    Instead, you have to modify your regular expression. In your case, you could use [^-+*/]* for the first captured group (left operand) -- to match everything until the operator, and [^)]* to match the second operand -- everything until the closing parenthesis. This will produce the output you expect:

    sed -E 's/calc\(([^-+*/]*)([-+*/])([^)]*)\)/calc(\1 \2 \3)/g' style.css
    #                ^^^^^^^^  ^^^^^^  ^^^^^
    #            left operand    op    right operand 
    #            all until op          all until )
    

    Note the -E is equivalent to -r, but is also works in non-GNU sed. Also, you don't need to escape your operators inside the brackets. In fact, almost nothing has to be escaped inside the brackets -- except the closing bracket if not supplied as the first character, and ^ if supplied as the first character.

    The output you were getting is easy to explain when you note the .*? is treated as a greedy .*, repeated zero or more times (?) -- The first .*? captures everything until the last operator in line, and the second .*? will capture the last operand, therefore "expanding" only the last calc expression in each line.