Skip to content

如何用正则表达式求模板字符串替换前后的键值对

eulerlcs edited this page Jul 18, 2017 · 2 revisions

这个问题正是体现正则表达式威力的好例子,所以在这纪录一下,供大家讨论,我把他的问题整理了一下。

问题

有一个模板字符串,占位符(或者也可以叫变量)的格式是${变量名},同时有一个把各个占位符替换成真实值之后的文本字符串,请列出占位符和其替换后真实值的键值对。

例子

问题 模板字符串:【工银信用卡】于${startTime}至${endTime}申办奋斗卡,无年费,赢郎平签名排球!详情${link} 文本字符串:【工银信用卡】于昨天至今天申办奋斗卡,无年费,赢郎平签名排球!详情没有

答案

占位符 真实值
${startTime} 昨天
${endTime} 今天
${link} 没有

思路

1.抽出所有的占位符

先写出占位符的正则表达式,然后用Matcher.find()查找模板字符串,Matcher.group()就是占位符。

2.改造模板字符串使其成为正则表达式

把占位符替换成正则表达式的子表达式(.*?),注意在替换后的正则表达式字符串前后要加 ^$,理由大家想想。

3.用上面改造后的正则表达式抽出替换后的真实值

同样,用Matcher.find()查找文本字符串,Matcher.group()就是替换后的真实值。

技术提示

/**
 * Matcher.group的例子:匹配 字母-数字
 * 
 * <pre>
 * group(0):正则表达式的匹配值
 * </pre>
 */
@Test
public void test05_00() {
	Pattern p = Pattern.compile("([a-z]+)-(\\d+)");
	Matcher m = p.matcher("type x-235, type y-3, type zw-465");

	while (m.find()) {
		System.out.println(m.group());
	}
}

一种解法

@Test
public void test_template_string() {
	String tmpl = null;
	String text = null;

	// test data 1
	tmpl = "【工银信用卡】于${startTime}至${endTime}申办奋斗卡,无年费,赢郎平签名排球!详情${link}";
	text = "【工银信用卡】于昨天至今天申办奋斗卡,无年费,赢郎平签名排球!详情没有";

	// test data 2
	tmpl = "a${startTime}b${endTime}";
	text = "abbc";

	// test data 3
	tmpl = "(${startTime}至${endTime}";
	text = "(昨天至今天";

	System.out.println("模板字符串:" + tmpl);
	System.out.println("文本字符串:" + text);
	System.out.println();

	// 模板字符串中变量的正则表达式
	String keyRegex = "\\$\\{.*?\\}";

	// 找出模板字符串中变量
	List<String> keyList = new ArrayList<>();
	{
		Pattern p = Pattern.compile(keyRegex);
		Matcher m = p.matcher(tmpl);

		while (m.find()) {
			keyList.add(m.group());
		}
	}

	// 找出文本字符串中替换值集合
	List<String> valueList = new ArrayList<>();
	{
		// **关键想法** 把模板字符串改装成正则表达式

		// ** 难点**
		// 如果字符串中有元字符,改装后的正则表达式会有语法错误。
		// 例子:如果模板字符串中仅存在一个(,该装后的正则表达式会有语法错误。
		// 所以在每一个字符前都加\,这样(就变成\(,变成匹配(,符合原意。
		// 但是如果模板字符串中仅存在一个t,变成了\t,变成了匹配tab键,又出现了错误。
		// 结合以上的说明,我们要有选择的在字符前加\, 我们在非数字字母的字符前加\
		String tmplEscape = tmpl.replaceAll("([^\\w])", "\\\\$1");

		// 在原始模板字符串中的占位符中的非数字字母的字符前,
		// 已经被加\,所以占位符的正则表达式也要做相应的编辑
		String keyRegexEscape = "\\\\\\$\\\\\\{.*?\\\\\\}";

		String tmplRegex = "^" + tmplEscape.replaceAll(keyRegexEscape, "(.*?)") + "$";
		System.out.println("模板字符串改装后的正则表达式:" + tmplRegex);
		System.out.println();

		Pattern p = Pattern.compile(tmplRegex);
		Matcher m = p.matcher(text);
		if (m.find()) {
			for (int i = 1; i <= m.groupCount(); i++) {
				valueList.add(m.group(i));
			}
		}
	}

	// 输出结果
	if (valueList.isEmpty()) {
		System.out.println("the text file format is not correct");
	} else {
		for (int i = 0; i < keyList.size(); i++) {
			System.out.println(keyList.get(i) + "\t\t" + valueList.get(i));
		}
	}
}