Операции replaceFirst
/ replaceAll
в String
и Matcher
всегда заменяют все совпадения. Они сводятся к реализации, как
public static String replace(
CharSequence source, Pattern p, String replacement, boolean all) {
Matcher m = p.matcher(source);
if(!m.find()) return source.toString();
StringBuffer sb = new StringBuffer();
do m.appendReplacement(sb, replacement); while(all && m.find());
return m.appendTail(sb).toString();
}
Обратите внимание, что до Java 9 мы должны использовать StringBuffer
вместо StringBuilder
здесь.
Когда мы игнорируем возможность иметь групповые ссылки в строке замены, мы можем углубиться на один уровень глубже в логику и получить
public static String replaceLiteral(
CharSequence source, Pattern p, String replacement, boolean all) {
Matcher m = p.matcher(source);
if(!m.find()) return source.toString();
StringBuilder sb = new StringBuilder();
int lastEnd = 0;
do {
sb.append(source, lastEnd, m.start()).append(replacement);
lastEnd = m.end();
} while(all && m.find());
return sb.append(source, lastEnd, source.length()).toString();
}
Для этого кода очень просто изменить логику для замены определенной именованной группы, а не всего соответствия:
public static String replaceGroupWithLiteral(
CharSequence source, Pattern p, String groupName, String replacement, boolean all) {
Matcher m = p.matcher(source);
if(!m.find()) return source.toString();
StringBuilder sb = new StringBuilder();
int lastEnd = 0;
do {
sb.append(source, lastEnd, m.start(groupName)).append(replacement);
lastEnd = m.end(groupName);
} while(all && m.find());
return sb.append(source, lastEnd, source.length()).toString();
}
Этого уже достаточно для реализации вашего примера:
private static final String NAME_GROUP = "name";
public static final Pattern pattern
= Pattern.compile("<.*:name>" + "(?<" + NAME_GROUP + ">.*)</.*:name>");
String input =
"<xml\n"
+ " <user:address>.../</user:address>\n"
+ " <user:name>foo</user:name>\n"
+ "</xml>\n";
String s = replaceGroupWithLiteral(input, pattern, NAME_GROUP, "hello", false);
System.out.println(s);
<xml
<user:address>.../</user:address>
<user:name>hello</user:name>
</xml>
Хотя я бы, наверное, использовал что-то вроде
public static final Pattern pattern
= Pattern.compile("<([^<>:]*?:name)>" + "(?<" + NAME_GROUP + ">.*)</\\1>");
Как уже было сказано (и поясняется именем метода), это отличается от обычной операции замены регулярного выражения, поскольку она всегда вставляет замену буквально. Чтобы получить то же поведение, что и для архетипа, требуется более сложный и менее эффективный код, поэтому я буду использовать его только тогда, когда действительно требуется ссылаться на группы (или предполагается, что синтаксис заменяет синтаксис по контракту).
public static String replaceGroup(
CharSequence source, Pattern p, String groupName, String replacement, boolean all) {
Matcher m = p.matcher(source);
if(!m.find()) return source.toString();
StringBuffer sb = new StringBuffer();
do {
int s = m.start(), gs = m.start(groupName), e = m.end(), ge = m.end(groupName);
String prefix = s == gs? "":
Matcher.quoteReplacement(source.subSequence(s, gs).toString());
String suffix = e == ge? "":
Matcher.quoteReplacement(source.subSequence(ge, e).toString());
m.appendReplacement(sb, prefix+replacement+suffix);
} while(all && m.find());
return m.appendTail(sb).toString();
}
С этим, если мы используем, например,
String s = replaceGroup(input, pattern, NAME_GROUP, "[[${"+NAME_GROUP+"}]]", false);
получаем
<xml
<user:address>.../</user:address>
<user:name>[[foo]]</user:name>
</xml>