${***}字符串表达式替换工具

处理如下需求等

我是:${name},我的年龄:${age}

或者

select * from user where name = ${name} and age = ${age}

如果只是固定{name}或{age}推荐使用Hutool工具:StrUtil.format();

官方文档:Hutool:StrUtilopen in new window

使用方式如下

HashMap<String, Object> map = new HashMap<>();
map.put("age", 112);
map.put("name", "admin");
String format = StrUtil.format("我是:{name},我的年龄:{age}", map);
System.out.println(format);

输出

我是:张三,我的年龄:112

自定义表达式前后缀符号,推荐使用以下工具

代码如下


import lombok.Data;

import java.util.Map;

/**
 * 参考:jodd.util.StringTemplateParser
 *
 * @author 丁乾文
 * @date 2022/4/26 20:45
 * @since 1.0.0
 */
@Data
public class StringTemplateParser {
    
    protected boolean replaceMissingKey = true;
    protected String missingKeyReplacement;
    protected boolean resolveEscapes = true;
    protected String macroPrefix = "$";
    protected String macroStart = "${";
    protected String macroEnd = "}";
    protected char escapeChar = '\\';
    protected boolean parseValues;

    public StringTemplateParser() {
    }

    public static StringTemplateParser create() {
        return new StringTemplateParser();
    }

    public StringTemplateParser setStrictFormat() {
        this.macroPrefix = null;
        return this;
    }

    public String parse(String template, StringTemplateParser.MacroResolver macroResolver) {
        StringBuilder result = new StringBuilder(template.length());
        int i = 0;
        int len = template.length();
        boolean strict;
        if (this.macroPrefix == null) {
            this.macroPrefix = this.macroStart;
            strict = true;
        } else {
            strict = false;
        }

        int prefixLen = this.macroPrefix.length();
        int startLen = this.macroStart.length();
        int endLen = this.macroEnd.length();

        while (i < len) {
            int ndx = template.indexOf(this.macroPrefix, i);
            if (ndx == -1) {
                result.append(i == 0 ? template : template.substring(i));
                break;
            }

            int j = ndx - 1;
            boolean escape = false;

            int count;
            for (count = 0; j >= 0 && template.charAt(j) == this.escapeChar; --j) {
                escape = !escape;
                if (escape) {
                    ++count;
                }
            }

            if (this.resolveEscapes) {
                result.append(template, i, ndx - count);
            } else {
                result.append(template, i, ndx);
            }

            if (escape) {
                result.append(this.macroPrefix);
                i = ndx + prefixLen;
            } else {
                boolean strictFormat = strict;
                if (!strict && isSubstringAt(template, this.macroStart, ndx)) {
                    strictFormat = true;
                }

                int ndx1;
                int ndx2;
                if (!strictFormat) {
                    ndx += prefixLen;
                    ndx1 = ndx;

                    ndx2 = ndx;
                    while (ndx2 < len && isPropertyNameChar(template.charAt(ndx2))) {
                        ++ndx2;
                    }

                    if (ndx2 == len) {
                        --ndx2;
                    }

                    while (ndx2 > ndx && !isAlphaOrDigit(template.charAt(ndx2))) {
                        --ndx2;
                    }

                    ++ndx2;
                    if (ndx2 == ndx + 1) {
                        result.append(this.macroPrefix);
                        i = ndx;
                        continue;
                    }
                } else {
                    ndx += startLen;
                    ndx2 = template.indexOf(this.macroEnd, ndx);
                    if (ndx2 == -1) {
                        throw new IllegalArgumentException("Invalid template, unclosed macro at: " + (ndx - startLen));
                    }

                    int n;
                    for (ndx1 = ndx; ndx1 < ndx2; ndx1 = n + startLen) {
                        n = indexOf(template, this.macroStart, ndx1, ndx2);
                        if (n == -1) {
                            break;
                        }
                    }
                }

                String name = template.substring(ndx1, ndx2);
                String value;
                if (this.missingKeyReplacement == null && this.replaceMissingKey) {
                    value = macroResolver.resolve(name);
                    if (value == null) {
                        value = "";
                    }
                } else {
                    try {
                        value = macroResolver.resolve(name);
                    } catch (Exception var20) {
                        value = null;
                    }

                    if (value == null) {
                        if (this.replaceMissingKey) {
                            value = this.missingKeyReplacement;
                        } else {
                            value = template.substring(ndx1 - startLen, ndx2 + 1);
                        }
                    }
                }

                if (ndx == ndx1) {
                    String stringValue = value;
                    if (this.parseValues && stringValue.contains(this.macroStart)) {
                        stringValue = this.parse(stringValue, macroResolver);
                    }

                    result.append(stringValue);
                    i = ndx2;
                    if (strictFormat) {
                        i = ndx2 + endLen;
                    }
                } else {
                    template = template.substring(0, ndx1 - startLen) + value + template.substring(ndx2 + endLen);
                    len = template.length();
                    i = ndx - startLen;
                }
            }
        }

        return result.toString();
    }

    public static boolean isSubstringAt(String string, String substring, int offset) {
        int len = substring.length();

        int max = offset + len;

        if (max > string.length()) {
            return false;
        }

        int ndx = 0;
        for (int i = offset; i < max; i++, ndx++) {
            if (string.charAt(i) != substring.charAt(ndx)) {
                return false;
            }
        }

        return true;
    }

    private static boolean isPropertyNameChar(char c) {
        return isDigit(c) || isAlpha(c) || (c == '_') || (c == '.') || (c == '[') || (c == ']');
    }

    private static boolean isAlpha(char c) {
        return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));
    }

    private static boolean isDigit(char c) {
        return c >= '0' && c <= '9';
    }

    private static boolean isAlphaOrDigit(char c) {
        return isDigit(c) || isAlpha(c);
    }


    private static int indexOf(String src, String sub, int startIndex, int endIndex) {
        if (startIndex < 0) {
            startIndex = 0;
        }
        int srcLen = src.length();
        if (endIndex > srcLen) {
            endIndex = srcLen;
        }
        int subLen = sub.length();
        if (subLen == 0) {
            return Math.min(startIndex, srcLen);
        }

        int total = endIndex - subLen + 1;
        char c = sub.charAt(0);
        mainLoop:
        for (int i = startIndex; i < total; i++) {
            if (src.charAt(i) != c) {
                continue;
            }
            int j = 1;
            int k = i + 1;
            while (j < subLen) {
                if (sub.charAt(j) != src.charAt(k)) {
                    continue mainLoop;
                }
                j++;
                k++;
            }
            return i;
        }
        return -1;
    }

    public static StringTemplateParser.MacroResolver createMapMacroResolver(final Map<?, ?> map) {
        return macroName -> {
            Object value = map.get(macroName);
            return value == null ? null : value.toString();
        };
    }

    /**
     * Macro value resolver.
     */
    public interface MacroResolver {
        /**
         * Resolves macro value for macro name founded in
         * string template. <code>null</code> values will
         * be replaced with empty strings.
         */
        String resolve(String macroName);

    }

}

使用方式

// 默认处理格式 ${name}
StringTemplateParser stp = StringTemplateParser.create();
// #{name}
// stp.setStrictFormat();
// stp.setMacroStart("#{");
HashMap<String, Object> map = new HashMap<>();
map.put("age", 112);
map.put("name", "张三");
String parse = stp.parse("我是:${name},我的年龄:${age}", StringTemplateParser.createMapMacroResolver(map));
System.out.println(parse);

输出

我是:张三,我的年龄:112

循环10000次耗时:81ms

通过正则表达式替换

代码如下

import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 〈一句话功能简述〉<br>
 * 〈〉
 *
 * @author dingqianwen
 * @date 2021/2/2
 * @since 1.0.0
 */
public class ParseUtil {

    private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\$\\{(.+?)}");


    /**
     * 根据正则表达式将文本中的变量使用实际的数据替换成无变量的文本
     *
     * @param content 替换前的数据
     * @return 替换后的数据
     * @author dingqianwen
     */
    public static String parseVariable(String content, Map<String, String> map) {
        Matcher m = VARIABLE_PATTERN.matcher(content);
        StringBuffer sb = new StringBuffer();
        while (m.find()) {
            String key = m.group(1);
            String value = map.getOrDefault(key, "");
            // bug修复 文本中的$转义
            m.appendReplacement(sb, Matcher.quoteReplacement(value));
        }
        m.appendTail(sb);
        return sb.toString();
    }

    /**
     * 正则表达式验证是否为一个变量,即${}
     *
     * @param content 检测内容
     * @return true是一个变量
     * @author dingqianwen
     */
    public static boolean isVariable(String content) {
        if (content == null || content.isBlank()) {
            return false;
        }
        Matcher matcher = VARIABLE_PATTERN.matcher(content);
        return matcher.matches();
    }


}

使用方式

Map<String, String> map = new HashMap<>();
map.put("age", "112");
map.put("name", "张三");
String value = ParseUtil.parseVariable("我是:${name},我的年龄:${age}", map);
System.out.println(value);

处理:#{name} 只需要把Pattern改为:Pattern.compile("#\\{(.+?)}")

更新日期:
作者: 丁乾文, qwding