Я хочу заменить значение узла «имя» в обоих примерах. Я использую группу регулярных выражений, чтобы сопоставить ее и заменить. Группировка работает, а замена нет.
input 1
<xml
<user:address>.../</user:address>
<user:name>foo</user:name>
</xml>
input 2
<xml
<user:address>.../</user:address>
<street:name>bar</street:name>
</xml>
private static final String NAME_GROUP = "name";
public static final Pattern pattern = Pattern.compile("<.*:name>" + "(?<" + NAME + ">.*)</.*:name>");
final Matcher nameMatcher = pattern.matcher(str);
final String s = nameMatcher.find() ? nameMatcher.group(NAME_GROUP) : null;
System.out.println(s);
//foo
//bar
теперь, когда я заменяю
String output = nameMatcher.replaceFirst("hello")
I get
hello</xml>
а я ожидал следующего
<xml
<user:address>.../</user:address>
<user:name>hello</user:name>
</xml>
Для обоих примеров. Почему группа работает, а не замена?
Это всего лишь пример. речь идет не о сопоставлении xml.
Итак, вы ожидаете, что replaceFirst("hello")
волшебным образом поймет, что вы хотите заменить конкретную группу, а не весь матч?
@Holger, это именно мой вопрос. как бы я заменил на основе имени? Я нашел найденное для группы "имя". поэтому теперь я хочу поручить регулярному выражению заменить его.
@brainstorm: Вы можете просто использовать просмотр вперед как: str = str.replaceAll("(?<=<user:name>)(?s).+?(?=</user:name>)", "hello");
@anubhava Возможна ли замена именованной группы соответствия. то, что я дал, является упрощенным примером нашего существующего кода. Есть групповое совпадение, найденное по имени. Хочу заменить, не работает.
Методы replace всегда будут заменять все совпадения. Вы можете использовать ссылки на группы в строке замены, чтобы включить совпадение, что позволяет вам делать такие вещи, как замена всего, кроме группы, но вы не можете использовать их для замены только группы. Вы можете реализовать свой собственный метод замены. Если это возможно, я мог бы добавить ответ, показывающий, как это сделать эффективно (и близко к тому, как работают оригинальные методы замены).
@Holger, пожалуйста, дайте свой ответ, чтобы он был полезен и для более широкой аудитории.
Я предполагаю, что здесь мы хотели бы заменить элемент имени некоторыми новыми именами. Один из способов — создать три группы захвата: одну в качестве левой границы для открытого тега, одну для желаемого вывода, который мы хотим заменить, и третью для закрывающего тега:
(<.+?:name>)(.+?)(</.+?:name>)
Если это выражение не нужно, его можно изменить или изменить в regex101.com.
jex.im также помогает визуализировать выражения.
import java.util.regex.Matcher;
import java.util.regex.Pattern;
final String regex = "(<.+?:name>)(.+?)(<\/.+?:name>)";
final String string = "<xml\n"
+ " <user:address>.../</user:address>\n"
+ " <user:name>foo</user:name>\n"
+ "</xml>\n"
+ "<xml\n"
+ " <user:address>.../</user:address>\n"
+ " <street:name>bar</street:name>\n"
+ "</xml>\n"
+ "<xml\n"
+ " <user:address>.../</user:address>\n"
+ " <user:name>hello</user:name>\n"
+ " </xml>";
final String subst = "\\1Any New Name You Wish Goes Here\\3";
final Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
final Matcher matcher = pattern.matcher(string);
// The substituted value will be contained in the result variable
final String result = matcher.replaceAll(subst);
System.out.println("Substitution result: " + result);
Редактировать:
Если мы хотим иметь теги <name></name>
, мы можем обновить наше выражение и сделать первую часть наших тегов необязательной:
(<(.+?:)?name>)(.+?)(</(.+?:)?name>)
можешь сказать что не так с моим? почему замена не работает, а группировка работает. Примечание. Я хочу, чтобы именованная группа соответствовала
Кстати, возможно, у меня может быть <имя>ИМЯ<имя>, поэтому (.+?) не будет соответствовать элементу
Предполагая, что это только пример и вы не пытаетесь анализировать XML с помощью регулярных выражений, вы можете использовать этот подход. Здесь мы сопоставляем и захватываем строку до и после строки в отдельных группах захвата. Взамен мы используем обратные ссылки этих групп, чтобы поместить строки перед строкой и после строки обратно в окончательный вывод.
final String str = "<xml\n" +
" <name>bar</name>\n" +
" <user:address>.../</user:address>\n" +
" <user:name>foo</user:name>\n" +
"</xml>";
final String NAME_GROUP = "name";
final Pattern pattern = Pattern.compile("(<(?:[^:]+:)?name>)(?<" + NAME_GROUP + ">.*?)(</(?:[^:]+:)?name>)");
final Matcher m = pattern.matcher(str);
StringBuilder sb = new StringBuilder();
while (m.find()) {
m.appendReplacement( sb, m.group(1) + "hello" + m.group(3) );
}
m.appendTail(sb);
System.out.println(sb);
Обратите внимание, что для этого конкретного случая можно использовать следующий более короткий код:
final Pattern pattern = Pattern.compile("(<(?:[^:]+:)?name>)>.*?(</(?:[^:]+:)?name>)");
final Matcher m = pattern.matcher(str);
String repl = m.replaceAll("$1hello$2");
System.out.println(repl);
Выход:
<xml
<name>hello</name>
<user:address>.../</user:address>
<user:name>hello</user:name>
</xml>
любопытно, почему вы использовали <[^:]+
<
соответствует литералу <
, а [^:]
соответствует любому символу, отличному от :
. Это называется отрицательным классом символов, который вычисляется значительно быстрее, чем .*
.
Тест m.group(NAME_GROUP) != null
устарел; группа никогда не должна быть null
. Кроме того, для данного конкретного решения нет необходимости осуществлять ручную замену, т.е. pattern.matcher(str).replaceAll("$1hello$3")
уже сделает свою работу.
Да, вы правы насчет m.group(NAME_GROUP) != null
. Что касается второй части вашего комментария, на самом деле этого можно добиться и с помощью str.replaceAll("(<[^:]+:name>).*?(</[^:]+:name>)", "$1hello$3")
. Я только что показал более сложный способ, поскольку OP, вероятно, пытается это сделать в более сложном варианте использования.
@anubhava Я предпочел предыдущее решение, потому что оно соответствует моему существующему коду, который у меня не так много для редактирования. Кроме того, в предыдущем используется именованная группа, а в этом нет. можно ли было бы иметь оба решения здесь для справки. Спасибо
просто чтобы прояснить, мой вопрос заключается в том, как выполнить замену в именованной группе, решение которой было в вашем предыдущем ответе. Хотя новое решение лучше, на самом деле это не то, что я искал. так что не могли бы вы включить оба решения?
@brainstorm: хорошо, я обновил свой ответ обоими фрагментами.
Спасибо, я ценю это. Я приму ответ вскоре после того, как моя сборка будет успешной :)
один из тестов не удался, потому что у него было <name>foo<name>, поэтому в основном user:
кажется необязательным. Я пытался заменить на (<[^:]+:?name>)
, но это не сработало..
Вместо (<[^:]+:name>)
используйте: (<(?:[^:]+:)?name>)
, где (?:...)
— незахватывающая группа.
Обратите внимание, что для appendReplacement
и appendTail
требуется StringBuffer
или Java 9, так как поддержка StringBuilder
была добавлена уже в Java 9. Кроме того, appendReplacement
будет интерпретировать групповые ссылки и escape-символы в замещающей строке, что делает опасным передачу совпадений, найденных в исходную строку обратно в строку замены. Мой ответ показывает, как сделать либо вставить все буквально, либо выполнить необходимые шаги, чтобы процитировать части, которые не следует интерпретировать. Эта проблема не относится к replaceAll("$1hello$2")
.
Операции 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>
Не используйте регулярное выражение для разбора XML