package org.nutz.lang.tmpl; import java.util.LinkedList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.nutz.lang.Lang; import org.nutz.lang.Strings; import org.nutz.lang.util.NutBean; import org.nutz.lang.util.NutMap; /** * 占位符支持 `${路径<类型:格式>?默认值}` 的写法 * <p/> * 支持的类型为: * <ul> * <li><b>int</b> : %d 格式化字符串 * <li><b>long</b> : %d 格式化字符串 * <li><b>float</b> : %f 格式化字符串 * <li><b>double</b>: %f 格式化字符串 * <li><b>boolean</b>: 否/是 格式化字符串 * <li><b>date</b> : yyyyMMdd 格式化字符串 * <li><b>string</b>: %s 格式化字符串 * <li><b>json<b> : cqn 输出一段 JSON 文本,c紧凑,q输出引号,n忽略null * </ul> * <p/> * 如果没有声明类型,则当做 "string" * * @author zozoh(zozohtnt@gmail.com) */ public class Tmpl { // private static final Pattern _P2 = // Pattern.compile("([\\w\\d_.\\[\\]'\"-]+)" // + "(<(int|long|boolean|float|double|date|string)( *: *([^>]*))?>)?" // + "([?] *(.*) *)?"); private static final Pattern _P2 = Pattern.compile("([^<>()?]+)" + "([<(](int|long|boolean|float|double|date|string|json)?( *: *([^>]*))?[>)])?" + "([?] *(.*) *)?"); private Pattern _P; private int groupIndex; private int escapeIndex; private List<TmplEle> list; private List<String> keys; /** * 解析模板对象 * * @param tmpl * 模板字符串 * @return 解析好的模板对象 * * @see #parse(String, Pattern, int, int) */ public static Tmpl parse(String tmpl) { return new Tmpl(tmpl, null, -1, 0); } public static Tmpl parsef(String fmt, Object... args) { return new Tmpl(String.format(fmt, args), null, -1, 0); } /** * 解析模板对象,并用上下文进行渲染。DDD * <p/> * 你可以通过参数 ptn 指定自定义的正则表达式来声明自己的模板占位符形式。 <br> * 默认的模板占位符是 <code>(?<![$])[$][{]([^}]+)[}]</code> * <p/> * 即,形式如 <code>${xxxx}</code> 的会被当做占位符, 同时 <code>$$</code> 可以逃逸 * * @param tmpl * 模板字符串 * @param ptn * 一个正则表达式,指明占位符的形式。 * @param groupIndex * 指定正则表达式,哪个匹配组作为你的占位符内容 * @param escapeIndex * 指明了逃逸字符的组,如果为 -1 则表示没有逃逸字符 * @return 模板对象 */ public static Tmpl parse(String tmpl, Pattern ptn, int groupIndex, int escapeIndex) { return new Tmpl(tmpl, ptn, groupIndex, escapeIndex); } /** * @see #exec(String, Pattern, int, int, NutBean, boolean) */ public static String exec(String tmpl, NutBean context) { return exec(tmpl, null, -1, 0, context, true); } /** * @see #exec(String, Pattern, int, int, NutBean, boolean) */ public static String exec(String tmpl, NutBean context, boolean showKey) { return exec(tmpl, null, -1, 0, context, showKey); } /** * 解析模板对象,并用上下文进行渲染。 * * @param tmpl * 模板字符串 * @param ptn * 一个正则表达式,指明占位符的形式。 * @param groupIndex * 指定正则表达式,哪个匹配组作为你的占位符内容 * @param context * 上下文 * @param showKey * 如果占位符不存在,也没有默认值,是否显示 KEY * @return 渲染结果 * * @see #parse(String, Pattern, int, int) */ public static String exec(String tmpl, Pattern ptn, int groupIndex, int escapeIndex, NutBean context, boolean showKey) { return new Tmpl(tmpl, ptn, groupIndex, escapeIndex).render(context, showKey); } private Tmpl() { list = new LinkedList<TmplEle>(); keys = new LinkedList<String>(); } private Tmpl(Pattern ptn, int groupIndex, int escapeIndex) { this(); // 默认的模板占位符 if (null == ptn) { _P = Pattern.compile("((?<![$])[$][{]([^}]+)[}])|([$]([$][{][^}]+[}]))"); this.groupIndex = 2; this.escapeIndex = 4; } // 自定义的占位符 else { _P = ptn; this.groupIndex = groupIndex; this.escapeIndex = escapeIndex; } } private Tmpl(String tmpl, Pattern ptn, int groupIndex, int escapeIndex) { this(ptn, groupIndex, escapeIndex); // 开始解析 Matcher m = _P.matcher(tmpl); int lastIndex = 0; while (m.find()) { int pos = m.start(); // 看看是否要生成静态对象 if (pos > lastIndex) { list.add(new TmplStaticEle(tmpl.substring(lastIndex, pos))); } // 看看是逃逸呢,还是匹配上了 String s_escape = this.escapeIndex > 0 ? m.group(this.escapeIndex) : null; String s_match = m.group(this.groupIndex); // 如果是逃逸 if (!Strings.isBlank(s_escape)) { list.add(new TmplStaticEle(s_escape)); } // 否则分析键 else { Matcher m2 = _P2.matcher(s_match); if (!m2.find()) throw Lang.makeThrow("Fail to parse tmpl key '%s'", m.group(1)); String key = m2.group(1); String type = Strings.sNull(m2.group(3), "string"); String fmt = m2.group(5); String dft = m2.group(7); // 记录键 keys.add(key); // 创建元素 if ("string".equals(type)) { list.add(new TmplStringEle(key, fmt, dft)); } // int else if ("int".equals(type)) { list.add(new TmplIntEle(key, fmt, dft)); } // long else if ("long".equals(type)) { list.add(new TmplLongEle(key, fmt, dft)); } // boolean else if ("boolean".equals(type)) { list.add(new TmplBooleanEle(key, fmt, dft)); } // float else if ("float".equals(type)) { list.add(new TmplFloatEle(key, fmt, dft)); } // double else if ("double".equals(type)) { list.add(new TmplDoubleEle(key, fmt, dft)); } // date else if ("date".equals(type)) { list.add(new TmplDateEle(key, fmt, dft)); } // json else if ("json".equals(type)) { list.add(new TmplJsonEle(key, fmt, dft)); } // 靠不可能 else { throw Lang.impossible(); } } // 记录 lastIndex = m.end(); } // 最后结尾是否要加入一个对象 if (!(lastIndex >= tmpl.length())) { list.add(new TmplStaticEle(tmpl.substring(lastIndex))); } } public String render(NutBean context) { return render(context, true); } public String render(NutBean context, boolean showKey) { StringBuilder sb = new StringBuilder(); if (null == context) context = new NutMap(); for (TmplEle ele : list) { ele.join(sb, context, showKey); } return sb.toString(); } public List<String> keys() { return this.keys; } public String toString() { StringBuilder sb = new StringBuilder(); for (TmplEle ele : list) { sb.append(ele); } return sb.toString(); } }