1
0
mirror of https://github.com/gnosygnu/xowa.git synced 2024-09-29 23:10:52 +00:00

TemplateStyles: Add XoCssMin from @desb42 [#704]

This commit is contained in:
gnosygnu 2020-04-19 08:54:54 -04:00
parent e9e5724a2a
commit 73a56ffab3
11 changed files with 807 additions and 42 deletions

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

View File

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

View File

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

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

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

View File

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

View 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();
}
}

View File

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

View File

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

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

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