1

I'm using regex to replace placeholders in a template file.

I have this method:

public static String processTemplate(String template, Map<String, String> attributes) {
    Matcher m = PLACEHOLDER_PATTERN.matcher(template);
    String message = template;
    boolean matches = m.matches();

    if (matches) {
        for (int i = 1; i < m.groupCount() + 1; i++) {
            message = message.replaceAll(m.group(i), attributes.get(m.group(i)));
        }
    }

    return message;
}

with this pattern:

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

But this test fails:

@Test
public void templates() {
    Map<String, String> attributes = new HashMap<>();
    attributes.put("${wobble}", "wobble");
    String result = processTemplate("wibble ${wobble}", attributes);
    assertEquals("wibble wobble", result);
}

And I don't know why. It seems that the 'match' is returning false.

12
  • 1
    The test is only a single line, but the prod code is multiline Commented Jan 19, 2016 at 14:14
  • You do not have ^ nor $ in the pattern, the Pattern.MULTILINE will not have any effect on your pattern. The Pattern.DOTALL will force . match newline symbols, and you have . in the pattern. Commented Jan 19, 2016 at 14:15
  • your template "${wobble}" doesn't have any newline in it Commented Jan 19, 2016 at 14:15
  • Thanks, but the multiline/newline stuff is misleading. I've just removed it. It has no bearing on the test passing or failing (I've just tried it). Commented Jan 19, 2016 at 14:17
  • @WiktorStribiżew, the \\$ doesn't mean end of line? Should it be "$" without "\\"? Commented Jan 19, 2016 at 14:17

2 Answers 2

2

This is how you can process your regex:

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

public static String processTemplate(String template, Map<String, String> attributes) {
    Matcher m = PLACEHOLDER_PATTERN.matcher(template);

    StringBuffer sb = new StringBuffer();
    while (m.find()) {
       if (attributes.containsKey(m.group()))
           m.appendReplacement(sb, attributes.get(m.group()));
    }
    m.appendTail(sb);

    return sb.toString();
}

Then call it as:

Map<String, String> attributes = new HashMap<>();
attributes.put("${wobble}", "wobble");
String result = processTemplate("wibble ${wobble}", attributes);
//=> "wibble wobble"

Changes are:

  1. Use matcher.find() instead matcher.matches()
  2. Use matcher.appendReplacement() to append each replacement into a buffer
  3. Finally call matcher.appendTail() to append remaining text
Sign up to request clarification or add additional context in comments.

1 Comment

There was a second problem, that you've solved with this solution too. It turns out that my replaceAll wouldn't have worked as written either, but your solution bypasses that problem nicely. Thanks again.
1

The problem is that you are using Matcher.matches() which, as the docs say:

Attempts to match the entire region against the pattern.

So when you pass in "wibble ${wobble}" the match fails because the "wibble " bit isn't accounted for in your regex.

Instead of Matcher.matches() you should use Matcher.find() which will find the next partial match.

1 Comment

You're absolutely right, this was the problem I was trying to solve. There was a further problem in the code though that I hadn't noticed, which was that the replaceAll wouldn't have worked either. This was noticed and solved by the accepted answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.