Могу ли я заменить группы в регулярном выражении Java? - PullRequest
84 голосов
/ 12 июня 2009

У меня есть этот код, и я хочу знать, могу ли я заменить только группы (не все шаблоны) в регулярном выражении Java. Код:

 //...
 Pattern p = Pattern.compile("(\\d).*(\\d)");
    String input = "6 example input 4";
    Matcher m = p.matcher(input);
    if (m.find()) {

        //Now I want replace group one ( (\\d) ) with number 
       //and group two (too (\\d) ) with 1, but I don't know how.

    }

Ответы [ 7 ]

108 голосов
/ 13 июня 2009

Используйте $n (где n - это цифра) для ссылки на захваченные подпоследовательности в replaceFirst(...). Я предполагаю, что вы хотели заменить первую группу литеральной строкой "число" , а вторую группу значением первой группы.

Pattern p = Pattern.compile("(\\d)(.*)(\\d)");
String input = "6 example input 4";
Matcher m = p.matcher(input);
if (m.find()) {
    // replace first number with "number" and second number with the first
    String output = m.replaceFirst("number $3$1");  // number 46
}

Рассмотрим (\D+) для второй группы вместо (.*). * - жадный сопоставитель, который сначала будет использовать последнюю цифру. Затем сопоставителю придется вернуться назад, когда он поймет, что окончательному (\d) нечего сопоставить, прежде чем он сможет сопоставиться с конечной цифрой.

47 голосов
/ 08 января 2014

Вы можете использовать Matcher#start(group) и Matcher#end(group) для создания универсального метода замены:

public static String replaceGroup(String regex, String source, int groupToReplace, String replacement) {
    return replaceGroup(regex, source, groupToReplace, 1, replacement);
}

public static String replaceGroup(String regex, String source, int groupToReplace, int groupOccurrence, String replacement) {
    Matcher m = Pattern.compile(regex).matcher(source);
    for (int i = 0; i < groupOccurrence; i++)
        if (!m.find()) return source; // pattern not met, may also throw an exception here
    return new StringBuilder(source).replace(m.start(groupToReplace), m.end(groupToReplace), replacement).toString();
}

public static void main(String[] args) {
    // replace with "%" what was matched by group 1 
    // input: aaa123ccc
    // output: %123ccc
    System.out.println(replaceGroup("([a-z]+)([0-9]+)([a-z]+)", "aaa123ccc", 1, "%"));

    // replace with "!!!" what was matched the 4th time by the group 2
    // input: a1b2c3d4e5
    // output: a1b2c3d!!!e5
    System.out.println(replaceGroup("([a-z])(\\d)", "a1b2c3d4e5", 2, 4, "!!!"));
}

Проверьте онлайн демо здесь .

14 голосов
/ 28 февраля 2018

Извините, что бил мертвую лошадь, но странно, что никто не указал на это: «Да, вы можете, но это противоположно тому, как вы используете захват групп в реальной жизни».

Если вы используете Regex так, как он предназначен, решение будет таким простым:

"6 example input 4".replaceAll("(?:\\d)(.*)(?:\\d)", "number$11");

или, как справедливо указано шмоселем ниже,

"6 example input 4".replaceAll("\d(.*)\d", "number$11");

... поскольку в вашем регулярном выражении нет веской причины группировать десятичные дроби вообще.

Обычно вы не используете захват групп в тех частях строки, которые хотите отбросить , вы используете их в той части строки, которую хотите сохранить .

Если вы действительно хотите, чтобы группы, которые вы хотите заменить, скорее всего, вместо этого вам понадобится шаблонизатор (например, mustache, ejs, StringTemplate, ...).


Помимо любопытных, даже не захватывающие группы в регулярных выражениях как раз для случая, когда движку регулярных выражений нужны они для распознавания и пропуска переменного текста. Например, в

(?:abc)*(capture me)(?:bcd)*

они нужны вам, если ваш ввод может выглядеть как «abcabc capture me bcdbcd» или «abc capture me bcd» или даже просто «захватывают меня».

Или, иначе говоря: если текст всегда один и тот же, а вы его не захватываете, нет никакой причины использовать группы вообще.

9 голосов
/ 12 июня 2009

Добавьте третью группу, добавив парены вокруг .*, затем замените подпоследовательность на "number" + m.group(2) + "1". e.g.:

String output = m.replaceFirst("number" + m.group(2) + "1");
1 голос
/ 09 июня 2010

Вы можете использовать методы matcher.start () и matcher.end () для получения групповых позиций. Таким образом, используя эти позиции, вы можете легко заменить любой текст.

0 голосов
/ 22 ноября 2018

заменить поля пароля из ввода:

{"_csrf":["9d90c85f-ac73-4b15-ad08-ebaa3fa4a005"],"originPassword":["uaas"],"newPassword":["uaas"],"confirmPassword":["uaas"]}



  private static final Pattern PATTERN = Pattern.compile(".*?password.*?\":\\[\"(.*?)\"\\](,\"|}$)", Pattern.CASE_INSENSITIVE);

  private static String replacePassword(String input, String replacement) {
    Matcher m = PATTERN.matcher(input);
    StringBuffer sb = new StringBuffer();
    while (m.find()) {
      Matcher m2 = PATTERN.matcher(m.group(0));
      if (m2.find()) {
        StringBuilder stringBuilder = new StringBuilder(m2.group(0));
        String result = stringBuilder.replace(m2.start(1), m2.end(1), replacement).toString();
        m.appendReplacement(sb, result);
      }
    }
    m.appendTail(sb);
    return sb.toString();
  }

  @Test
  public void test1() {
    String input = "{\"_csrf\":[\"9d90c85f-ac73-4b15-ad08-ebaa3fa4a005\"],\"originPassword\":[\"123\"],\"newPassword\":[\"456\"],\"confirmPassword\":[\"456\"]}";
    String expected = "{\"_csrf\":[\"9d90c85f-ac73-4b15-ad08-ebaa3fa4a005\"],\"originPassword\":[\"**\"],\"newPassword\":[\"**\"],\"confirmPassword\":[\"**\"]}";
    Assert.assertEquals(expected, replacePassword(input, "**"));
  }
0 голосов
/ 23 января 2018

Вот другое решение, которое также позволяет заменять одну группу в нескольких совпадениях. Он использует стеки для обратного порядка выполнения, поэтому можно безопасно выполнять строковую операцию.

private static void demo () {

    final String sourceString = "hello world!";

    final String regex = "(hello) (world)(!)";
    final Pattern pattern = Pattern.compile(regex);

    String result = replaceTextOfMatchGroup(sourceString, pattern, 2, world -> world.toUpperCase());
    System.out.println(result);  // output: hello WORLD!
}

public static String replaceTextOfMatchGroup(String sourceString, Pattern pattern, int groupToReplace, Function<String,String> replaceStrategy) {
    Stack<Integer> startPositions = new Stack<>();
    Stack<Integer> endPositions = new Stack<>();
    Matcher matcher = pattern.matcher(sourceString);

    while (matcher.find()) {
        startPositions.push(matcher.start(groupToReplace));
        endPositions.push(matcher.end(groupToReplace));
    }
    StringBuilder sb = new StringBuilder(sourceString);
    while (! startPositions.isEmpty()) {
        int start = startPositions.pop();
        int end = endPositions.pop();
        if (start >= 0 && end >= 0) {
            sb.replace(start, end, replaceStrategy.apply(sourceString.substring(start, end)));
        }
    }
    return sb.toString();       
}
...