All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.liferay.portal.minifier.CSSCompressor Maven / Gradle / Ivy

/**
 * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 */

package com.liferay.portal.minifier;

import com.liferay.petra.string.StringBundler;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.StringUtil;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Iván Zaera Avellón
 * @deprecated As of Cavanaugh (7.4.x), with no direct replacement
 */
@Deprecated
public class CSSCompressor {

	public CSSCompressor(Reader reader) throws IOException {
		int c = 0;

		while ((c = reader.read()) != -1) {
			_sb.append((char)c);
		}
	}

	public void compress(Writer writer, int lineBreakPosition)
		throws IOException {

		String css = _sb.toString();
		List comments = new ArrayList<>();

		css = _preserveCandidateCommments(css, comments);

		List preservedTokens = new ArrayList<>();

		css = _preserveParensToken(css, "url", preservedTokens);

		css = _preserveParensToken(css, "calc", preservedTokens);

		css = _preserveToken(
			css, "progid:DXImageTransform.Microsoft.Matrix",
			"(?i)progid:DXImageTransform.Microsoft.Matrix\\s*([\"']?)", false,
			preservedTokens);

		css = _preserveStrings(css, comments, preservedTokens);

		css = _removeComments(css, comments, preservedTokens);

		css = _preserveIE9Hack(css, preservedTokens);

		css = _collapseWhitespace(css);

		css = _removeUnneededLeadingSpaces(css);

		css = _retainSpaceForSpecialIE6Cases(css);

		css = _removeWhitespaceAfterPreservedComments(css);

		css = _hoistCharsetDirectives(css);

		css = _collapseCharsetDirectives(css);

		css = _lowercaseDirectives(css);

		css = _lowercasePseudoElements(css);

		css = _lowercaseFunctions(css);

		css = _lowercaseFunctionsThatCanBeValues(css);

		css = _putSomeSpacesBack(css);

		css = _removeUnneededTrailingSpaces(css);

		css = _removeUnneededSemiColons(css);

		css = _replace0UnitWith0(css);

		css = _removeUnneededDecimals(css);

		css = _collapseMultipleZeroes(css);

		css = _restoreSomeMultipleZeroes(css);

		css = _removeIntegerZeroBeforeDecimals(css);

		css = _shortenRGBColors(css);

		css = _shortenTwinComponentDigitsColors(css);

		css = _shortenSymbolicNameColors(css);

		css = _replaceBorderNone(css);

		css = _shortenIEOpacityFilter(css);

		css = _removeEmptyRules(css);

		css = _applyLineBreak(css, lineBreakPosition);

		css = _removeMultipleSemicolons(css);

		css = _restorePreservedTokens(css, preservedTokens);

		css = css.trim();

		writer.write(css);
	}

	private String _applyLineBreak(String css, int lineBreakPosition) {
		if (lineBreakPosition >= 0) {
			StringBuffer sb = new StringBuffer(css);

			int i = 0;
			int lineStartPosition = 0;

			while (i < sb.length()) {
				char c = sb.charAt(i++);

				if ((c == '}') &&
					((i - lineStartPosition) > lineBreakPosition)) {

					sb.insert(i, '\n');
					lineStartPosition = i;
				}
			}

			css = sb.toString();
		}

		return css;
	}

	private String _collapseCharsetDirectives(String css) {
		StringBuffer sb = new StringBuffer();

		Matcher matcher = _collapseCharsetDirectivesPattern.matcher(css);

		while (matcher.find()) {
			matcher.appendReplacement(
				sb,
				StringBundler.concat(
					matcher.group(2), StringUtil.toLowerCase(matcher.group(3)),
					matcher.group(4)));
		}

		matcher.appendTail(sb);

		return sb.toString();
	}

	private String _collapseMultipleZeroes(String css) {
		css = css.replaceAll(":0 0 0 0(;|})", ":0$1");

		css = css.replaceAll(":0 0 0(;|})", ":0$1");

		return css.replaceAll("(? comments) {

		StringBuffer sb = new StringBuffer(css);

		int cssLength = css.length();
		int startIndex = 0;

		while ((startIndex = sb.indexOf("/*", startIndex)) >= 0) {
			int endIndex = sb.indexOf("*/", startIndex + 2);

			if (endIndex < 0) {
				endIndex = cssLength;
			}

			comments.add(sb.substring(startIndex + 2, endIndex));

			sb.replace(
				startIndex + 2, endIndex,
				"___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" +
					(comments.size() - 1) + "___");

			startIndex += 2;
		}

		return sb.toString();
	}

	private String _preserveIE9Hack(String css, List preservedTokens) {
		while (css.indexOf(_BACKSLASH_9) > -1) {
			preservedTokens.add(_BACKSLASH_9);

			css = StringUtil.replace(
				css, _BACKSLASH_9,
				"___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) +
					"___");
		}

		return css;
	}

	private String _preserveParensToken(
		String css, String preservedToken, List preservedTokens) {

		StringBuffer sb = new StringBuffer();

		int fromIndex = 0;
		int startIndex;

		while ((startIndex = css.indexOf(preservedToken + "(", fromIndex)) !=
					-1) {

			int index = startIndex + preservedToken.length() + 1;
			int nestingLevel = 1;

			while (nestingLevel > 0) {
				if (css.charAt(index) == '(') {
					nestingLevel++;
				}
				else if (css.charAt(index) == ')') {
					nestingLevel--;
				}

				index++;
			}

			sb.append(css.substring(fromIndex, startIndex));
			sb.append(preservedToken);
			sb.append("(___YUICSSMIN_PRESERVED_TOKEN_");
			sb.append(preservedTokens.size());
			sb.append("___)");

			preservedTokens.add(
				css.substring(
					startIndex + preservedToken.length() + 1, index - 1));

			fromIndex = index;
		}

		sb.append(css.substring(fromIndex));

		return sb.toString();
	}

	private String _preserveStrings(
		String css, List comments, List preservedTokens) {

		StringBuffer sb = new StringBuffer();

		Matcher matcher = _preserveStringsPattern.matcher(css);

		while (matcher.find()) {
			String token = matcher.group();

			char quote = token.charAt(0);

			token = token.substring(1, token.length() - 1);

			if (token.indexOf("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_") >=
					0) {

				for (int i = 0; i < comments.size(); i++) {
					token = StringUtil.replace(
						token,
						"___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___",
						comments.get(i));
				}
			}

			token = token.replaceAll(
				"(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=",
				"alpha(opacity=");

			preservedTokens.add(token);

			String preserver = StringBundler.concat(
				Character.toString(quote), "___YUICSSMIN_PRESERVED_TOKEN_",
				preservedTokens.size() - 1, "___", Character.toString(quote));

			matcher.appendReplacement(sb, preserver);
		}

		matcher.appendTail(sb);

		return sb.toString();
	}

	private String _preserveToken(
		String css, String preservedToken, String tokenRegex,
		boolean removeWhiteSpace, List preservedTokens) {

		StringBuffer sb = new StringBuffer();

		int appendIndex = 0;

		Pattern pattern = Pattern.compile(tokenRegex);

		Matcher matcher = pattern.matcher(css);

		int maxIndex = css.length() - 1;

		while (matcher.find()) {
			if (matcher.start() < appendIndex) {
				continue;
			}

			String terminator = matcher.group(1);

			if (terminator.length() == 0) {
				terminator = ")";
			}

			boolean foundTerminator = false;

			int endIndex = matcher.end() - 1;

			while (!foundTerminator && ((endIndex + 1) <= maxIndex)) {
				endIndex = css.indexOf(terminator, endIndex + 1);

				if (endIndex <= 0) {
					break;
				}
				else if ((endIndex > 0) && (css.charAt(endIndex - 1) != '\\')) {
					foundTerminator = true;

					if (!Objects.equals(terminator, ")")) {
						endIndex = css.indexOf(")", endIndex);
					}
				}
			}

			sb.append(css.substring(appendIndex, matcher.start()));

			if (foundTerminator) {
				int startIndex =
					matcher.start() + (preservedToken.length() + 1);

				String token = css.substring(startIndex, endIndex);

				if (removeWhiteSpace) {
					token = token.replaceAll("\\s+", "");
				}

				preservedTokens.add(token);

				String preserver = StringBundler.concat(
					preservedToken, "(___YUICSSMIN_PRESERVED_TOKEN_",
					preservedTokens.size() - 1, "___)");

				sb.append(preserver);

				appendIndex = endIndex + 1;
			}
			else {
				sb.append(css.substring(matcher.start(), matcher.end()));
				appendIndex = matcher.end();
			}
		}

		sb.append(css.substring(appendIndex));

		return sb.toString();
	}

	private String _putSomeSpacesBack(String css) {
		return css.replaceAll("(?i)\\band\\(", "and (");
	}

	private String _removeComments(
		String css, List comments, List preservedTokens) {

		for (int i = 0; i < comments.size(); i++) {
			String comment = comments.get(i);

			String placeholder =
				"___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___";

			if (comment.startsWith("!")) {
				preservedTokens.add(comment);

				css = StringUtil.replace(
					css, placeholder,
					"___YUICSSMIN_PRESERVED_TOKEN_" +
						(preservedTokens.size() - 1) + "___");

				continue;
			}

			if (comment.endsWith("\\")) {
				preservedTokens.add("\\");

				css = StringUtil.replace(
					css, placeholder,
					"___YUICSSMIN_PRESERVED_TOKEN_" +
						(preservedTokens.size() - 1) + "___");

				i = i + 1;

				preservedTokens.add("");

				css = StringUtil.replace(
					css, "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___",
					"___YUICSSMIN_PRESERVED_TOKEN_" +
						(preservedTokens.size() - 1) + "___");

				continue;
			}

			if (comment.length() == 0) {
				int startIndex = css.indexOf(placeholder);

				if ((startIndex > 2) && (css.charAt(startIndex - 3) == '>')) {
					preservedTokens.add("");

					css = StringUtil.replace(
						css, placeholder,
						"___YUICSSMIN_PRESERVED_TOKEN_" +
							(preservedTokens.size() - 1) + "___");
				}
			}

			css = StringUtil.removeSubstring(css, "/*" + placeholder + "*/");
		}

		return css;
	}

	private String _removeEmptyRules(String css) {
		css = css.replaceAll(
			"\\(([\\-A-Za-z]+):([0-9]+)\\/([0-9]+)\\)",
			"($1:$2___YUI_QUERY_FRACTION___$3)");

		css = css.replaceAll("[^\\}\\{/;]+\\{\\}", "");

		return css.replaceAll("___YUI_QUERY_FRACTION___", "/");
	}

	private String _removeIntegerZeroBeforeDecimals(String css) {
		return css.replaceAll("(:|\\s)0+\\.(\\d+)", "$1.$2");
	}

	private String _removeMultipleSemicolons(String css) {
		return css.replaceAll(";;+", ";");
	}

	private String _removeUnneededDecimals(String css) {
		return css.replaceAll(
			"([0-9])\\.0(px|em|%|in|cm|mm|pc|pt|ex|deg|g?rad|m?s|k?hz| |;)",
			"$1$2");
	}

	private String _removeUnneededLeadingSpaces(String css) {
		StringBuffer sb = new StringBuffer();

		Matcher matcher = _removeUnneededLeadingSpacesPattern.matcher(css);

		while (matcher.find()) {
			String group = matcher.group();

			group = group.replaceAll(":", "___YUICSSMIN_PSEUDOCLASSCOLON___");

			group = group.replaceAll("\\\\", "\\\\\\\\");

			group = group.replaceAll("\\$", "\\\\\\$");

			matcher.appendReplacement(sb, group);
		}

		matcher.appendTail(sb);

		css = sb.toString();

		css = css.replaceAll("\\s+([!{};:>+\\(\\)\\],])", "$1");

		css = css.replaceAll("!important", " !important");

		css = css.replaceAll("___YUICSSMIN_PSEUDOCLASSCOLON___", ":");

		return css;
	}

	private String _removeUnneededSemiColons(String css) {
		return css.replaceAll(";+}", "}");
	}

	private String _removeUnneededTrailingSpaces(String css) {
		return css.replaceAll("([!{}:;>+\\(\\[,])\\s+", "$1");
	}

	private String _removeWhitespaceAfterPreservedComments(String css) {
		return css.replaceAll("\\*/ ", "*/");
	}

	private String _replace0UnitWith0(String css) {
		String oldCss = null;

		do {
			oldCss = css;

			Matcher matcher = _replace0UnitWith0Pattern1.matcher(css);

			css = matcher.replaceAll("$1$20");
		}
		while (!css.equals(oldCss));

		do {
			oldCss = css;

			Matcher matcher = _replace0UnitWith0Pattern2.matcher(css);

			css = matcher.replaceAll("($10");
		}
		while (!css.equals(oldCss));

		return css;
	}

	private String _replaceBorderNone(String css) {
		StringBuffer sb = new StringBuffer();

		Matcher matcher = _replaceBorderNonePattern.matcher(css);

		while (matcher.find()) {
			matcher.appendReplacement(
				sb,
				StringUtil.toLowerCase(matcher.group(1)) + ":0" +
					matcher.group(2));
		}

		matcher.appendTail(sb);

		return sb.toString();
	}

	private String _restorePreservedTokens(
		String css, List preservedTokens) {

		for (int i = 0; i < preservedTokens.size(); i++) {
			String preservedToken = preservedTokens.get(i);

			css = StringUtil.replace(
				css, "___YUICSSMIN_PRESERVED_TOKEN_" + i + "___",
				preservedToken);
		}

		return css;
	}

	private String _restoreSomeMultipleZeroes(String css) {
		Matcher matcher = _restoreSomeMultipleZeroesPattern.matcher(css);

		StringBuffer sb = new StringBuffer();

		while (matcher.find()) {
			matcher.appendReplacement(
				sb,
				StringUtil.toLowerCase(matcher.group(1)) + ":0 0" +
					matcher.group(2));
		}

		matcher.appendTail(sb);

		return sb.toString();
	}

	private String _retainSpaceForSpecialIE6Cases(String css) {
		StringBuffer sb = new StringBuffer();

		Matcher matcher = _retainSpaceForSpecialIE6CasesPattern.matcher(css);

		while (matcher.find()) {
			matcher.appendReplacement(
				sb,
				StringBundler.concat(
					":first-", StringUtil.toLowerCase(matcher.group(1)), " ",
					matcher.group(2)));
		}

		matcher.appendTail(sb);

		return sb.toString();
	}

	private String _shortenIEOpacityFilter(String css) {
		return css.replaceAll(
			"(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=",
			"alpha(opacity=");
	}

	private String _shortenRGBColors(String css) {
		StringBuffer sb = new StringBuffer();

		Matcher matcher = _shortenRGBColorsPattern.matcher(css);

		while (matcher.find()) {
			String group = matcher.group(1);

			String[] rgbColors = group.split(",");

			StringBuffer hexColor = new StringBuffer("#");

			for (String rgbColor : rgbColors) {
				int val = GetterUtil.getInteger(rgbColor);

				if (val < 16) {
					hexColor.append("0");
				}

				if (val > 255) {
					val = 255;
				}

				hexColor.append(Integer.toHexString(val));
			}

			matcher.appendReplacement(sb, hexColor.toString());
		}

		matcher.appendTail(sb);

		return sb.toString();
	}

	private String _shortenSymbolicNameColors(String css) {
		css = css.replaceAll("(:|\\s)(#f00)(;|})", "$1red$3");

		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");

		return css;
	}

	private String _shortenTwinComponentDigitsColors(String css) {
		StringBuffer sb = new StringBuffer();

		int index = 0;
		Matcher matcher = _shortenTwinComponentDigitsColorsPattern.matcher(css);

		while (matcher.find(index)) {
			sb.append(css.substring(index, matcher.start()));

			boolean filter = false;

			if ((matcher.group(1) != null) &&
				!Objects.equals(matcher.group(1), "")) {

				filter = true;
			}

			if (filter) {
				sb.append(matcher.group(1));

				sb.append("#");

				sb.append(matcher.group(2));

				sb.append(matcher.group(3));

				sb.append(matcher.group(4));

				sb.append(matcher.group(5));

				sb.append(matcher.group(6));

				sb.append(matcher.group(7));
			}
			else {
				if (StringUtil.equalsIgnoreCase(
						matcher.group(2), matcher.group(3)) &&
					StringUtil.equalsIgnoreCase(
						matcher.group(4), matcher.group(5)) &&
					StringUtil.equalsIgnoreCase(
						matcher.group(6), matcher.group(7))) {

					sb.append("#");

					sb.append(StringUtil.toLowerCase(matcher.group(3)));

					sb.append(StringUtil.toLowerCase(matcher.group(5)));

					sb.append(StringUtil.toLowerCase(matcher.group(7)));
				}
				else {
					sb.append("#");

					sb.append(StringUtil.toLowerCase(matcher.group(2)));

					sb.append(StringUtil.toLowerCase(matcher.group(3)));

					sb.append(StringUtil.toLowerCase(matcher.group(4)));

					sb.append(StringUtil.toLowerCase(matcher.group(5)));

					sb.append(StringUtil.toLowerCase(matcher.group(6)));

					sb.append(StringUtil.toLowerCase(matcher.group(7)));
				}
			}

			index = matcher.end(7);
		}

		sb.append(css.substring(index));

		return sb.toString();
	}

	private static final String _BACKSLASH_9 = "\\9";

	private static final Pattern _collapseCharsetDirectivesPattern =
		Pattern.compile("(?i)^((\\s*)(@charset)( [^;]+;\\s*))+");
	private static final Pattern _hoistCharsetDirectivesPattern =
		Pattern.compile("(?i)^(.*)(@charset)( \"[^\"]*\";)");
	private static final Pattern _lowercaseDirectivesPattern = Pattern.compile(
		"(?i)@(font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?" +
			"keyframe|media|page|namespace)");
	private static final Pattern _lowercaseFunctionsPattern = Pattern.compile(
		"(?i):(lang|not|nth-child|nth-last-child|nth-last-of-type|" +
			"nth-of-type|(?:-(?:moz|webkit)-)?any)\\(");
	private static final Pattern _lowercaseFunctionsThatCanBeValuesPattern =
		Pattern.compile(
			StringBundler.concat(
				"(?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)"));
	private static final Pattern _lowercasePseudoElementsPattern =
		Pattern.compile(
			StringBundler.concat(
				"(?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)"));
	private static final Pattern _preserveStringsPattern = Pattern.compile(
		"(\"([^\\\\\"]|\\\\.|\\\\)*\")|(\'([^\\\\\']|\\\\.|\\\\)*\')");
	private static final Pattern _removeUnneededLeadingSpacesPattern =
		Pattern.compile("(^|\\})((^|([^\\{:])+):)+([^\\{]*\\{)");
	private static final Pattern _replace0UnitWith0Pattern1 = Pattern.compile(
		"(?i)(^|: ?)((?:[0-9a-z-.]+ )*?)?(?:0?\\.)?0(?:px|em|%|in|cm|mm|" +
			"pc|pt|ex|deg|g?rad|m?s|k?hz)");
	private static final Pattern _replace0UnitWith0Pattern2 = Pattern.compile(
		"(?i)\\( ?((?:[0-9a-z-.]+[ ,])*)?(?:0?\\.)?0(?:px|em|%|in|cm|mm|" +
			"pc|pt|ex|deg|g?rad|m?s|k?hz)");
	private static final Pattern _replaceBorderNonePattern = Pattern.compile(
		"(?i)(border|border-top|border-right|border-bottom|border-left|" +
			"outline|background):none(;|})");
	private static final Pattern _restoreSomeMultipleZeroesPattern =
		Pattern.compile(
			StringBundler.concat(
				"(?i)(background-position|webkit-mask-position|",
				"transform-origin|webkit-transform-origin|",
				"moz-transform-origin|o-transform-origin|",
				"ms-transform-origin):0(;|})"));
	private static final Pattern _retainSpaceForSpecialIE6CasesPattern =
		Pattern.compile("(?i):first\\-(line|letter)(\\{|,)");
	private static final Pattern _shortenRGBColorsPattern = Pattern.compile(
		"rgb\\s*\\(\\s*([0-9,\\s]+)\\s*\\)");
	private static final Pattern _shortenTwinComponentDigitsColorsPattern =
		Pattern.compile(
			StringBundler.concat(
				"(\\=\\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{][^{]*?\\})"));

	private final StringBuffer _sb = new StringBuffer();

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy