|
|
@@ -17,19 +17,26 @@
|
|
|
package com.jfinal.template.stat;
|
|
|
|
|
|
/**
|
|
|
- * 对 Text 节点进行压缩
|
|
|
+ * Compressor
|
|
|
*
|
|
|
- * 1:为追求性能极致,只压缩 Text 节点,所以压缩结果会存在一部分空白字符
|
|
|
- * 2:每次读取一行,按行进行压缩
|
|
|
- * 3:第一行左侧空白不压缩
|
|
|
- * 4:最后一行右侧空白不压缩(注意:最后一行以字符 '\n' 结尾时不算最后一行)
|
|
|
- * 5:第一行、最后一行以外的其它行左右侧都压缩
|
|
|
- * 6:文本之内的空白不压缩,例如字符串 "abc def" 中的 "abc" 与 "def" 之间的空格不压缩
|
|
|
- * 7:压缩分隔符默认配置为 '\n',还可配置为 ' '。如果模板中含有 javascript 脚本,需配置为 '\n'
|
|
|
- * 8:可通过 Engine.setCompressor(Compressor) 来定制自己的实现类
|
|
|
- * 可使用第三方的压缩框架来定制,例如使用 google 的压缩框架:
|
|
|
- * 压缩 html: com.googlecode.htmlcompressor:htmlcompressor
|
|
|
- * 压缩 javascript: com.google.javascript:closure-compiler
|
|
|
+ * 压缩规则:
|
|
|
+ * 1:为追求性能极致只压缩模板中的静态文本内容,指令输出的内容不压缩,例如 #(blog.content) 输出的内容不会被压缩
|
|
|
+ * 由于模板静态内容会被缓存,所以只需压缩一次,性能被最大化
|
|
|
+ *
|
|
|
+ * 2:多个连续空格字符压缩为一个空格字符
|
|
|
+ *
|
|
|
+ * 3:空格字符与 '\n' 的组合,压缩为一个 separator 字符。separator 为 Compressor 类中的属性值
|
|
|
+ * 组合是指 0 个或多个空格字符与 1 个或多个 '\n' 字符组成的连续字符串
|
|
|
+ * 组合不区分字符出现的次序,例如下面的四个字符串都满足该组合条件:
|
|
|
+ * "\n" " \n " "\n \n" "\n \n "
|
|
|
+ *
|
|
|
+ * 注意事项:
|
|
|
+ * 1:html 模板中存在 javascript 时分隔字符要配置为 '\n',分符字符 ' ' 不支持 js 压缩
|
|
|
+ *
|
|
|
+ * 2:由于多个连续的空格字符会被压缩为一个空格字符,所以当模板静态文本内容本身需要保持其多空格字符
|
|
|
+ * 不被压缩为一个时,不能使用该压缩功能,例如:
|
|
|
+ * <input value="hello ">
|
|
|
+ * 上例中的字符串 "hello " 中的两个空格会被压缩为一个空格
|
|
|
*/
|
|
|
public class Compressor {
|
|
|
|
|
|
@@ -46,94 +53,52 @@ public class Compressor {
|
|
|
|
|
|
public StringBuilder compress(StringBuilder content) {
|
|
|
int len = content.length();
|
|
|
+ StringBuilder ret = new StringBuilder(len);
|
|
|
|
|
|
- // 仅包含一个字符 '\n',需单独处理,否则会返回空字符串 ""
|
|
|
- // 测试用例: "#date()\n#date()" "#(1)\n#(2)"
|
|
|
- if (len == 1) {
|
|
|
- // 换行字符 '\n' 替换为 separator。空格除外的空白字符替换为 ' ' 压缩效果更好,例如 '\t'
|
|
|
- if (content.charAt(0) == '\n') {
|
|
|
- content.setCharAt(0, separator);
|
|
|
- } else if (content.charAt(0) < ' ') {
|
|
|
- content.setCharAt(0, ' ');
|
|
|
- }
|
|
|
- return content;
|
|
|
- }
|
|
|
-
|
|
|
+ char ch;
|
|
|
+ boolean hasLineFeed;
|
|
|
int begin = 0;
|
|
|
int forward = 0;
|
|
|
- int lineType = 1; // 1 表示第一行
|
|
|
- StringBuilder result = null;
|
|
|
+
|
|
|
while (forward < len) {
|
|
|
- if (content.charAt(forward) == '\n') {
|
|
|
- if (result == null) {
|
|
|
- result = new StringBuilder(len); // 延迟创建
|
|
|
+ // 扫描空白字符
|
|
|
+ hasLineFeed = false;
|
|
|
+ while (forward < len) {
|
|
|
+ ch = content.charAt(forward);
|
|
|
+ if (ch <= ' ') { // 包含换行字符在内的空白字符
|
|
|
+ if (ch == '\n') { // 包含换行字符
|
|
|
+ hasLineFeed = true;
|
|
|
+ }
|
|
|
+ forward++;
|
|
|
+ } else { // 非空白字符
|
|
|
+ break ;
|
|
|
}
|
|
|
- compressLine(content, begin, forward - 1, lineType, result);
|
|
|
-
|
|
|
- begin = forward + 1;
|
|
|
- forward = begin;
|
|
|
- lineType = 2; // 2 表示中间行
|
|
|
- } else {
|
|
|
- forward++;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (lineType == 1) { // 此时为 1,表示既是第一行也是最后一行
|
|
|
- return content;
|
|
|
- }
|
|
|
-
|
|
|
- lineType = 3; // 3 表示最后一行
|
|
|
- compressLine(content, begin, forward - 1, lineType, result);
|
|
|
-
|
|
|
- return result;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 按行压缩
|
|
|
- *
|
|
|
- * 只压缩文本前后的空白字符,文本内部的空白字符不压缩,极大简化压缩算法、提升压缩效率,并且压缩结果也不错
|
|
|
- *
|
|
|
- * @param content 被处理行文本所在的 StringBuilder 对象
|
|
|
- * @param start 被处理行文本的开始下标
|
|
|
- * @param end 被处理行文本的结束下标(注意 end 下标所指向的字符被包含在处理的范围之内)
|
|
|
- * @param lineType 1 表示第一行,2 表示中间行,3 表示最后一行
|
|
|
- * @param result 存放压缩结果
|
|
|
- */
|
|
|
- protected void compressLine(StringBuilder content, int start, int end, int lineType, StringBuilder result) {
|
|
|
- // 第一行不压缩左侧空白
|
|
|
- if (lineType != 1) {
|
|
|
- while (start <= end && content.charAt(start) <= ' ') {
|
|
|
- start++;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 最后一行不压缩右侧空白
|
|
|
- if (lineType != 3) {
|
|
|
- while (end >= start && content.charAt(end) <= ' ') {
|
|
|
- end--;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 空白行可出现 start 大于 end 的情况
|
|
|
- if (start <= end) {
|
|
|
- for (int i = start; i <= end; i++) {
|
|
|
- result.append(content.charAt(i));
|
|
|
}
|
|
|
|
|
|
- // 最后一行右侧未压缩,不能添加分隔字符。最后一行以 '\n' 结尾时 lineType 一定不为 3
|
|
|
- if (lineType != 3) {
|
|
|
- result.append(separator);
|
|
|
+ // 压缩空白字符
|
|
|
+ if (begin != forward) {
|
|
|
+ if (hasLineFeed) {
|
|
|
+ ret.append(separator);
|
|
|
+ } else {
|
|
|
+ ret.append(' ');
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- // 空白行,且是第一行,需要添加分隔字符,否则会被压缩去除掉该空行
|
|
|
- // 测试用例:"id=#(123)\nand" "id=#(123) \nand"
|
|
|
- else {
|
|
|
- if (lineType == 1) {
|
|
|
- // 第一行不压缩左侧空白的规则,是针对其为 "非空白行",所以此处没有原样保留空白字符
|
|
|
- // 最后一行不压缩右侧空白的规则,也是针对其为 "非空白行"
|
|
|
- result.append(separator);
|
|
|
+
|
|
|
+ // 复制非空白字符
|
|
|
+ while (forward < len) {
|
|
|
+ ch = content.charAt(forward);
|
|
|
+ if (ch > ' ') {
|
|
|
+ ret.append(ch);
|
|
|
+ forward++;
|
|
|
+ } else {
|
|
|
+ break ;
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
+ begin = forward;
|
|
|
}
|
|
|
+
|
|
|
+ return ret;
|
|
|
}
|
|
|
}
|
|
|
|