RegEx для захвата и замены элемента textContent - PullRequest
0 голосов
/ 21 мая 2019

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

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>

Для обоих примеров. Почему группа работает, а не замена?

Ответы [ 3 ]

2 голосов
/ 22 мая 2019

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

Операции 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>
1 голос
/ 21 мая 2019

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

(<.+?:name>)(.+?)(<\/.+?:name>)

Демо

enter image description here

RegEx

Если это выражение не требуется, его можно изменить или изменить в regex101.com .

RegEx Circuit

jex.im также помогает визуализировать выражения.

enter image description here

Тест

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>)

enter image description here

DEMO

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...