TemplateStyles: Add XoCssMin from @desb42 [#704]

staging
gnosygnu 4 years ago
parent e9e5724a2a
commit 73a56ffab3

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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 @@
/*
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,
or alternatively under the terms of the Apache License Version 2.0.
You may use XOWA according to either of these licenses as is most appropriate
for your project on a case-by-case basis.
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 gplx.core.primitives.*;
import gplx.core.lists.hashs.*;
import gplx.xowa.wikis.caches.*;
import gplx.xowa.htmls.core.htmls.*; import gplx.xowa.parsers.htmls.*; import gplx.xowa.htmls.heads.*;
import gplx.xowa.parsers.*; import gplx.xowa.parsers.xndes.*;
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.wikis.nss.*;
package gplx.xowa.xtns.template_styles;
import gplx.*;
import gplx.core.lists.hashs.Hash_adp__int;
import gplx.xowa.Xoa_ttl;
import gplx.xowa.Xoae_app;
import gplx.xowa.Xoae_page;
import gplx.xowa.Xowe_wiki;
import gplx.xowa.htmls.core.htmls.Xoh_html_wtr;
import gplx.xowa.htmls.core.htmls.Xoh_wtr_ctx;
import gplx.xowa.htmls.heads.Xoh_head_itm__css_dynamic;
import gplx.xowa.htmls.hxtns.blobs.Hxtn_blob_tbl;
import gplx.xowa.htmls.hxtns.pages.Hxtn_page_mgr;
import gplx.xowa.htmls.hxtns.wikis.Hxtn_wiki_itm;
import gplx.xowa.htmls.minifys.XoCssMin;
import gplx.xowa.parsers.Xop_ctx;
import gplx.xowa.parsers.Xop_root_tkn;
import gplx.xowa.parsers.htmls.Mwh_atr_itm;
import gplx.xowa.parsers.htmls.Mwh_atr_itm_owner2;
import gplx.xowa.parsers.xndes.Xop_xnde_tag;
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 {
private byte[] css_ttl_bry;
private byte[] css_src;
private boolean css_ignore;
private int css_page_id;
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) {
switch (xatr_id) {
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) {
Bry_bfr tmp_bfr = ctx.Wiki().Utl__bfr_mkr().Get_b512();
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();
css_dynamic.Enabled_y_();
css_dynamic.Add(tmp_bfr.To_bry_and_clear());

@ -1,20 +1,10 @@
/*
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,
or alternatively under the terms of the Apache License Version 2.0.
You may use XOWA according to either of these licenses as is most appropriate
for your project on a case-by-case basis.
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.*;
package gplx.xowa.xtns.template_styles;
import gplx.String_;
import gplx.core.tests.Gftest;
import gplx.xowa.Xop_fxt;
import org.junit.Before;
import org.junit.Test;
public class Template_styles_nde_tst {
private final Template_styles_nde_fxt fxt = new Template_styles_nde_fxt();
@Before public void init() {
@ -128,7 +118,7 @@ class Template_styles_nde_fxt {
parser_fxt.Init_page_create(page, text);
}
public String Make__css_color(String color) {
return ".style0{color:" + color + ";}";
return ".style0{color:" + color + "}";
}
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

@ -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);
}
}

@ -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…
Cancel
Save