RegEx для захвата и замены элемента textContent

Я хочу заменить значение узла «имя» в обоих примерах. Я использую группу регулярных выражений, чтобы сопоставить ее и заменить. Группировка работает, а замена нет.

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

anubhava 20.05.2019 23:28

Это всего лишь пример. речь идет не о сопоставлении xml.

brain storm 21.05.2019 01:11

Итак, вы ожидаете, что replaceFirst("hello") волшебным образом поймет, что вы хотите заменить конкретную группу, а не весь матч?

Holger 21.05.2019 17:04

@Holger, это именно мой вопрос. как бы я заменил на основе имени? Я нашел найденное для группы "имя". поэтому теперь я хочу поручить регулярному выражению заменить его.

brain storm 21.05.2019 19:36

@brainstorm: Вы можете просто использовать просмотр вперед как: str = str.replaceAll("(?<=<user:name>)(?s).+?(?=</user:name>)", "hello");

anubhava 21.05.2019 20:56

@anubhava Возможна ли замена именованной группы соответствия. то, что я дал, является упрощенным примером нашего существующего кода. Есть групповое совпадение, найденное по имени. Хочу заменить, не работает.

brain storm 21.05.2019 23:12

Методы replace всегда будут заменять все совпадения. Вы можете использовать ссылки на группы в строке замены, чтобы включить совпадение, что позволяет вам делать такие вещи, как замена всего, кроме группы, но вы не можете использовать их для замены только группы. Вы можете реализовать свой собственный метод замены. Если это возможно, я мог бы добавить ответ, показывающий, как это сделать эффективно (и близко к тому, как работают оригинальные методы замены).

Holger 22.05.2019 09:14

@Holger, пожалуйста, дайте свой ответ, чтобы он был полезен и для более широкой аудитории.

brain storm 22.05.2019 21:48
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
8
189
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Я предполагаю, что здесь мы хотели бы заменить элемент имени некоторыми новыми именами. Один из способов — создать три группы захвата: одну в качестве левой границы для открытого тега, одну для желаемого вывода, который мы хотим заменить, и третью для закрывающего тега:

(<.+?: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>)

ДЕМО

можешь сказать что не так с моим? почему замена не работает, а группировка работает. Примечание. Я хочу, чтобы именованная группа соответствовала

brain storm 21.05.2019 16:43

Кстати, возможно, у меня может быть <имя>ИМЯ<имя>, поэтому (.+?) не будет соответствовать элементу

brain storm 21.05.2019 16:53
Ответ принят как подходящий

Предполагая, что это только пример и вы не пытаетесь анализировать 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>

любопытно, почему вы использовали <[^:]+

brain storm 22.05.2019 00:30
< соответствует литералу <, а [^:] соответствует любому символу, отличному от :. Это называется отрицательным классом символов, который вычисляется значительно быстрее, чем .*.
anubhava 22.05.2019 08:22

Тест m.group(NAME_GROUP) != null устарел; группа никогда не должна быть null. Кроме того, для данного конкретного решения нет необходимости осуществлять ручную замену, т.е. pattern.matcher(str).replaceAll("$1hello$3") уже сделает свою работу.

Holger 22.05.2019 09:28

Да, вы правы насчет m.group(NAME_GROUP) != null. Что касается второй части вашего комментария, на самом деле этого можно добиться и с помощью str.replaceAll("(<[^:]+:name>).*?(</[^:]+:name>)", "$1hello$3"). Я только что показал более сложный способ, поскольку OP, вероятно, пытается это сделать в более сложном варианте использования.

anubhava 22.05.2019 09:50

@anubhava Я предпочел предыдущее решение, потому что оно соответствует моему существующему коду, который у меня не так много для редактирования. Кроме того, в предыдущем используется именованная группа, а в этом нет. можно ли было бы иметь оба решения здесь для справки. Спасибо

brain storm 22.05.2019 18:36

просто чтобы прояснить, мой вопрос заключается в том, как выполнить замену в именованной группе, решение которой было в вашем предыдущем ответе. Хотя новое решение лучше, на самом деле это не то, что я искал. так что не могли бы вы включить оба решения?

brain storm 22.05.2019 18:41

@brainstorm: хорошо, я обновил свой ответ обоими фрагментами.

anubhava 22.05.2019 18:55

Спасибо, я ценю это. Я приму ответ вскоре после того, как моя сборка будет успешной :)

brain storm 22.05.2019 18:58

один из тестов не удался, потому что у него было <name>foo<name>, поэтому в основном user: кажется необязательным. Я пытался заменить на (<[^:]+:?name>), но это не сработало..

brain storm 22.05.2019 23:19

Вместо (<[^:]+:name>) используйте: (<(?:[^:]+:)?name>), где (?:...) — незахватывающая группа.

anubhava 22.05.2019 23:25

Обратите внимание, что для appendReplacement и appendTail требуется StringBuffer или Java 9, так как поддержка StringBuilder была добавлена ​​уже в Java 9. Кроме того, appendReplacement будет интерпретировать групповые ссылки и escape-символы в замещающей строке, что делает опасным передачу совпадений, найденных в исходную строку обратно в строку замены. Мой ответ показывает, как сделать либо вставить все буквально, либо выполнить необходимые шаги, чтобы процитировать части, которые не следует интерпретировать. Эта проблема не относится к replaceAll("$1hello$2").

Holger 23.05.2019 11:34

Операции 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>

Другие вопросы по теме