mirror of
https://github.com/gnosygnu/xowa.git
synced 2024-10-27 20:34:16 +00:00
TemplateStyles: Add XoCssMin from @desb42 [#704]
This commit is contained in:
parent
e9e5724a2a
commit
73a56ffab3
78
100_core/src/gplx/core/tooling/asserts/TestAssert.java
Normal file
78
100_core/src/gplx/core/tooling/asserts/TestAssert.java
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package gplx.core.tooling.asserts;
|
||||||
|
|
||||||
|
import gplx.core.tests.Gftest;
|
||||||
|
import gplx.core.tooling.dataCollectors.GfoDataCollectorGrp;
|
||||||
|
import gplx.core.tooling.dataCollectors.GfoDataCollectorMgr;
|
||||||
|
import gplx.langs.java.util.List_;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface TestAssert {
|
||||||
|
void Test(GfoDataCollectorGrp actlItm);
|
||||||
|
void NotePrepend(String s);
|
||||||
|
static void Test(String note, GfoDataCollectorMgr mgr, TestAssert.Grp[] itms) {
|
||||||
|
for (TestAssert.Grp itm : itms) {
|
||||||
|
itm.NotePrepend(note);
|
||||||
|
itm.Test(mgr.GetGrp(itm.Key()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class Grp implements TestAssert {
|
||||||
|
private String key;
|
||||||
|
private TestAssert[] rules;
|
||||||
|
public Grp(String key, TestAssert... rules) {
|
||||||
|
this.key = key;
|
||||||
|
this.rules = rules;
|
||||||
|
}
|
||||||
|
public String Key() {return key;}
|
||||||
|
public void NotePrepend(String s) {
|
||||||
|
note = s;
|
||||||
|
for (TestAssert rule : rules) {
|
||||||
|
rule.NotePrepend(s);
|
||||||
|
}
|
||||||
|
} private String note;
|
||||||
|
public void Test(GfoDataCollectorGrp grp) {
|
||||||
|
for (TestAssert rule : rules) {
|
||||||
|
rule.Test(grp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class StringEq implements TestAssert {
|
||||||
|
private String key;
|
||||||
|
private String expdVal;
|
||||||
|
public StringEq(String key, String expd) {
|
||||||
|
this.key = key;
|
||||||
|
this.expdVal = expd;
|
||||||
|
}
|
||||||
|
public void NotePrepend(String s) {note = s + "." + key;} private String note;
|
||||||
|
public void Test(GfoDataCollectorGrp grp) {
|
||||||
|
String actlVal = (String)grp.Get(key);
|
||||||
|
Gftest.Eq__str(expdVal, actlVal, note);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class StringHas implements TestAssert {
|
||||||
|
private String key;
|
||||||
|
private String expdVal;
|
||||||
|
public StringHas(String key, String expd) {
|
||||||
|
this.key = key;
|
||||||
|
this.expdVal = expd;
|
||||||
|
}
|
||||||
|
public void NotePrepend(String s) {note = s + "." + key;} private String note;
|
||||||
|
public void Test(GfoDataCollectorGrp grp) {
|
||||||
|
String actlVal = (String)grp.Get(key);
|
||||||
|
Gftest.Eq__bool_y(actlVal.contains(expdVal), note);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class ListEq implements TestAssert {
|
||||||
|
private String key;
|
||||||
|
private List<?> expdList;
|
||||||
|
public ListEq(String key, Object... expd) {
|
||||||
|
this.key = key;
|
||||||
|
this.expdList = List_.NewByAry(expd);
|
||||||
|
}
|
||||||
|
public void NotePrepend(String s) {note = s + "." + key;} private String note;
|
||||||
|
public void Test(GfoDataCollectorGrp grp) {
|
||||||
|
List<?> actlList = (List<?>)grp.Get(key);
|
||||||
|
Gftest.Eq__ary(expdList.toArray(), actlList.toArray(), note);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package gplx.core.tooling.dataCollectors;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class GfoDataCollectorGrp {
|
||||||
|
private final String key;
|
||||||
|
private final LinkedHashMap<String, Object> hash = new LinkedHashMap<>();
|
||||||
|
public GfoDataCollectorGrp(String key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
public String Key() {return key;}
|
||||||
|
public GfoDataCollectorGrp Add(String dataKey, String dataVal) {
|
||||||
|
hash.put(dataKey, dataVal);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public GfoDataCollectorGrp Add(String dataKey, List<?> dataVal) {
|
||||||
|
hash.put(dataKey, dataVal);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public Object Get(String dataKey) {
|
||||||
|
return hash.get(dataKey);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package gplx.core.tooling.dataCollectors;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
|
||||||
|
public class GfoDataCollectorMgr {
|
||||||
|
private final LinkedHashMap<String, GfoDataCollectorGrp> hash = new LinkedHashMap<>();
|
||||||
|
public GfoDataCollectorGrp GetGrp(String grpKey) {return hash.get(grpKey);}
|
||||||
|
public GfoDataCollectorGrp AddGrp(String grpKey) {
|
||||||
|
GfoDataCollectorGrp grp = new GfoDataCollectorGrp(grpKey);
|
||||||
|
hash.put(grpKey, grp);
|
||||||
|
return grp;
|
||||||
|
}
|
||||||
|
}
|
13
100_core/src/gplx/langs/java/util/List_.java
Normal file
13
100_core/src/gplx/langs/java/util/List_.java
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package gplx.langs.java.util;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class List_ {
|
||||||
|
public static List<Object> NewByAry(Object[] ary) {
|
||||||
|
List<Object> rv = new ArrayList<>();
|
||||||
|
for (Object o : ary)
|
||||||
|
rv.add(o);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
}
|
132
400_xowa/src/gplx/langs/javascripts/JsString_.java
Normal file
132
400_xowa/src/gplx/langs/javascripts/JsString_.java
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
package gplx.langs.javascripts;
|
||||||
|
|
||||||
|
import gplx.Err_;
|
||||||
|
import gplx.langs.javascripts.util.regex.JsPattern_;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class JsString_ {
|
||||||
|
public static String slice(String str, int beginIndex) {
|
||||||
|
return slice(str, beginIndex, str.length());
|
||||||
|
}
|
||||||
|
// REF.JOS:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice
|
||||||
|
public static String slice(String str, int beginIndex, int endIndex) {
|
||||||
|
int srcLen = str.length();
|
||||||
|
|
||||||
|
// bgn
|
||||||
|
if (beginIndex < 0) {
|
||||||
|
beginIndex = srcLen + beginIndex;
|
||||||
|
if (beginIndex < 0) {
|
||||||
|
beginIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (beginIndex > srcLen) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// end
|
||||||
|
if (endIndex < 0) {
|
||||||
|
endIndex = srcLen + endIndex;
|
||||||
|
if (endIndex < 0) {
|
||||||
|
endIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (endIndex > srcLen) {
|
||||||
|
endIndex = srcLen;
|
||||||
|
}
|
||||||
|
if (endIndex < beginIndex) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return str.substring(beginIndex, endIndex);
|
||||||
|
}
|
||||||
|
private static final int
|
||||||
|
REPLACE_TYPE_STR = 0
|
||||||
|
, REPLACE_TYPE_MATCHER = 1
|
||||||
|
, REPLACE_TYPE_ARG1 = 2
|
||||||
|
, REPLACE_TYPE_ARG2 = 3
|
||||||
|
, REPLACE_TYPE_ARG3 = 4
|
||||||
|
;
|
||||||
|
public interface JsStringReplaceArg1 {
|
||||||
|
String processMatcher(String all);
|
||||||
|
}
|
||||||
|
public interface JsStringReplaceArg2 {
|
||||||
|
String processMatcher(String all, String arg1);
|
||||||
|
}
|
||||||
|
public interface JsStringReplaceArg3 {
|
||||||
|
String processMatcher(String all, String arg1, String arg2);
|
||||||
|
}
|
||||||
|
public interface JsStringReplaceMatcher {
|
||||||
|
String processMatcher(Matcher m);
|
||||||
|
}
|
||||||
|
public static String replace(String s, ConcurrentHashMap<String, Pattern> map, String p, String repl) {
|
||||||
|
return replace(s, map, p, JsPattern_.NONE, repl);
|
||||||
|
}
|
||||||
|
public static String replace(String src, ConcurrentHashMap<String, Pattern> map, String pat, int patFlags, String repl) {
|
||||||
|
return replaceObject(src, map, pat, patFlags, repl, REPLACE_TYPE_STR);
|
||||||
|
}
|
||||||
|
public static String replaceArg1(String src, ConcurrentHashMap<String, Pattern> map, String pat, JsStringReplaceArg1 func) {
|
||||||
|
return replaceObject(src, map, pat, JsPattern_.NONE, func, REPLACE_TYPE_ARG1);
|
||||||
|
}
|
||||||
|
public static String replaceArg1(String src, ConcurrentHashMap<String, Pattern> map, String pat, int patFlags, JsStringReplaceArg1 func) {
|
||||||
|
return replaceObject(src, map, pat, patFlags, func, REPLACE_TYPE_ARG1);
|
||||||
|
}
|
||||||
|
public static String replaceArg2(String src, ConcurrentHashMap<String, Pattern> map, String pat, JsStringReplaceArg2 func) {
|
||||||
|
return replaceObject(src, map, pat, JsPattern_.NONE, func, REPLACE_TYPE_ARG2);
|
||||||
|
}
|
||||||
|
public static String replaceArg2(String src, ConcurrentHashMap<String, Pattern> map, String pat, int patFlags, JsStringReplaceArg2 func) {
|
||||||
|
return replaceObject(src, map, pat, patFlags, func, REPLACE_TYPE_ARG2);
|
||||||
|
}
|
||||||
|
public static String replaceArg3(String src, ConcurrentHashMap<String, Pattern> map, String pat, int patFlags, JsStringReplaceArg3 func) {
|
||||||
|
return replaceObject(src, map, pat, patFlags, func, REPLACE_TYPE_ARG3);
|
||||||
|
}
|
||||||
|
public static String replaceMatcher(String src, ConcurrentHashMap<String, Pattern> map, String pat, int patFlags, JsStringReplaceMatcher func) {
|
||||||
|
return replaceObject(src, map, pat, patFlags, func, REPLACE_TYPE_MATCHER);
|
||||||
|
}
|
||||||
|
private static String replaceObject(String src, ConcurrentHashMap<String, Pattern> map, String pat, int patFlags, Object replObj, int replType) {
|
||||||
|
Pattern pattern = JsPattern_.getOrCompile(map, pat, patFlags);
|
||||||
|
|
||||||
|
// match
|
||||||
|
Matcher m = pattern.matcher(src);
|
||||||
|
StringBuffer sb = null;
|
||||||
|
while (m.find()) {
|
||||||
|
// get repl
|
||||||
|
String repl = null;
|
||||||
|
switch (replType) {
|
||||||
|
case REPLACE_TYPE_STR:
|
||||||
|
repl = (String)replObj;
|
||||||
|
break;
|
||||||
|
case REPLACE_TYPE_MATCHER:
|
||||||
|
repl = ((JsStringReplaceMatcher)replObj).processMatcher(m);
|
||||||
|
break;
|
||||||
|
case REPLACE_TYPE_ARG1:
|
||||||
|
repl = ((JsStringReplaceArg1)replObj).processMatcher(m.group(0));
|
||||||
|
break;
|
||||||
|
case REPLACE_TYPE_ARG2:
|
||||||
|
repl = ((JsStringReplaceArg2)replObj).processMatcher(m.group(0), m.group(1));
|
||||||
|
break;
|
||||||
|
case REPLACE_TYPE_ARG3:
|
||||||
|
repl = ((JsStringReplaceArg3)replObj).processMatcher(m.group(0), m.group(1), m.group(2));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw Err_.new_unhandled_default(replType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sb == null) {
|
||||||
|
sb = new StringBuffer();
|
||||||
|
}
|
||||||
|
m.appendReplacement(sb, repl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return
|
||||||
|
if (sb != null) {
|
||||||
|
m.appendTail(sb);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return src;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package gplx.langs.javascripts.util.regex;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class JsPattern_ {
|
||||||
|
public static final int NONE = 0;
|
||||||
|
public static Pattern getOrCompile(ConcurrentHashMap<String, Pattern> map, String pat) {return getOrCompile(map, pat, NONE);}
|
||||||
|
public static Pattern getOrCompile(ConcurrentHashMap<String, Pattern> map, String pat, int patFlags) {
|
||||||
|
// get pattern
|
||||||
|
Pattern pattern = map.get(pat);
|
||||||
|
if (pattern == null) {
|
||||||
|
pattern = Pattern.compile(pat, patFlags);
|
||||||
|
map.put(pat, pattern);
|
||||||
|
}
|
||||||
|
return pattern;
|
||||||
|
}
|
||||||
|
}
|
364
400_xowa/src/gplx/xowa/htmls/minifys/XoCssMin.java
Normal file
364
400_xowa/src/gplx/xowa/htmls/minifys/XoCssMin.java
Normal file
@ -0,0 +1,364 @@
|
|||||||
|
package gplx.xowa.htmls.minifys;
|
||||||
|
|
||||||
|
import gplx.core.bits.Bitmask_;
|
||||||
|
import gplx.core.tooling.dataCollectors.GfoDataCollectorMgr;
|
||||||
|
import gplx.langs.javascripts.JsString_;
|
||||||
|
import gplx.langs.javascripts.util.regex.JsPattern_;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
// XoCssMin based on:
|
||||||
|
// * Node.JS: https://github.com/jbleuzen/node-cssmin/blob/master/cssmin.js
|
||||||
|
// * YCSSMIN: https://github.com/yui/ycssmin/blob/master/cssmin.js
|
||||||
|
// * desb42 : https://github.com/desb42/myxowa/blob/master/400_xowa/src/gplx/xowa/htmls/Xoh_css_minify_v3.java
|
||||||
|
public class XoCssMin {
|
||||||
|
private final static ConcurrentHashMap<String, Pattern> patterns = new ConcurrentHashMap<>();
|
||||||
|
public static final int
|
||||||
|
MODE_NODEJS = 1
|
||||||
|
, MODE_YCSS_MIN = 2
|
||||||
|
, MODE_XOWA = 4
|
||||||
|
, MODE_ALL = MODE_NODEJS | MODE_YCSS_MIN | MODE_XOWA
|
||||||
|
;
|
||||||
|
public void DataCollectorMgr_(GfoDataCollectorMgr v) {this.dataCollectorMgr = v;} private GfoDataCollectorMgr dataCollectorMgr;
|
||||||
|
public String cssmin(String css, int linebreakpos) {return cssmin(css, linebreakpos, MODE_ALL);}
|
||||||
|
public String cssmin(String css, int linebreakpos, int mode) {
|
||||||
|
boolean isModeYcssMin = Bitmask_.Has_int(mode, MODE_YCSS_MIN);
|
||||||
|
boolean isModeXowa = Bitmask_.Has_int(mode, MODE_XOWA);
|
||||||
|
|
||||||
|
int startIndex = 0,
|
||||||
|
endIndex = 0,
|
||||||
|
i = 0, max = 0;
|
||||||
|
List<String> preservedTokens = new ArrayList<>();
|
||||||
|
List<String> comments = new ArrayList<>();
|
||||||
|
String token = "";
|
||||||
|
int totallen = css.length();
|
||||||
|
String placeholder = "";
|
||||||
|
|
||||||
|
if (isModeYcssMin) {
|
||||||
|
css = _extractDataUrls(css, preservedTokens);
|
||||||
|
if (dataCollectorMgr != null) {
|
||||||
|
dataCollectorMgr.AddGrp("extractDataUrls").Add("css", css).Add("preservedTokens", preservedTokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect all comment blocks...
|
||||||
|
while ((startIndex = css.indexOf("/*", startIndex)) >= 0) {
|
||||||
|
endIndex = css.indexOf("*/", startIndex + 2);
|
||||||
|
if (endIndex < 0) {
|
||||||
|
endIndex = totallen;
|
||||||
|
}
|
||||||
|
token = JsString_.slice(css, startIndex + 2, endIndex);
|
||||||
|
comments.add(token);
|
||||||
|
css = JsString_.slice(css, 0, startIndex + 2) + "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + (comments.size() - 1) + "___" + JsString_.slice(css, endIndex);
|
||||||
|
startIndex += 2;
|
||||||
|
}
|
||||||
|
if (dataCollectorMgr != null) {
|
||||||
|
dataCollectorMgr.AddGrp("collectComments").Add("css", css).Add("comments", comments);
|
||||||
|
}
|
||||||
|
|
||||||
|
// preserve strings so their content doesn't get accidentally minified
|
||||||
|
css = JsString_.replaceArg1(css, patterns
|
||||||
|
, "(\"([^\\\\\"]|\\\\.|\\\\)*\")|('([^\\\\']|\\\\.|\\\\)*')"
|
||||||
|
, (match) -> {
|
||||||
|
int idx = 0, max2 = 0; String quote = match.substring(0, 1);
|
||||||
|
|
||||||
|
match = JsString_.slice(match, 1, -1);
|
||||||
|
|
||||||
|
// maybe the string contains a comment-like substring?
|
||||||
|
// one, maybe more? put'em back then
|
||||||
|
if (match.indexOf("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_") >= 0) {
|
||||||
|
for (idx = 0, max2 = comments.size(); idx < max2; idx = idx + 1) {
|
||||||
|
match = match.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + idx + "___", comments.get(idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// minify alpha opacity in filter strings
|
||||||
|
match = JsString_.replace(match, patterns,
|
||||||
|
"progid:DXImageTransform\\.Microsoft\\.Alpha\\(Opacity=", Pattern.CASE_INSENSITIVE,
|
||||||
|
"alpha(opacity=");
|
||||||
|
|
||||||
|
preservedTokens.add(match);
|
||||||
|
|
||||||
|
return quote + "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___" + quote;
|
||||||
|
});
|
||||||
|
|
||||||
|
// strings are safe, now wrestle the comments
|
||||||
|
for (i = 0, max = comments.size(); i < max; i = i + 1) {
|
||||||
|
|
||||||
|
token = comments.get(i);
|
||||||
|
placeholder = "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___";
|
||||||
|
|
||||||
|
// ! in the first position of the comment means preserve
|
||||||
|
// so push to the preserved tokens keeping the !
|
||||||
|
if (token.charAt(0) == '!') {
|
||||||
|
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.charAt(token.length() - 1) == '\\') {
|
||||||
|
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 + "*/", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Normalize all whitespace strings to single spaces. Easier to work with that way.
|
||||||
|
css = JsString_.replace(css, patterns, "\\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.
|
||||||
|
css = JsString_.replaceArg1
|
||||||
|
(css, patterns,
|
||||||
|
"(^|\\})(([^\\{:])+:)+([^\\{]*\\{)"
|
||||||
|
, s -> s.replace(":", "___YUICSSMIN_PSEUDOCLASSCOLON___"));
|
||||||
|
if (dataCollectorMgr != null) {
|
||||||
|
dataCollectorMgr.AddGrp("swapPseudo").Add("css", css);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preserve spaces in calc expressions;
|
||||||
|
// XO:MODE_YCSS_MIN omits this
|
||||||
|
css = JsString_.replaceArg2(css, patterns, "calc\\s*\\(\\s*(.*?)\\s*\\)", (m, c) -> {
|
||||||
|
return m.replace(c, JsString_.replace(c, patterns, "\\s+", "___YUICSSMIN_SPACE_IN_CALC___"));
|
||||||
|
});
|
||||||
|
|
||||||
|
css = JsString_.replace(css, patterns, "\\s+([!{}:;>+\\(\\)\\[,])", "$1");
|
||||||
|
css = css.replace("___YUICSSMIN_PSEUDOCLASSCOLON___", ":");
|
||||||
|
|
||||||
|
// retain space for special IE6 cases
|
||||||
|
css = JsString_.replace(css, patterns, ":first-(line|letter)(\\{|,)", ":first-$1 $2");
|
||||||
|
|
||||||
|
// no space after the end of a preserved comment
|
||||||
|
css = css.replace("*/ ", "*/");
|
||||||
|
|
||||||
|
|
||||||
|
// If there is a @charset, then only allow one, and push to the top of the file.
|
||||||
|
css = JsString_.replace(css, patterns, "^(.*)(@charset \"[^\"]*\";)", Pattern.CASE_INSENSITIVE, "$2$1");
|
||||||
|
css = JsString_.replace(css, patterns, "/^(\\s*@charset [^;]+;\\s*)+/", Pattern.CASE_INSENSITIVE, "$1");
|
||||||
|
|
||||||
|
// Put the space back in some cases, to support stuff like
|
||||||
|
// @media screen and (-webkit-min-device-pixel-ratio:0){
|
||||||
|
css = JsString_.replace(css, patterns, "\\band\\(", Pattern.CASE_INSENSITIVE, "and (");
|
||||||
|
|
||||||
|
|
||||||
|
// Remove the spaces after the things that should not have spaces after them.
|
||||||
|
css = JsString_.replace(css, patterns, "([!{}:;>+\\(\\[,])\\s+", "$1");
|
||||||
|
|
||||||
|
// Restore preserved spaces in calc expressions
|
||||||
|
// XO:MODE_YCSS_MIN omits this
|
||||||
|
css = css.replace("___YUICSSMIN_SPACE_IN_CALC___", " ");
|
||||||
|
|
||||||
|
// remove unnecessary semicolons
|
||||||
|
css = JsString_.replace(css, patterns,";+\\}", "}");
|
||||||
|
|
||||||
|
// Replace 0(px,em,%) with 0.
|
||||||
|
css = JsString_.replace(css, patterns, "([\\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", Pattern.CASE_INSENSITIVE,"$1$2");
|
||||||
|
|
||||||
|
// Replace 0 0 0 0; with 0.
|
||||||
|
css = JsString_.replace(css, patterns, ":0 0 0 0(;|\\})",":0$1");
|
||||||
|
css = JsString_.replace(css, patterns, ":0 0 0(;|\\})",":0$1");
|
||||||
|
css = JsString_.replace(css, patterns, ":0 0(;|\\})",":0$1");
|
||||||
|
|
||||||
|
// Replace background-position:0; with background-position:0 0;
|
||||||
|
// same for transform-origin
|
||||||
|
css = JsString_.replaceArg3(css, patterns,
|
||||||
|
"(background-position|transform-origin|webkit-transform-origin|moz-transform-origin|o-transform-origin|ms-transform-origin):0(;|\\})", Pattern.CASE_INSENSITIVE
|
||||||
|
, (all, prop, tail)-> prop.toLowerCase() + ":0 0" + tail);
|
||||||
|
|
||||||
|
// Replace 0.6 to .6, but only when preceded by : or a white-space
|
||||||
|
css = JsString_.replace(css, patterns, "(:|\\s)0+\\.(\\d+)", "$1.$2");
|
||||||
|
|
||||||
|
// Shorten colors from rgb(51,102,153) to #336699
|
||||||
|
// This makes it more likely that it'll get further compressed in the next step.
|
||||||
|
css = JsString_.replaceMatcher(css, patterns,
|
||||||
|
"rgb\\s*\\(\\s*([0-9,\\s]+)\\s*\\)", Pattern.CASE_INSENSITIVE
|
||||||
|
, m -> {
|
||||||
|
String[] rgbcolors = m.group(1).split(",");
|
||||||
|
String rgb = "";
|
||||||
|
for (int idx = 0; idx < rgbcolors.length; idx = idx + 1) {
|
||||||
|
rgbcolors[idx] = Integer.toHexString(Integer.parseInt(rgbcolors[idx], 10));
|
||||||
|
if (rgbcolors[idx].length() == 1) {
|
||||||
|
rgbcolors[idx] = "0" + rgbcolors[idx];
|
||||||
|
}
|
||||||
|
rgb += rgbcolors[idx];
|
||||||
|
}
|
||||||
|
return '#' + rgb;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// XO:MODE_YCSS_MIN replaces this with its own function
|
||||||
|
// XO:desb42 comments this
|
||||||
|
css = JsString_.replaceMatcher(css, patterns
|
||||||
|
, "([^\"'=\\s])(\\s*)#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])", Pattern.CASE_INSENSITIVE
|
||||||
|
, match-> {
|
||||||
|
if (
|
||||||
|
match.group(3).toLowerCase().equals(match.group(4).toLowerCase()) &&
|
||||||
|
match.group(5).toLowerCase().equals(match.group(6).toLowerCase()) &&
|
||||||
|
match.group(7).toLowerCase().equals(match.group(8).toLowerCase())
|
||||||
|
) {
|
||||||
|
return (match.group(1) + match.group(2) + '#' + match.group(3) + match.group(5) + match.group(7)).toLowerCase();
|
||||||
|
} else {
|
||||||
|
return match.group(0).toLowerCase();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// border: none -> border:0
|
||||||
|
css = JsString_.replaceArg3(css, patterns
|
||||||
|
, "(border|border-top|border-right|border-bottom|border-right|outline|background):none(;|\\})", Pattern.CASE_INSENSITIVE
|
||||||
|
, (all, prop, tail) -> prop.toLowerCase() + ":0" + tail
|
||||||
|
);
|
||||||
|
|
||||||
|
// shorter opacity IE filter
|
||||||
|
css = JsString_.replace(css, patterns, "progid:DXImageTransform\\.Microsoft\\.Alpha\\(Opacity=", Pattern.CASE_INSENSITIVE, "alpha(opacity=");
|
||||||
|
|
||||||
|
// Remove empty rules.
|
||||||
|
css = JsString_.replace(css, patterns, "[^\\};\\{\\/]+\\{\\}", "");
|
||||||
|
|
||||||
|
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.
|
||||||
|
startIndex = 0;
|
||||||
|
i = 0;
|
||||||
|
// XO: desb42 comments this
|
||||||
|
while (i < css.length()) {
|
||||||
|
i = i + 1;
|
||||||
|
if (css.charAt(i - 1) == '}' && i - startIndex > linebreakpos) {
|
||||||
|
css = JsString_.slice(css, 0, i) + '\n' + JsString_.slice(css, i);
|
||||||
|
startIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace multiple semi-colons in a row by a single one
|
||||||
|
// See SF bug #1980989
|
||||||
|
css = JsString_.replace(css, patterns, ";;+", ";");
|
||||||
|
|
||||||
|
// restore preserved comments and strings
|
||||||
|
for (i = 0, max = preservedTokens.size(); i < max; i = i + 1) {
|
||||||
|
css = css.replace("___YUICSSMIN_PRESERVED_TOKEN_" + i + "___", preservedTokens.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim the final string (for any leading or trailing white spaces)
|
||||||
|
css = JsString_.replace(css, patterns, "^\\s+|\\s+$", "");
|
||||||
|
|
||||||
|
if (isModeXowa) {
|
||||||
|
// add the '.mw-parser-output ' selector
|
||||||
|
// XO: commented out; handled in TemplateStyles to improve performance
|
||||||
|
// css = JsString_.replace(css, patterns, "\\}([^@}].{2})", "}.mw-parser-output $1");
|
||||||
|
// css = JsString_.replace(css, patterns, "(@media[^\\{]*\\{)", "$1.mw-parser-output ");
|
||||||
|
// if (css.charAt(0) != '@')
|
||||||
|
// css = ".mw-parser-output " + css;
|
||||||
|
|
||||||
|
// change some url(...) entries
|
||||||
|
css = css.replace("//upload.wikimedia.org", "//www.xowa.org/xowa/fsys/bin/any/xowa/upload.wikimedia.org");
|
||||||
|
}
|
||||||
|
|
||||||
|
return css;
|
||||||
|
}
|
||||||
|
private String _extractDataUrls(String css, List<String> preservedTokens) {
|
||||||
|
|
||||||
|
// Leave data urls alone to increase parse performance.
|
||||||
|
// XO: also ensures it is not touched by the rest of the cssmin
|
||||||
|
int maxIndex = css.length() - 1,
|
||||||
|
appendIndex = 0,
|
||||||
|
startIndex,
|
||||||
|
endIndex;
|
||||||
|
String terminator;
|
||||||
|
boolean foundTerminator;
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
Matcher m;
|
||||||
|
String preserver,
|
||||||
|
token;
|
||||||
|
Pattern pattern = JsPattern_.getOrCompile(patterns,"url\\(\\s*([\"']?)data\\:");
|
||||||
|
|
||||||
|
// Since we need to account for non-base64 data urls, we need to handle
|
||||||
|
// ' and ) being part of the data string. Hence switching to indexOf,
|
||||||
|
// to determine whether or not we have matching string terminators and
|
||||||
|
// handling sb appends directly, instead of using matcher.append* methods.
|
||||||
|
m = pattern.matcher(css);
|
||||||
|
while (m.find()) {
|
||||||
|
|
||||||
|
startIndex = m.start() + 4; // "url(".length()
|
||||||
|
terminator = m.group(1); // ', " or empty (not quoted); XO:can be any of `'`, `"`, ``
|
||||||
|
|
||||||
|
if (terminator.length() == 0) { // XO: this means that the regex matched nothing; (["']?) -> ``
|
||||||
|
terminator = ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
foundTerminator = false;
|
||||||
|
|
||||||
|
endIndex = m.end() - 1;
|
||||||
|
|
||||||
|
while(foundTerminator == false && endIndex+1 <= maxIndex) {// XO: search forward for next terminator
|
||||||
|
endIndex = css.indexOf(terminator, endIndex + 1);
|
||||||
|
|
||||||
|
// endIndex == 0 doesn't really apply here
|
||||||
|
if ((endIndex > 0) && (css.charAt(endIndex - 1) != '\\')) {// XO:found terminator; check it isn't escaped; EX: `\'`
|
||||||
|
foundTerminator = true;
|
||||||
|
if (!(")".equals(terminator))) {// XO:cur terminator is either `'` or `"`; grab next `)`
|
||||||
|
endIndex = css.indexOf(")", endIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// XO:NOTE: foundTerminator will always be true.
|
||||||
|
// Specifically, if endIndex is -1:
|
||||||
|
// * loop will start from top and do `endIndex = css.indexOf(terminator, -1 + 1)` which evaluates to the original endIndex
|
||||||
|
// * since endIndex will always be > 0 and endIndex - 1 will never be \ (b/c of regex), foundTerminator will always be true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enough searching, start moving stuff over to the buffer
|
||||||
|
sb.append(css.substring(appendIndex, m.start()));
|
||||||
|
|
||||||
|
if (foundTerminator) {
|
||||||
|
token = css.substring(startIndex, endIndex);
|
||||||
|
token = JsString_.replace(token, patterns, "\\s+", "");
|
||||||
|
preservedTokens.add(token);
|
||||||
|
|
||||||
|
preserver = "url(___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?
|
||||||
|
// XO: as detailed above, foundTerminator will always be true
|
||||||
|
sb.append(css.substring(m.start(), m.end()));
|
||||||
|
appendIndex = m.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(css.substring(appendIndex));
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -1,32 +1,35 @@
|
|||||||
/*
|
package gplx.xowa.xtns.template_styles;
|
||||||
XOWA: the XOWA Offline Wiki Application
|
|
||||||
Copyright (C) 2012-2017 gnosygnu@gmail.com
|
|
||||||
|
|
||||||
XOWA is licensed under the terms of the General Public License (GPL) Version 3,
|
import gplx.*;
|
||||||
or alternatively under the terms of the Apache License Version 2.0.
|
import gplx.core.lists.hashs.Hash_adp__int;
|
||||||
|
import gplx.xowa.Xoa_ttl;
|
||||||
You may use XOWA according to either of these licenses as is most appropriate
|
import gplx.xowa.Xoae_app;
|
||||||
for your project on a case-by-case basis.
|
import gplx.xowa.Xoae_page;
|
||||||
|
import gplx.xowa.Xowe_wiki;
|
||||||
The terms of each license can be found in the source code repository:
|
import gplx.xowa.htmls.core.htmls.Xoh_html_wtr;
|
||||||
|
import gplx.xowa.htmls.core.htmls.Xoh_wtr_ctx;
|
||||||
GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
|
import gplx.xowa.htmls.heads.Xoh_head_itm__css_dynamic;
|
||||||
Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
|
import gplx.xowa.htmls.hxtns.blobs.Hxtn_blob_tbl;
|
||||||
*/
|
import gplx.xowa.htmls.hxtns.pages.Hxtn_page_mgr;
|
||||||
package gplx.xowa.xtns.template_styles; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*;
|
import gplx.xowa.htmls.hxtns.wikis.Hxtn_wiki_itm;
|
||||||
import gplx.core.primitives.*;
|
import gplx.xowa.htmls.minifys.XoCssMin;
|
||||||
import gplx.core.lists.hashs.*;
|
import gplx.xowa.parsers.Xop_ctx;
|
||||||
import gplx.xowa.wikis.caches.*;
|
import gplx.xowa.parsers.Xop_root_tkn;
|
||||||
import gplx.xowa.htmls.core.htmls.*; import gplx.xowa.parsers.htmls.*; import gplx.xowa.htmls.heads.*;
|
import gplx.xowa.parsers.htmls.Mwh_atr_itm;
|
||||||
import gplx.xowa.parsers.*; import gplx.xowa.parsers.xndes.*;
|
import gplx.xowa.parsers.htmls.Mwh_atr_itm_owner2;
|
||||||
import gplx.xowa.htmls.hxtns.*; import gplx.xowa.htmls.hxtns.pages.*; import gplx.xowa.htmls.hxtns.blobs.*; import gplx.xowa.htmls.hxtns.wikis.*;
|
import gplx.xowa.parsers.xndes.Xop_xnde_tag;
|
||||||
import gplx.xowa.wikis.nss.*;
|
import gplx.xowa.parsers.xndes.Xop_xnde_tkn;
|
||||||
|
import gplx.xowa.wikis.caches.Xow_page_cache_itm;
|
||||||
|
import gplx.xowa.wikis.nss.Xow_ns_;
|
||||||
|
import gplx.xowa.xtns.Xox_xnde;
|
||||||
|
import gplx.xowa.xtns.Xox_xnde_;
|
||||||
public class Template_styles_nde implements Xox_xnde, Mwh_atr_itm_owner2 {
|
public class Template_styles_nde implements Xox_xnde, Mwh_atr_itm_owner2 {
|
||||||
private byte[] css_ttl_bry;
|
private byte[] css_ttl_bry;
|
||||||
private byte[] css_src;
|
private byte[] css_src;
|
||||||
private boolean css_ignore;
|
private boolean css_ignore;
|
||||||
private int css_page_id;
|
private int css_page_id;
|
||||||
private Xoa_ttl css_ttl;
|
private Xoa_ttl css_ttl;
|
||||||
|
private static XoCssMin cssMin = new XoCssMin();
|
||||||
public void Xatr__set(Xowe_wiki wiki, byte[] src, Mwh_atr_itm xatr, byte xatr_id) {
|
public void Xatr__set(Xowe_wiki wiki, byte[] src, Mwh_atr_itm xatr, byte xatr_id) {
|
||||||
switch (xatr_id) {
|
switch (xatr_id) {
|
||||||
case Xatr__src: css_ttl_bry = xatr.Val_as_bry(); break;
|
case Xatr__src: css_ttl_bry = xatr.Val_as_bry(); break;
|
||||||
@ -82,7 +85,7 @@ public class Template_styles_nde implements Xox_xnde, Mwh_atr_itm_owner2 {
|
|||||||
if (!css_ignore) {
|
if (!css_ignore) {
|
||||||
Bry_bfr tmp_bfr = ctx.Wiki().Utl__bfr_mkr().Get_b512();
|
Bry_bfr tmp_bfr = ctx.Wiki().Utl__bfr_mkr().Get_b512();
|
||||||
try {
|
try {
|
||||||
html_head.Bld_many(tmp_bfr, css_page_id, css_src);
|
html_head.Bld_many(tmp_bfr, css_page_id, Bry_.new_u8(cssMin.cssmin(String_.new_u8(css_src), -1)) );
|
||||||
Xoh_head_itm__css_dynamic css_dynamic = ctx.Page().Html_data().Head_mgr().Itm__css_dynamic();
|
Xoh_head_itm__css_dynamic css_dynamic = ctx.Page().Html_data().Head_mgr().Itm__css_dynamic();
|
||||||
css_dynamic.Enabled_y_();
|
css_dynamic.Enabled_y_();
|
||||||
css_dynamic.Add(tmp_bfr.To_bry_and_clear());
|
css_dynamic.Add(tmp_bfr.To_bry_and_clear());
|
||||||
|
@ -1,20 +1,10 @@
|
|||||||
/*
|
package gplx.xowa.xtns.template_styles;
|
||||||
XOWA: the XOWA Offline Wiki Application
|
|
||||||
Copyright (C) 2012-2017 gnosygnu@gmail.com
|
|
||||||
|
|
||||||
XOWA is licensed under the terms of the General Public License (GPL) Version 3,
|
import gplx.String_;
|
||||||
or alternatively under the terms of the Apache License Version 2.0.
|
import gplx.core.tests.Gftest;
|
||||||
|
import gplx.xowa.Xop_fxt;
|
||||||
You may use XOWA according to either of these licenses as is most appropriate
|
import org.junit.Before;
|
||||||
for your project on a case-by-case basis.
|
import org.junit.Test;
|
||||||
|
|
||||||
The terms of each license can be found in the source code repository:
|
|
||||||
|
|
||||||
GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
|
|
||||||
Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
|
|
||||||
*/
|
|
||||||
package gplx.xowa.xtns.template_styles; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*;
|
|
||||||
import org.junit.*; import gplx.core.tests.*;
|
|
||||||
public class Template_styles_nde_tst {
|
public class Template_styles_nde_tst {
|
||||||
private final Template_styles_nde_fxt fxt = new Template_styles_nde_fxt();
|
private final Template_styles_nde_fxt fxt = new Template_styles_nde_fxt();
|
||||||
@Before public void init() {
|
@Before public void init() {
|
||||||
@ -128,7 +118,7 @@ class Template_styles_nde_fxt {
|
|||||||
parser_fxt.Init_page_create(page, text);
|
parser_fxt.Init_page_create(page, text);
|
||||||
}
|
}
|
||||||
public String Make__css_color(String color) {
|
public String Make__css_color(String color) {
|
||||||
return ".style0{color:" + color + ";}";
|
return ".style0{color:" + color + "}";
|
||||||
}
|
}
|
||||||
public String Make__style(int id, String css) {
|
public String Make__style(int id, String css) {
|
||||||
return "\n/*TemplateStyles:r" + id + "*/\n.mw-parser-output " + css; // .mw-parser-output needs to be added to all TemplateStyles CSS, else TS ids called "portal" will affect sidebar; ISSUE#:426; PAGE:en.w:Poland DATE:2020-04-10
|
return "\n/*TemplateStyles:r" + id + "*/\n.mw-parser-output " + css; // .mw-parser-output needs to be added to all TemplateStyles CSS, else TS ids called "portal" will affect sidebar; ISSUE#:426; PAGE:en.w:Poland DATE:2020-04-10
|
||||||
|
27
400_xowa/tst/gplx/langs/javascripts/JsString_Test.java
Normal file
27
400_xowa/tst/gplx/langs/javascripts/JsString_Test.java
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package gplx.langs.javascripts;
|
||||||
|
|
||||||
|
import gplx.core.tests.Gftest;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class JsString_Test {
|
||||||
|
private final JsString_Tstr tstr = new JsString_Tstr();
|
||||||
|
@Test public void slice() {
|
||||||
|
tstr.Test_slice("bgn.positive.basic", "bc" , "abc", 1);
|
||||||
|
tstr.Test_slice("bgn.positive.large", "" , "abc", 4);
|
||||||
|
tstr.Test_slice("bgn.negative.basic", "c" , "abc", -1);
|
||||||
|
tstr.Test_slice("bgn.negative.large", "abc", "abc", -4);
|
||||||
|
tstr.Test_slice("end.positive.basic", "b" , "abc", 1, 2);
|
||||||
|
tstr.Test_slice("end.positive.eos" , "bc" , "abc", 1, 3);
|
||||||
|
tstr.Test_slice("end.positive.large", "bc" , "abc", 1, 4);
|
||||||
|
tstr.Test_slice("end.negative.basic", "b" , "abc", 1, -1);
|
||||||
|
tstr.Test_slice("end.negative.large", "" , "abc", 1, -4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class JsString_Tstr {
|
||||||
|
public void Test_slice(String note, String expd, String src, int bgn) {
|
||||||
|
Gftest.Eq__str(expd, JsString_.slice(src, bgn), note);
|
||||||
|
}
|
||||||
|
public void Test_slice(String note, String expd, String src, int bgn, int end) {
|
||||||
|
Gftest.Eq__str(expd, JsString_.slice(src, bgn, end), note);
|
||||||
|
}
|
||||||
|
}
|
103
400_xowa/tst/gplx/xowa/htmls/minifys/XoCssMinTest.java
Normal file
103
400_xowa/tst/gplx/xowa/htmls/minifys/XoCssMinTest.java
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package gplx.xowa.htmls.minifys;
|
||||||
|
|
||||||
|
import gplx.core.tests.Gftest;
|
||||||
|
import gplx.core.tooling.dataCollectors.GfoDataCollectorMgr;
|
||||||
|
import gplx.core.tooling.asserts.TestAssert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class XoCssMinTest {
|
||||||
|
private final XoCssMinTstr tstr = new XoCssMinTstr();
|
||||||
|
// NOTE: can pull more tests from https://github.com/yui/yuicompressor/tree/master/tests
|
||||||
|
@Test public void extractDataUrls() {
|
||||||
|
tstr.Test("extractDataUrls.basic" // all 3 types: `'`, `"`, ``
|
||||||
|
, "url('data:a1') url(\"data:a2\") url(data:a3)"
|
||||||
|
, "url('data:a1') url(\"data:a2\") url(data:a3)"
|
||||||
|
, new TestAssert.Grp("extractDataUrls"
|
||||||
|
, new TestAssert.ListEq("preservedTokens", "'data:a1'", "\"data:a2\"", "data:a3")
|
||||||
|
, new TestAssert.StringEq("css", "url(___YUICSSMIN_PRESERVED_TOKEN_0___) url(___YUICSSMIN_PRESERVED_TOKEN_1___) url(___YUICSSMIN_PRESERVED_TOKEN_2___)")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
tstr.Test("extractDataUrls.whitespace"
|
||||||
|
, "url( 'data:a1 ' ) "
|
||||||
|
, "url('data:a1')"
|
||||||
|
, new TestAssert.Grp("extractDataUrls"
|
||||||
|
, new TestAssert.ListEq("preservedTokens", "'data:a1'")
|
||||||
|
, new TestAssert.StringEq("css", "url(___YUICSSMIN_PRESERVED_TOKEN_0___) ")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
tstr.Test("extractDataUrls.escapedTerminator"
|
||||||
|
, "url('data:a\\')bc')"
|
||||||
|
, "url('data:a\\')bc')"
|
||||||
|
, new TestAssert.Grp("extractDataUrls"
|
||||||
|
, new TestAssert.ListEq("preservedTokens", "'data:a\\')bc'")
|
||||||
|
, new TestAssert.StringEq("css", "url(___YUICSSMIN_PRESERVED_TOKEN_0___)")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@Test public void comments() {
|
||||||
|
tstr.Test("collectComments.basic"
|
||||||
|
, "a /* b */ c /* d */ e"
|
||||||
|
, "a c e"
|
||||||
|
, new TestAssert.Grp("collectComments"
|
||||||
|
, new TestAssert.ListEq("comments", " b ", " d ")
|
||||||
|
, new TestAssert.StringEq("css", "a /*___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_0___*/ c /*___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_1___*/ e")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@Test public void rgb() {
|
||||||
|
tstr.Test("rgb.basic" ,"rgb (128,128,128)", "#808080");
|
||||||
|
tstr.Test("rgb.casing" ,"RgB (128,128,128)", "#808080");
|
||||||
|
}
|
||||||
|
@Test public void borders() {
|
||||||
|
tstr.Test("borders.basic" , "border:none;" , "border:0;");
|
||||||
|
tstr.Test("borders.background" , "background:none;", "background:0;");
|
||||||
|
tstr.Test("borders.casing" , "bOrDeR:NoNe;" , "border:0;");
|
||||||
|
tstr.Test("borders.close-brace", "{border:none}" , "{border:0}");
|
||||||
|
}
|
||||||
|
@Test public void background() {
|
||||||
|
tstr.Test("background.basic" ,"background-position:0;" , "background-position:0 0;");
|
||||||
|
tstr.Test("background.ms-transform","ms-transform-origin:0;" , "ms-transform-origin:0 0;");
|
||||||
|
tstr.Test("background.casing" ,"Background-Position:0;" , "background-position:0 0;");
|
||||||
|
tstr.Test("background.close-brace" ,"{background-position:0}", "{background-position:0 0}");
|
||||||
|
}
|
||||||
|
@Test public void pseudo() {
|
||||||
|
tstr.Test("swapPseudo.caret"
|
||||||
|
, "abc ^ class:val { xyz"
|
||||||
|
, "abc ^ class:val{xyz"
|
||||||
|
, new TestAssert.Grp("swapPseudo"
|
||||||
|
, new TestAssert.StringEq("css", "abc ^ class___YUICSSMIN_PSEUDOCLASSCOLON___val { xyz")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
tstr.Test("swapPseudo.brace"
|
||||||
|
, "abc } class:val { xyz"
|
||||||
|
, "abc}class:val{xyz"
|
||||||
|
, new TestAssert.Grp("swapPseudo"
|
||||||
|
, new TestAssert.StringEq("css", "abc } class___YUICSSMIN_PSEUDOCLASSCOLON___val { xyz")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@Test public void compressHex() {
|
||||||
|
tstr.Test("compressHex.basic" ,"( #aa1199)" , "(#a19)");
|
||||||
|
tstr.Test("compressHex.case" ,"( #ABCDEF)" , "(#abcdef)");
|
||||||
|
tstr.Test("compressHex.skip" ,"color=#ffffff"); // surprisingly, this doesn't compress due to ([^"'=\s])
|
||||||
|
tstr.Test("compressHex.chromaExample","filter:chroma(color=\"#FFFFFF\");");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class XoCssMinTstr {
|
||||||
|
private final XoCssMin min = new XoCssMin();
|
||||||
|
private GfoDataCollectorMgr dataCollectorMgr = new GfoDataCollectorMgr();
|
||||||
|
public void Mode_(int v) {mode = v;} private int mode = XoCssMin.MODE_NODEJS | XoCssMin.MODE_YCSS_MIN;
|
||||||
|
public void Test(String note, String orig, TestAssert.Grp... expdRules) {Test(note, orig, orig, expdRules);}
|
||||||
|
public void Test(String note, String orig, String expd, TestAssert.Grp... expdRules) {
|
||||||
|
if (expdRules != null)
|
||||||
|
min.DataCollectorMgr_(dataCollectorMgr);
|
||||||
|
String actl = min.cssmin(orig, -1, mode);
|
||||||
|
if (expdRules != null) {
|
||||||
|
TestAssert.Test(note, dataCollectorMgr, expdRules);
|
||||||
|
}
|
||||||
|
Gftest.Eq__str(expd, actl, note);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user