From a57cd7ecc4527a6963f2fdf623ee4ef29b60dfec Mon Sep 17 00:00:00 2001 From: Anthony Goubard Date: Fri, 8 May 2020 16:23:43 +0200 Subject: [PATCH 1/2] Fixed build: * 1.5 target and source is no longer supported with latest version of Java * Variable already declared in same method --- README.md | 362 +++--- build.xml | 160 +-- .../yui/compressor/CssCompressor.java | 1093 ++++++++--------- 3 files changed, 807 insertions(+), 808 deletions(-) diff --git a/README.md b/README.md index 3f06983f..461afd88 100644 --- a/README.md +++ b/README.md @@ -1,181 +1,181 @@ -YUI Compressor - The Yahoo! JavaScript and CSS Compressor -========================================================= - -The YUI Compressor is a JavaScript compressor which, in addition to removing -comments and white-spaces, obfuscates local variables using the smallest -possible variable name. This obfuscation is safe, even when using constructs -such as 'eval' or 'with' (although the compression is not optimal in those -cases) Compared to jsmin, the average savings is around 20%. - -The YUI Compressor is also able to safely compress CSS files. The decision -on which compressor is being used is made on the file extension (js or css) - -Building --------- - - ant - -Testing -------- - - ./tests/suite.sh - - -Node.js Package ---------------- - -You can require compressor in a Node.js package and compress files and strings in async. -_It still uses Java under the hood_ - - npm i yuicompressor - -```javascript - -var compressor = require('yuicompressor'); - -compressor.compress('/path/to/file or String of JS', { - //Compressor Options: - charset: 'utf8', - type: 'js', - nomunge: true, - 'line-break': 80 -}, function(err, data, extra) { - //err If compressor encounters an error, it's stderr will be here - //data The compressed string, you write it out where you want it - //extra The stderr (warnings are printed here in case you want to echo them -}); - -``` - -Options: -* `charset` // defaults to 'utf8' -* `type` // defaults to 'js' -* `line-break` -* `nomunge` -* `preserve-semi` -* `disable-optimizations` - - -TODO ----- - -* Better Docs -* Help Pages - -Build Status ------------- - -[![Build Status](https://secure.travis-ci.org/yui/yuicompressor.svg?branch=master)](http://travis-ci.org/yui/yuicompressor) - - -Global Options --------------- - - -h, --help - Prints help on how to use the YUI Compressor - - --line-break - Some source control tools don't like files containing lines longer than, - say 8000 characters. The linebreak option is used in that case to split - long lines after a specific column. It can also be used to make the code - more readable, easier to debug (especially with the MS Script Debugger) - Specify 0 to get a line break after each semi-colon in JavaScript, and - after each rule in CSS. - - --type js|css - The type of compressor (JavaScript or CSS) is chosen based on the - extension of the input file name (.js or .css) This option is required - if no input file has been specified. Otherwise, this option is only - required if the input file extension is neither 'js' nor 'css'. - - --charset character-set - If a supported character set is specified, the YUI Compressor will use it - to read the input file. Otherwise, it will assume that the platform's - default character set is being used. The output file is encoded using - the same character set. - - -o outfile - - Place output in file outfile. If not specified, the YUI Compressor will - default to the standard output, which you can redirect to a file. - Supports a filter syntax for expressing the output pattern when there are - multiple input files. ex: - java -jar yuicompressor.jar -o '.css$:-min.css' *.css - ... will minify all .css files and save them as -min.css - - -v, --verbose - Display informational messages and warnings. - -JavaScript Only Options ------------------------ - - --nomunge - Minify only. Do not obfuscate local symbols. - - --preserve-semi - Preserve unnecessary semicolons (such as right before a '}') This option - is useful when compressed code has to be run through JSLint (which is the - case of YUI for example) - - --disable-optimizations - Disable all the built-in micro optimizations. - -Notes ------ - -* If no input file is specified, it defaults to stdin. - -* Supports wildcards for specifying multiple input files. - -* The YUI Compressor requires Java version >= 1.5. - -* It is possible to prevent a local variable, nested function or function -argument from being obfuscated by using "hints". A hint is a string that -is located at the very beginning of a function body like so: - -``` -function fn (arg1, arg2, arg3) { - "arg2:nomunge, localVar:nomunge, nestedFn:nomunge"; - - ... - var localVar; - ... - - function nestedFn () { - .... - } - - ... -} -``` -The hint itself disappears from the compressed file. - -* C-style comments starting with `/*!` are preserved. This is useful with - comments containing copyright/license information. As of 2.4.8, the '!' - is no longer dropped by YUICompressor. For example: - -``` -/*! - * TERMS OF USE - EASING EQUATIONS - * Open source under the BSD License. - * Copyright 2001 Robert Penner All rights reserved. - */ -``` - -remains in the output, untouched by YUICompressor. - -Modified Rhino Files --------------------- - -YUI Compressor uses a modified version of the Rhino library -(http://www.mozilla.org/rhino/) The changes were made to support -JScript conditional comments, preserved comments, unescaped slash -characters in regular expressions, and to allow for the optimization -of escaped quotes in string literals. - -Copyright And License ---------------------- - -Copyright (c) 2013 Yahoo! Inc. All rights reserved. -The copyrights embodied in the content of this file are licensed -by Yahoo! Inc. under the BSD (revised) open source license. +YUI Compressor - The Yahoo! JavaScript and CSS Compressor +========================================================= + +The YUI Compressor is a JavaScript compressor which, in addition to removing +comments and white-spaces, obfuscates local variables using the smallest +possible variable name. This obfuscation is safe, even when using constructs +such as 'eval' or 'with' (although the compression is not optimal in those +cases) Compared to jsmin, the average savings is around 20%. + +The YUI Compressor is also able to safely compress CSS files. The decision +on which compressor is being used is made on the file extension (js or css) + +Building +-------- + + ant + +Testing +------- + + ./tests/suite.sh + + +Node.js Package +--------------- + +You can require compressor in a Node.js package and compress files and strings in async. +_It still uses Java under the hood_ + + npm i yuicompressor + +```javascript + +var compressor = require('yuicompressor'); + +compressor.compress('/path/to/file or String of JS', { + //Compressor Options: + charset: 'utf8', + type: 'js', + nomunge: true, + 'line-break': 80 +}, function(err, data, extra) { + //err If compressor encounters an error, it's stderr will be here + //data The compressed string, you write it out where you want it + //extra The stderr (warnings are printed here in case you want to echo them +}); + +``` + +Options: +* `charset` // defaults to 'utf8' +* `type` // defaults to 'js' +* `line-break` +* `nomunge` +* `preserve-semi` +* `disable-optimizations` + + +TODO +---- + +* Better Docs +* Help Pages + +Build Status +------------ + +[![Build Status](https://secure.travis-ci.org/yui/yuicompressor.svg?branch=master)](http://travis-ci.org/yui/yuicompressor) + + +Global Options +-------------- + + -h, --help + Prints help on how to use the YUI Compressor + + --line-break + Some source control tools don't like files containing lines longer than, + say 8000 characters. The linebreak option is used in that case to split + long lines after a specific column. It can also be used to make the code + more readable, easier to debug (especially with the MS Script Debugger) + Specify 0 to get a line break after each semi-colon in JavaScript, and + after each rule in CSS. + + --type js|css + The type of compressor (JavaScript or CSS) is chosen based on the + extension of the input file name (.js or .css) This option is required + if no input file has been specified. Otherwise, this option is only + required if the input file extension is neither 'js' nor 'css'. + + --charset character-set + If a supported character set is specified, the YUI Compressor will use it + to read the input file. Otherwise, it will assume that the platform's + default character set is being used. The output file is encoded using + the same character set. + + -o outfile + + Place output in file outfile. If not specified, the YUI Compressor will + default to the standard output, which you can redirect to a file. + Supports a filter syntax for expressing the output pattern when there are + multiple input files. ex: + java -jar yuicompressor.jar -o '.css$:-min.css' *.css + ... will minify all .css files and save them as -min.css + + -v, --verbose + Display informational messages and warnings. + +JavaScript Only Options +----------------------- + + --nomunge + Minify only. Do not obfuscate local symbols. + + --preserve-semi + Preserve unnecessary semicolons (such as right before a '}') This option + is useful when compressed code has to be run through JSLint (which is the + case of YUI for example) + + --disable-optimizations + Disable all the built-in micro optimizations. + +Notes +----- + +* If no input file is specified, it defaults to stdin. + +* Supports wildcards for specifying multiple input files. + +* The YUI Compressor requires Java version >= 1.6. + +* It is possible to prevent a local variable, nested function or function +argument from being obfuscated by using "hints". A hint is a string that +is located at the very beginning of a function body like so: + +``` +function fn (arg1, arg2, arg3) { + "arg2:nomunge, localVar:nomunge, nestedFn:nomunge"; + + ... + var localVar; + ... + + function nestedFn () { + .... + } + + ... +} +``` +The hint itself disappears from the compressed file. + +* C-style comments starting with `/*!` are preserved. This is useful with + comments containing copyright/license information. As of 2.4.8, the '!' + is no longer dropped by YUICompressor. For example: + +``` +/*! + * TERMS OF USE - EASING EQUATIONS + * Open source under the BSD License. + * Copyright 2001 Robert Penner All rights reserved. + */ +``` + +remains in the output, untouched by YUICompressor. + +Modified Rhino Files +-------------------- + +YUI Compressor uses a modified version of the Rhino library +(http://www.mozilla.org/rhino/) The changes were made to support +JScript conditional comments, preserved comments, unescaped slash +characters in regular expressions, and to allow for the optimization +of escaped quotes in string literals. + +Copyright And License +--------------------- + +Copyright (c) 2013 Yahoo! Inc. All rights reserved. +The copyrights embodied in the content of this file are licensed +by Yahoo! Inc. under the BSD (revised) open source license. diff --git a/build.xml b/build.xml index a2663b36..64387864 100644 --- a/build.xml +++ b/build.xml @@ -1,80 +1,80 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/com/yahoo/platform/yui/compressor/CssCompressor.java b/src/com/yahoo/platform/yui/compressor/CssCompressor.java index 953bbd27..090be157 100644 --- a/src/com/yahoo/platform/yui/compressor/CssCompressor.java +++ b/src/com/yahoo/platform/yui/compressor/CssCompressor.java @@ -1,547 +1,546 @@ -/* - * YUI Compressor - * http://developer.yahoo.com/yui/compressor/ - * Author: Julien Lecomte - http://www.julienlecomte.net/ - * Author: Isaac Schlueter - http://foohack.com/ - * Author: Stoyan Stefanov - http://phpied.com/ - * Contributor: Dan Beam - http://danbeam.org/ - * Copyright (c) 2013 Yahoo! Inc. All rights reserved. - * The copyrights embodied in the content of this file are licensed - * by Yahoo! Inc. under the BSD (revised) open source license. - */ -package com.yahoo.platform.yui.compressor; - -import java.io.IOException; -import java.io.Reader; -import java.io.Writer; -import java.util.regex.Pattern; -import java.util.regex.Matcher; -import java.util.ArrayList; - -public class CssCompressor { - - private StringBuffer srcsb = new StringBuffer(); - - public CssCompressor(Reader in) throws IOException { - // Read the stream... - int c; - while ((c = in.read()) != -1) { - srcsb.append((char) c); - } - } - - /** - * @param css - full css string - * @param preservedToken - token to preserve - * @param tokenRegex - regex to find token - * @param removeWhiteSpace - remove any white space in the token - * @param preservedTokens - array of token values - * @return - */ - protected String preserveToken(String css, String preservedToken, - String tokenRegex, boolean removeWhiteSpace, ArrayList preservedTokens) { - - int maxIndex = css.length() - 1; - int appendIndex = 0; - - StringBuffer sb = new StringBuffer(); - - Pattern p = Pattern.compile(tokenRegex); - Matcher m = p.matcher(css); - - while (m.find()) { - int startIndex = m.start() + (preservedToken.length() + 1); - String terminator = m.group(1); - - // skip this, if CSS was already copied to "sb" upto this position - if (m.start() < appendIndex) { - continue; - } - - if (terminator.length() == 0) { - terminator = ")"; - } - - boolean foundTerminator = false; - - int endIndex = m.end() - 1; - while(foundTerminator == false && endIndex+1 <= maxIndex) { - endIndex = css.indexOf(terminator, endIndex+1); - - if (endIndex <= 0) { - break; - } else if ((endIndex > 0) && (css.charAt(endIndex-1) != '\\')) { - foundTerminator = true; - if (!")".equals(terminator)) { - endIndex = css.indexOf(")", endIndex); - } - } - } - - // Enough searching, start moving stuff over to the buffer - sb.append(css.substring(appendIndex, m.start())); - - if (foundTerminator) { - String token = css.substring(startIndex, endIndex); - if(removeWhiteSpace) - token = token.replaceAll("\\s+", ""); - preservedTokens.add(token); - - String preserver = preservedToken + "(___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___)"; - sb.append(preserver); - - appendIndex = endIndex + 1; - } else { - // No end terminator found, re-add the whole match. Should we throw/warn here? - sb.append(css.substring(m.start(), m.end())); - appendIndex = m.end(); - } - } - - sb.append(css.substring(appendIndex)); - - return sb.toString(); - } - - public void compress(Writer out, int linebreakpos) - throws IOException { - - Pattern p; - Matcher m; - String css = srcsb.toString(); - - int startIndex = 0; - int endIndex = 0; - int i = 0; - int max = 0; - ArrayList preservedTokens = new ArrayList(0); - ArrayList comments = new ArrayList(0); - String token; - int totallen = css.length(); - String placeholder; - - - StringBuffer sb = new StringBuffer(css); - - // collect all comment blocks... - while ((startIndex = sb.indexOf("/*", startIndex)) >= 0) { - endIndex = sb.indexOf("*/", startIndex + 2); - if (endIndex < 0) { - endIndex = totallen; - } - - token = sb.substring(startIndex + 2, endIndex); - comments.add(token); - sb.replace(startIndex + 2, endIndex, "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + (comments.size() - 1) + "___"); - startIndex += 2; - } - css = sb.toString(); - - - css = this.preserveToken(css, "url", "(?i)url\\(\\s*([\"']?)data\\:", true, preservedTokens); - css = this.preserveToken(css, "calc", "(?i)calc\\(\\s*([\"']?)", false, preservedTokens); - css = this.preserveToken(css, "progid:DXImageTransform.Microsoft.Matrix", "(?i)progid:DXImageTransform.Microsoft.Matrix\\s*([\"']?)", false, preservedTokens); - - - // preserve strings so their content doesn't get accidentally minified - sb = new StringBuffer(); - p = Pattern.compile("(\"([^\\\\\"]|\\\\.|\\\\)*\")|(\'([^\\\\\']|\\\\.|\\\\)*\')"); - m = p.matcher(css); - while (m.find()) { - token = m.group(); - char quote = token.charAt(0); - token = token.substring(1, token.length() - 1); - - // maybe the string contains a comment-like substring? - // one, maybe more? put'em back then - if (token.indexOf("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_") >= 0) { - for (i = 0, max = comments.size(); i < max; i += 1) { - token = token.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", comments.get(i).toString()); - } - } - - // minify alpha opacity in filter strings - token = token.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity="); - - preservedTokens.add(token); - String preserver = quote + "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___" + quote; - m.appendReplacement(sb, preserver); - } - m.appendTail(sb); - css = sb.toString(); - - - // strings are safe, now wrestle the comments - for (i = 0, max = comments.size(); i < max; i += 1) { - - token = comments.get(i).toString(); - placeholder = "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___"; - - // ! in the first position of the comment means preserve - // so push to the preserved tokens while stripping the ! - if (token.startsWith("!")) { - preservedTokens.add(token); - css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___"); - continue; - } - - // \ in the last position looks like hack for Mac/IE5 - // shorten that to /*\*/ and the next one to /**/ - if (token.endsWith("\\")) { - preservedTokens.add("\\"); - css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___"); - i = i + 1; // attn: advancing the loop - preservedTokens.add(""); - css = css.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___"); - continue; - } - - // keep empty comments after child selectors (IE7 hack) - // e.g. html >/**/ body - if (token.length() == 0) { - startIndex = css.indexOf(placeholder); - if (startIndex > 2) { - if (css.charAt(startIndex - 3) == '>') { - preservedTokens.add(""); - css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___"); - } - } - } - - // in all other cases kill the comment - css = css.replace("/*" + placeholder + "*/", ""); - } - - // preserve \9 IE hack - final String backslash9 = "\\9"; - while (css.indexOf(backslash9) > -1) { - preservedTokens.add(backslash9); - css = css.replace(backslash9, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___"); - } - - // Normalize all whitespace strings to single spaces. Easier to work with that way. - css = css.replaceAll("\\s+", " "); - - // Remove the spaces before the things that should not have spaces before them. - // But, be careful not to turn "p :link {...}" into "p:link{...}" - // Swap out any pseudo-class colons with the token, and then swap back. - sb = new StringBuffer(); - p = Pattern.compile("(^|\\})((^|([^\\{:])+):)+([^\\{]*\\{)"); - m = p.matcher(css); - while (m.find()) { - String s = m.group(); - s = s.replaceAll(":", "___YUICSSMIN_PSEUDOCLASSCOLON___"); - s = s.replaceAll( "\\\\", "\\\\\\\\" ).replaceAll( "\\$", "\\\\\\$" ); - m.appendReplacement(sb, s); - } - m.appendTail(sb); - css = sb.toString(); - // Remove spaces before the things that should not have spaces before them. - css = css.replaceAll("\\s+([!{};:>+\\(\\)\\],])", "$1"); - // Restore spaces for !important - css = css.replaceAll("!important", " !important"); - // bring back the colon - css = css.replaceAll("___YUICSSMIN_PSEUDOCLASSCOLON___", ":"); - - // retain space for special IE6 cases - sb = new StringBuffer(); - p = Pattern.compile("(?i):first\\-(line|letter)(\\{|,)"); - m = p.matcher(css); - while (m.find()) { - m.appendReplacement(sb, ":first-" + m.group(1).toLowerCase() + " " + m.group(2)); - } - m.appendTail(sb); - css = sb.toString(); - - // no space after the end of a preserved comment - css = css.replaceAll("\\*/ ", "*/"); - - // If there are multiple @charset directives, push them to the top of the file. - sb = new StringBuffer(); - p = Pattern.compile("(?i)^(.*)(@charset)( \"[^\"]*\";)"); - m = p.matcher(css); - while (m.find()) { - String s = m.group(1).replaceAll("\\\\", "\\\\\\\\").replaceAll("\\$", "\\\\\\$"); - m.appendReplacement(sb, m.group(2).toLowerCase() + m.group(3) + s); - } - m.appendTail(sb); - css = sb.toString(); - - // When all @charset are at the top, remove the second and after (as they are completely ignored). - sb = new StringBuffer(); - p = Pattern.compile("(?i)^((\\s*)(@charset)( [^;]+;\\s*))+"); - m = p.matcher(css); - while (m.find()) { - m.appendReplacement(sb, m.group(2) + m.group(3).toLowerCase() + m.group(4)); - } - m.appendTail(sb); - css = sb.toString(); - - // lowercase some popular @directives (@charset is done right above) - sb = new StringBuffer(); - p = Pattern.compile("(?i)@(font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframe|media|page|namespace)"); - m = p.matcher(css); - while (m.find()) { - m.appendReplacement(sb, '@' + m.group(1).toLowerCase()); - } - m.appendTail(sb); - css = sb.toString(); - - // lowercase some more common pseudo-elements - sb = new StringBuffer(); - p = Pattern.compile("(?i):(active|after|before|checked|disabled|empty|enabled|first-(?:child|of-type)|focus|hover|last-(?:child|of-type)|link|only-(?:child|of-type)|root|:selection|target|visited)"); - m = p.matcher(css); - while (m.find()) { - m.appendReplacement(sb, ':' + m.group(1).toLowerCase()); - } - m.appendTail(sb); - css = sb.toString(); - - // lowercase some more common functions - sb = new StringBuffer(); - p = Pattern.compile("(?i):(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|(?:-(?:moz|webkit)-)?any)\\("); - m = p.matcher(css); - while (m.find()) { - m.appendReplacement(sb, ':' + m.group(1).toLowerCase() + '('); - } - m.appendTail(sb); - css = sb.toString(); - - // lower case some common function that can be values - // NOTE: rgb() isn't useful as we replace with #hex later, as well as and() is already done for us right after this - sb = new StringBuffer(); - p = Pattern.compile("(?i)([:,\\( ]\\s*)(attr|color-stop|from|rgba|to|url|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:linear|radial)-gradient)|-webkit-gradient)"); - m = p.matcher(css); - while (m.find()) { - m.appendReplacement(sb, m.group(1) + m.group(2).toLowerCase()); - } - m.appendTail(sb); - css = sb.toString(); - - // Put the space back in some cases, to support stuff like - // @media screen and (-webkit-min-device-pixel-ratio:0){ - css = css.replaceAll("(?i)\\band\\(", "and ("); - - // Remove the spaces after the things that should not have spaces after them. - css = css.replaceAll("([!{}:;>+\\(\\[,])\\s+", "$1"); - - // remove unnecessary semicolons - css = css.replaceAll(";+}", "}"); - - // Replace 0(px,em) with 0. (don't replace seconds are they are needed for transitions to be valid) - String oldCss; - p = Pattern.compile("(?i)(^|: ?)((?:[0-9a-z-.]+ )*?)?(?:0?\\.)?0(?:px|em|in|cm|mm|pc|pt|ex|deg|g?rad|k?hz)"); - do { - oldCss = css; - m = p.matcher(css); - css = m.replaceAll("$1$20"); - } while (!(css.equals(oldCss))); - - // We do the same with % but don't replace the 0% in keyframes - String oldCss; - p = Pattern.compile("(?i)(: ?)((?:[0-9a-z-.]+ )*?)?(?:0?\\.)?0(?:%)"); - do { - oldCss = css; - m = p.matcher(css); - css = m.replaceAll("$1$20"); - } while (!(css.equals(oldCss))); - - //Replace the keyframe 100% step with 'to' which is shorter - p = Pattern.compile("(?i)(^|,|{) ?(?:100% ?{)"); - do { - oldCss = css; - m = p.matcher(css); - css = m.replaceAll("$1to{"); - } while (!(css.equals(oldCss))); - - // Replace 0(px,em,%) with 0 inside groups (e.g. -MOZ-RADIAL-GRADIENT(CENTER 45DEG, CIRCLE CLOSEST-SIDE, ORANGE 0%, RED 100%)) - p = Pattern.compile("(?i)\\( ?((?:[0-9a-z-.]+[ ,])*)?(?:0?\\.)?0(?:px|em|%|in|cm|mm|pc|pt|ex|deg|g?rad|m?s|k?hz)"); - do { - oldCss = css; - m = p.matcher(css); - css = m.replaceAll("($10"); - } while (!(css.equals(oldCss))); - - // Replace x.0(px,em,%) with x(px,em,%). - css = css.replaceAll("([0-9])\\.0(px|em|%|in|cm|mm|pc|pt|ex|deg|m?s|g?rad|k?hz| |;)", "$1$2"); - - // Replace 0 0 0 0; with 0. - css = css.replaceAll(":0 0 0 0(;|})", ":0$1"); - css = css.replaceAll(":0 0 0(;|})", ":0$1"); - css = css.replaceAll("(? 255) { - val = 255; - } - hexcolor.append(Integer.toHexString(val)); - } - m.appendReplacement(sb, hexcolor.toString()); - } - m.appendTail(sb); - css = sb.toString(); - - // Shorten colors from #AABBCC to #ABC. Note that we want to make sure - // the color is not preceded by either ", " or =. Indeed, the property - // filter: chroma(color="#FFFFFF"); - // would become - // filter: chroma(color="#FFF"); - // which makes the filter break in IE. - // We also want to make sure we're only compressing #AABBCC patterns inside { }, not id selectors ( #FAABAC {} ) - // We also want to avoid compressing invalid values (e.g. #AABBCCD to #ABCD) - p = Pattern.compile("(\\=\\s*?[\"']?)?" + "#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])" + "(:?\\}|[^0-9a-fA-F{][^{]*?\\})"); - - m = p.matcher(css); - sb = new StringBuffer(); - int index = 0; - - while (m.find(index)) { - - sb.append(css.substring(index, m.start())); - - boolean isFilter = (m.group(1) != null && !"".equals(m.group(1))); - - if (isFilter) { - // Restore, as is. Compression will break filters - sb.append(m.group(1) + "#" + m.group(2) + m.group(3) + m.group(4) + m.group(5) + m.group(6) + m.group(7)); - } else { - if( m.group(2).equalsIgnoreCase(m.group(3)) && - m.group(4).equalsIgnoreCase(m.group(5)) && - m.group(6).equalsIgnoreCase(m.group(7))) { - - // #AABBCC pattern - sb.append("#" + (m.group(3) + m.group(5) + m.group(7)).toLowerCase()); - - } else { - - // Non-compressible color, restore, but lower case. - sb.append("#" + (m.group(2) + m.group(3) + m.group(4) + m.group(5) + m.group(6) + m.group(7)).toLowerCase()); - } - } - - index = m.end(7); - } - - sb.append(css.substring(index)); - css = sb.toString(); - - // Replace #f00 -> red - css = css.replaceAll("(:|\\s)(#f00)(;|})", "$1red$3"); - // Replace other short color keywords - css = css.replaceAll("(:|\\s)(#000080)(;|})", "$1navy$3"); - css = css.replaceAll("(:|\\s)(#808080)(;|})", "$1gray$3"); - css = css.replaceAll("(:|\\s)(#808000)(;|})", "$1olive$3"); - css = css.replaceAll("(:|\\s)(#800080)(;|})", "$1purple$3"); - css = css.replaceAll("(:|\\s)(#c0c0c0)(;|})", "$1silver$3"); - css = css.replaceAll("(:|\\s)(#008080)(;|})", "$1teal$3"); - css = css.replaceAll("(:|\\s)(#ffa500)(;|})", "$1orange$3"); - css = css.replaceAll("(:|\\s)(#800000)(;|})", "$1maroon$3"); - - // border: none -> border:0 - sb = new StringBuffer(); - p = Pattern.compile("(?i)(border|border-top|border-right|border-bottom|border-left|outline|background):none(;|})"); - m = p.matcher(css); - while (m.find()) { - m.appendReplacement(sb, m.group(1).toLowerCase() + ":0" + m.group(2)); - } - m.appendTail(sb); - css = sb.toString(); - - // shorter opacity IE filter - css = css.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity="); - - // Find a fraction that is used for Opera's -o-device-pixel-ratio query - // Add token to add the "\" back in later - css = css.replaceAll("\\(([\\-A-Za-z]+):([0-9]+)\\/([0-9]+)\\)", "($1:$2___YUI_QUERY_FRACTION___$3)"); - - // Remove empty rules. - css = css.replaceAll("[^\\}\\{/;]+\\{\\}", ""); - - // Add "\" back to fix Opera -o-device-pixel-ratio query - css = css.replaceAll("___YUI_QUERY_FRACTION___", "/"); - - // TODO: Should this be after we re-insert tokens. These could alter the break points. However then - // we'd need to make sure we don't break in the middle of a string etc. - if (linebreakpos >= 0) { - // Some source control tools don't like it when files containing lines longer - // than, say 8000 characters, are checked in. The linebreak option is used in - // that case to split long lines after a specific column. - i = 0; - int linestartpos = 0; - sb = new StringBuffer(css); - while (i < sb.length()) { - char c = sb.charAt(i++); - if (c == '}' && i - linestartpos > linebreakpos) { - sb.insert(i, '\n'); - linestartpos = i; - } - } - - css = sb.toString(); - } - - // Replace multiple semi-colons in a row by a single one - // See SF bug #1980989 - css = css.replaceAll(";;+", ";"); - - // restore preserved comments and strings - for(i = 0, max = preservedTokens.size(); i < max; i++) { - css = css.replace("___YUICSSMIN_PRESERVED_TOKEN_" + i + "___", preservedTokens.get(i).toString()); - } - - // Add spaces back in between operators for css calc function - // https://developer.mozilla.org/en-US/docs/Web/CSS/calc - // Added by Eric Arnol-Martin (earnolmartin@gmail.com) - sb = new StringBuffer(); - p = Pattern.compile("calc\\([^\\)]*\\)"); - m = p.matcher(css); - while (m.find()) { - String s = m.group(); - - s = s.replaceAll("(?<=[-|%|px|em|rem|vw|\\d]+)\\+", " + "); - s = s.replaceAll("(?<=[-|%|px|em|rem|vw|\\d]+)\\-", " - "); - s = s.replaceAll("(?<=[-|%|px|em|rem|vw|\\d]+)\\*", " * "); - s = s.replaceAll("(?<=[-|%|px|em|rem|vw|\\d]+)\\/", " / "); - - m.appendReplacement(sb, s); - } - m.appendTail(sb); - css = sb.toString(); - - // Trim the final string (for any leading or trailing white spaces) - css = css.trim(); - - // Write the output... - out.write(css); - } -} +/* + * YUI Compressor + * http://developer.yahoo.com/yui/compressor/ + * Author: Julien Lecomte - http://www.julienlecomte.net/ + * Author: Isaac Schlueter - http://foohack.com/ + * Author: Stoyan Stefanov - http://phpied.com/ + * Contributor: Dan Beam - http://danbeam.org/ + * Copyright (c) 2013 Yahoo! Inc. All rights reserved. + * The copyrights embodied in the content of this file are licensed + * by Yahoo! Inc. under the BSD (revised) open source license. + */ +package com.yahoo.platform.yui.compressor; + +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import java.util.ArrayList; + +public class CssCompressor { + + private StringBuffer srcsb = new StringBuffer(); + + public CssCompressor(Reader in) throws IOException { + // Read the stream... + int c; + while ((c = in.read()) != -1) { + srcsb.append((char) c); + } + } + + /** + * @param css - full css string + * @param preservedToken - token to preserve + * @param tokenRegex - regex to find token + * @param removeWhiteSpace - remove any white space in the token + * @param preservedTokens - array of token values + * @return + */ + protected String preserveToken(String css, String preservedToken, + String tokenRegex, boolean removeWhiteSpace, ArrayList preservedTokens) { + + int maxIndex = css.length() - 1; + int appendIndex = 0; + + StringBuffer sb = new StringBuffer(); + + Pattern p = Pattern.compile(tokenRegex); + Matcher m = p.matcher(css); + + while (m.find()) { + int startIndex = m.start() + (preservedToken.length() + 1); + String terminator = m.group(1); + + // skip this, if CSS was already copied to "sb" upto this position + if (m.start() < appendIndex) { + continue; + } + + if (terminator.length() == 0) { + terminator = ")"; + } + + boolean foundTerminator = false; + + int endIndex = m.end() - 1; + while(foundTerminator == false && endIndex+1 <= maxIndex) { + endIndex = css.indexOf(terminator, endIndex+1); + + if (endIndex <= 0) { + break; + } else if ((endIndex > 0) && (css.charAt(endIndex-1) != '\\')) { + foundTerminator = true; + if (!")".equals(terminator)) { + endIndex = css.indexOf(")", endIndex); + } + } + } + + // Enough searching, start moving stuff over to the buffer + sb.append(css.substring(appendIndex, m.start())); + + if (foundTerminator) { + String token = css.substring(startIndex, endIndex); + if(removeWhiteSpace) + token = token.replaceAll("\\s+", ""); + preservedTokens.add(token); + + String preserver = preservedToken + "(___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___)"; + sb.append(preserver); + + appendIndex = endIndex + 1; + } else { + // No end terminator found, re-add the whole match. Should we throw/warn here? + sb.append(css.substring(m.start(), m.end())); + appendIndex = m.end(); + } + } + + sb.append(css.substring(appendIndex)); + + return sb.toString(); + } + + public void compress(Writer out, int linebreakpos) + throws IOException { + + Pattern p; + Matcher m; + String css = srcsb.toString(); + + int startIndex = 0; + int endIndex = 0; + int i = 0; + int max = 0; + ArrayList preservedTokens = new ArrayList(0); + ArrayList comments = new ArrayList(0); + String token; + int totallen = css.length(); + String placeholder; + + + StringBuffer sb = new StringBuffer(css); + + // collect all comment blocks... + while ((startIndex = sb.indexOf("/*", startIndex)) >= 0) { + endIndex = sb.indexOf("*/", startIndex + 2); + if (endIndex < 0) { + endIndex = totallen; + } + + token = sb.substring(startIndex + 2, endIndex); + comments.add(token); + sb.replace(startIndex + 2, endIndex, "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + (comments.size() - 1) + "___"); + startIndex += 2; + } + css = sb.toString(); + + + css = this.preserveToken(css, "url", "(?i)url\\(\\s*([\"']?)data\\:", true, preservedTokens); + css = this.preserveToken(css, "calc", "(?i)calc\\(\\s*([\"']?)", false, preservedTokens); + css = this.preserveToken(css, "progid:DXImageTransform.Microsoft.Matrix", "(?i)progid:DXImageTransform.Microsoft.Matrix\\s*([\"']?)", false, preservedTokens); + + + // preserve strings so their content doesn't get accidentally minified + sb = new StringBuffer(); + p = Pattern.compile("(\"([^\\\\\"]|\\\\.|\\\\)*\")|(\'([^\\\\\']|\\\\.|\\\\)*\')"); + m = p.matcher(css); + while (m.find()) { + token = m.group(); + char quote = token.charAt(0); + token = token.substring(1, token.length() - 1); + + // maybe the string contains a comment-like substring? + // one, maybe more? put'em back then + if (token.indexOf("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_") >= 0) { + for (i = 0, max = comments.size(); i < max; i += 1) { + token = token.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", comments.get(i).toString()); + } + } + + // minify alpha opacity in filter strings + token = token.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity="); + + preservedTokens.add(token); + String preserver = quote + "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___" + quote; + m.appendReplacement(sb, preserver); + } + m.appendTail(sb); + css = sb.toString(); + + + // strings are safe, now wrestle the comments + for (i = 0, max = comments.size(); i < max; i += 1) { + + token = comments.get(i).toString(); + placeholder = "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___"; + + // ! in the first position of the comment means preserve + // so push to the preserved tokens while stripping the ! + if (token.startsWith("!")) { + preservedTokens.add(token); + css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___"); + continue; + } + + // \ in the last position looks like hack for Mac/IE5 + // shorten that to /*\*/ and the next one to /**/ + if (token.endsWith("\\")) { + preservedTokens.add("\\"); + css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___"); + i = i + 1; // attn: advancing the loop + preservedTokens.add(""); + css = css.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___"); + continue; + } + + // keep empty comments after child selectors (IE7 hack) + // e.g. html >/**/ body + if (token.length() == 0) { + startIndex = css.indexOf(placeholder); + if (startIndex > 2) { + if (css.charAt(startIndex - 3) == '>') { + preservedTokens.add(""); + css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___"); + } + } + } + + // in all other cases kill the comment + css = css.replace("/*" + placeholder + "*/", ""); + } + + // preserve \9 IE hack + final String backslash9 = "\\9"; + while (css.indexOf(backslash9) > -1) { + preservedTokens.add(backslash9); + css = css.replace(backslash9, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___"); + } + + // Normalize all whitespace strings to single spaces. Easier to work with that way. + css = css.replaceAll("\\s+", " "); + + // Remove the spaces before the things that should not have spaces before them. + // But, be careful not to turn "p :link {...}" into "p:link{...}" + // Swap out any pseudo-class colons with the token, and then swap back. + sb = new StringBuffer(); + p = Pattern.compile("(^|\\})((^|([^\\{:])+):)+([^\\{]*\\{)"); + m = p.matcher(css); + while (m.find()) { + String s = m.group(); + s = s.replaceAll(":", "___YUICSSMIN_PSEUDOCLASSCOLON___"); + s = s.replaceAll( "\\\\", "\\\\\\\\" ).replaceAll( "\\$", "\\\\\\$" ); + m.appendReplacement(sb, s); + } + m.appendTail(sb); + css = sb.toString(); + // Remove spaces before the things that should not have spaces before them. + css = css.replaceAll("\\s+([!{};:>+\\(\\)\\],])", "$1"); + // Restore spaces for !important + css = css.replaceAll("!important", " !important"); + // bring back the colon + css = css.replaceAll("___YUICSSMIN_PSEUDOCLASSCOLON___", ":"); + + // retain space for special IE6 cases + sb = new StringBuffer(); + p = Pattern.compile("(?i):first\\-(line|letter)(\\{|,)"); + m = p.matcher(css); + while (m.find()) { + m.appendReplacement(sb, ":first-" + m.group(1).toLowerCase() + " " + m.group(2)); + } + m.appendTail(sb); + css = sb.toString(); + + // no space after the end of a preserved comment + css = css.replaceAll("\\*/ ", "*/"); + + // If there are multiple @charset directives, push them to the top of the file. + sb = new StringBuffer(); + p = Pattern.compile("(?i)^(.*)(@charset)( \"[^\"]*\";)"); + m = p.matcher(css); + while (m.find()) { + String s = m.group(1).replaceAll("\\\\", "\\\\\\\\").replaceAll("\\$", "\\\\\\$"); + m.appendReplacement(sb, m.group(2).toLowerCase() + m.group(3) + s); + } + m.appendTail(sb); + css = sb.toString(); + + // When all @charset are at the top, remove the second and after (as they are completely ignored). + sb = new StringBuffer(); + p = Pattern.compile("(?i)^((\\s*)(@charset)( [^;]+;\\s*))+"); + m = p.matcher(css); + while (m.find()) { + m.appendReplacement(sb, m.group(2) + m.group(3).toLowerCase() + m.group(4)); + } + m.appendTail(sb); + css = sb.toString(); + + // lowercase some popular @directives (@charset is done right above) + sb = new StringBuffer(); + p = Pattern.compile("(?i)@(font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframe|media|page|namespace)"); + m = p.matcher(css); + while (m.find()) { + m.appendReplacement(sb, '@' + m.group(1).toLowerCase()); + } + m.appendTail(sb); + css = sb.toString(); + + // lowercase some more common pseudo-elements + sb = new StringBuffer(); + p = Pattern.compile("(?i):(active|after|before|checked|disabled|empty|enabled|first-(?:child|of-type)|focus|hover|last-(?:child|of-type)|link|only-(?:child|of-type)|root|:selection|target|visited)"); + m = p.matcher(css); + while (m.find()) { + m.appendReplacement(sb, ':' + m.group(1).toLowerCase()); + } + m.appendTail(sb); + css = sb.toString(); + + // lowercase some more common functions + sb = new StringBuffer(); + p = Pattern.compile("(?i):(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|(?:-(?:moz|webkit)-)?any)\\("); + m = p.matcher(css); + while (m.find()) { + m.appendReplacement(sb, ':' + m.group(1).toLowerCase() + '('); + } + m.appendTail(sb); + css = sb.toString(); + + // lower case some common function that can be values + // NOTE: rgb() isn't useful as we replace with #hex later, as well as and() is already done for us right after this + sb = new StringBuffer(); + p = Pattern.compile("(?i)([:,\\( ]\\s*)(attr|color-stop|from|rgba|to|url|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:linear|radial)-gradient)|-webkit-gradient)"); + m = p.matcher(css); + while (m.find()) { + m.appendReplacement(sb, m.group(1) + m.group(2).toLowerCase()); + } + m.appendTail(sb); + css = sb.toString(); + + // Put the space back in some cases, to support stuff like + // @media screen and (-webkit-min-device-pixel-ratio:0){ + css = css.replaceAll("(?i)\\band\\(", "and ("); + + // Remove the spaces after the things that should not have spaces after them. + css = css.replaceAll("([!{}:;>+\\(\\[,])\\s+", "$1"); + + // remove unnecessary semicolons + css = css.replaceAll(";+}", "}"); + + // Replace 0(px,em) with 0. (don't replace seconds are they are needed for transitions to be valid) + String oldCss; + p = Pattern.compile("(?i)(^|: ?)((?:[0-9a-z-.]+ )*?)?(?:0?\\.)?0(?:px|em|in|cm|mm|pc|pt|ex|deg|g?rad|k?hz)"); + do { + oldCss = css; + m = p.matcher(css); + css = m.replaceAll("$1$20"); + } while (!(css.equals(oldCss))); + + // We do the same with % but don't replace the 0% in keyframes + p = Pattern.compile("(?i)(: ?)((?:[0-9a-z-.]+ )*?)?(?:0?\\.)?0(?:%)"); + do { + oldCss = css; + m = p.matcher(css); + css = m.replaceAll("$1$20"); + } while (!(css.equals(oldCss))); + + //Replace the keyframe 100% step with 'to' which is shorter + p = Pattern.compile("(?i)(^|,|{) ?(?:100% ?{)"); + do { + oldCss = css; + m = p.matcher(css); + css = m.replaceAll("$1to{"); + } while (!(css.equals(oldCss))); + + // Replace 0(px,em,%) with 0 inside groups (e.g. -MOZ-RADIAL-GRADIENT(CENTER 45DEG, CIRCLE CLOSEST-SIDE, ORANGE 0%, RED 100%)) + p = Pattern.compile("(?i)\\( ?((?:[0-9a-z-.]+[ ,])*)?(?:0?\\.)?0(?:px|em|%|in|cm|mm|pc|pt|ex|deg|g?rad|m?s|k?hz)"); + do { + oldCss = css; + m = p.matcher(css); + css = m.replaceAll("($10"); + } while (!(css.equals(oldCss))); + + // Replace x.0(px,em,%) with x(px,em,%). + css = css.replaceAll("([0-9])\\.0(px|em|%|in|cm|mm|pc|pt|ex|deg|m?s|g?rad|k?hz| |;)", "$1$2"); + + // Replace 0 0 0 0; with 0. + css = css.replaceAll(":0 0 0 0(;|})", ":0$1"); + css = css.replaceAll(":0 0 0(;|})", ":0$1"); + css = css.replaceAll("(? 255) { + val = 255; + } + hexcolor.append(Integer.toHexString(val)); + } + m.appendReplacement(sb, hexcolor.toString()); + } + m.appendTail(sb); + css = sb.toString(); + + // Shorten colors from #AABBCC to #ABC. Note that we want to make sure + // the color is not preceded by either ", " or =. Indeed, the property + // filter: chroma(color="#FFFFFF"); + // would become + // filter: chroma(color="#FFF"); + // which makes the filter break in IE. + // We also want to make sure we're only compressing #AABBCC patterns inside { }, not id selectors ( #FAABAC {} ) + // We also want to avoid compressing invalid values (e.g. #AABBCCD to #ABCD) + p = Pattern.compile("(\\=\\s*?[\"']?)?" + "#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])" + "(:?\\}|[^0-9a-fA-F{][^{]*?\\})"); + + m = p.matcher(css); + sb = new StringBuffer(); + int index = 0; + + while (m.find(index)) { + + sb.append(css.substring(index, m.start())); + + boolean isFilter = (m.group(1) != null && !"".equals(m.group(1))); + + if (isFilter) { + // Restore, as is. Compression will break filters + sb.append(m.group(1) + "#" + m.group(2) + m.group(3) + m.group(4) + m.group(5) + m.group(6) + m.group(7)); + } else { + if( m.group(2).equalsIgnoreCase(m.group(3)) && + m.group(4).equalsIgnoreCase(m.group(5)) && + m.group(6).equalsIgnoreCase(m.group(7))) { + + // #AABBCC pattern + sb.append("#" + (m.group(3) + m.group(5) + m.group(7)).toLowerCase()); + + } else { + + // Non-compressible color, restore, but lower case. + sb.append("#" + (m.group(2) + m.group(3) + m.group(4) + m.group(5) + m.group(6) + m.group(7)).toLowerCase()); + } + } + + index = m.end(7); + } + + sb.append(css.substring(index)); + css = sb.toString(); + + // Replace #f00 -> red + css = css.replaceAll("(:|\\s)(#f00)(;|})", "$1red$3"); + // Replace other short color keywords + css = css.replaceAll("(:|\\s)(#000080)(;|})", "$1navy$3"); + css = css.replaceAll("(:|\\s)(#808080)(;|})", "$1gray$3"); + css = css.replaceAll("(:|\\s)(#808000)(;|})", "$1olive$3"); + css = css.replaceAll("(:|\\s)(#800080)(;|})", "$1purple$3"); + css = css.replaceAll("(:|\\s)(#c0c0c0)(;|})", "$1silver$3"); + css = css.replaceAll("(:|\\s)(#008080)(;|})", "$1teal$3"); + css = css.replaceAll("(:|\\s)(#ffa500)(;|})", "$1orange$3"); + css = css.replaceAll("(:|\\s)(#800000)(;|})", "$1maroon$3"); + + // border: none -> border:0 + sb = new StringBuffer(); + p = Pattern.compile("(?i)(border|border-top|border-right|border-bottom|border-left|outline|background):none(;|})"); + m = p.matcher(css); + while (m.find()) { + m.appendReplacement(sb, m.group(1).toLowerCase() + ":0" + m.group(2)); + } + m.appendTail(sb); + css = sb.toString(); + + // shorter opacity IE filter + css = css.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity="); + + // Find a fraction that is used for Opera's -o-device-pixel-ratio query + // Add token to add the "\" back in later + css = css.replaceAll("\\(([\\-A-Za-z]+):([0-9]+)\\/([0-9]+)\\)", "($1:$2___YUI_QUERY_FRACTION___$3)"); + + // Remove empty rules. + css = css.replaceAll("[^\\}\\{/;]+\\{\\}", ""); + + // Add "\" back to fix Opera -o-device-pixel-ratio query + css = css.replaceAll("___YUI_QUERY_FRACTION___", "/"); + + // TODO: Should this be after we re-insert tokens. These could alter the break points. However then + // we'd need to make sure we don't break in the middle of a string etc. + if (linebreakpos >= 0) { + // Some source control tools don't like it when files containing lines longer + // than, say 8000 characters, are checked in. The linebreak option is used in + // that case to split long lines after a specific column. + i = 0; + int linestartpos = 0; + sb = new StringBuffer(css); + while (i < sb.length()) { + char c = sb.charAt(i++); + if (c == '}' && i - linestartpos > linebreakpos) { + sb.insert(i, '\n'); + linestartpos = i; + } + } + + css = sb.toString(); + } + + // Replace multiple semi-colons in a row by a single one + // See SF bug #1980989 + css = css.replaceAll(";;+", ";"); + + // restore preserved comments and strings + for(i = 0, max = preservedTokens.size(); i < max; i++) { + css = css.replace("___YUICSSMIN_PRESERVED_TOKEN_" + i + "___", preservedTokens.get(i).toString()); + } + + // Add spaces back in between operators for css calc function + // https://developer.mozilla.org/en-US/docs/Web/CSS/calc + // Added by Eric Arnol-Martin (earnolmartin@gmail.com) + sb = new StringBuffer(); + p = Pattern.compile("calc\\([^\\)]*\\)"); + m = p.matcher(css); + while (m.find()) { + String s = m.group(); + + s = s.replaceAll("(?<=[-|%|px|em|rem|vw|\\d]+)\\+", " + "); + s = s.replaceAll("(?<=[-|%|px|em|rem|vw|\\d]+)\\-", " - "); + s = s.replaceAll("(?<=[-|%|px|em|rem|vw|\\d]+)\\*", " * "); + s = s.replaceAll("(?<=[-|%|px|em|rem|vw|\\d]+)\\/", " / "); + + m.appendReplacement(sb, s); + } + m.appendTail(sb); + css = sb.toString(); + + // Trim the final string (for any leading or trailing white spaces) + css = css.trim(); + + // Write the output... + out.write(css); + } +} From 9e421e79a3fe168a7b757a0b78fe300a9b61cb28 Mon Sep 17 00:00:00 2001 From: Anthony Goubard Date: Fri, 8 May 2020 16:25:09 +0200 Subject: [PATCH 2/2] Fixed invalid regular expression: java.util.regex.PatternSyntaxException: Illegal repetition near index 8 (?i)(^|,|{) ?(?:100% ?{) --- src/com/yahoo/platform/yui/compressor/CssCompressor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/yahoo/platform/yui/compressor/CssCompressor.java b/src/com/yahoo/platform/yui/compressor/CssCompressor.java index 090be157..33351d0d 100644 --- a/src/com/yahoo/platform/yui/compressor/CssCompressor.java +++ b/src/com/yahoo/platform/yui/compressor/CssCompressor.java @@ -346,7 +346,7 @@ public void compress(Writer out, int linebreakpos) } while (!(css.equals(oldCss))); //Replace the keyframe 100% step with 'to' which is shorter - p = Pattern.compile("(?i)(^|,|{) ?(?:100% ?{)"); + p = Pattern.compile("(?i)(^|,|\\{) ?(?:100% ?\\{)"); do { oldCss = css; m = p.matcher(css);