Подтвердить что ты не робот

Получить имена групп в java regex

Я пытаюсь получить как шаблон, так и строку и вернуть карту имени группы → согласованный результат.

Пример:

(?<user>.*)

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

проблема в том, что я не могу получить имя группы из Java regex api. Я могу получить только согласованные значения по имени или по индексу. У меня нет списка имен групп, и ни Pattern, ни Matcher, похоже, не раскрывают эту информацию. Я проверил его источник, и кажется, что информация там - она ​​просто не отображается пользователю.

Я попробовал как Java java.util.regex, так и jregex. (и все равно, если кто-то предложил любую другую библиотеку, которая хороша, поддерживается и имеет высокую производительность, поддерживающую эту функцию).

4b9b3361

Ответ 1

В Java нет API для получения имен названных групп захвата. Я думаю, что это недостающая функция.

Легкий выход - выбрать кандидат с именем группы захвата из шаблона, а затем попытаться получить доступ к названной группе из соответствия. Другими словами, вы не знаете точных имен именованных групп захвата, пока не включите строку, которая соответствует всему шаблону.

Pattern для записи имен указанной группы захвата \(\?<([a-zA-Z][a-zA-Z0-9]*)> (на основе Pattern документации по классам).

(Трудный способ - реализовать парсер для регулярного выражения и получить имена групп захвата).

Пример реализации:

import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;
import java.util.Iterator;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.regex.MatchResult;

class RegexTester {

    public static void main(String args[]) {
        Scanner scanner = new Scanner(System.in);

        String regex = scanner.nextLine();
        StringBuilder input = new StringBuilder();
        while (scanner.hasNextLine()) {
            input.append(scanner.nextLine()).append('\n');
        }

        Set<String> namedGroups = getNamedGroupCandidates(regex);

        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(input);
        int groupCount = m.groupCount();

        int matchCount = 0;

        if (m.find()) {
            // Remove invalid groups
            Iterator<String> i = namedGroups.iterator();
            while (i.hasNext()) {
                try {
                    m.group(i.next());
                } catch (IllegalArgumentException e) {
                    i.remove();
                }
            }

            matchCount += 1;
            System.out.println("Match " + matchCount + ":");
            System.out.println("=" + m.group() + "=");
            System.out.println();
            printMatches(m, namedGroups);

            while (m.find()) {
                matchCount += 1;
                System.out.println("Match " + matchCount + ":");
                System.out.println("=" + m.group() + "=");
                System.out.println();
                printMatches(m, namedGroups);
            }
        }
    }

    private static void printMatches(Matcher matcher, Set<String> namedGroups) {
        for (String name: namedGroups) {
            String matchedString = matcher.group(name);
            if (matchedString != null) {
                System.out.println(name + "=" + matchedString + "=");
            } else {
                System.out.println(name + "_");
            }
        }

        System.out.println();

        for (int i = 1; i < matcher.groupCount(); i++) {
            String matchedString = matcher.group(i);
            if (matchedString != null) {
                System.out.println(i + "=" + matchedString + "=");
            } else {
                System.out.println(i + "_");
            }
        }

        System.out.println();
    }

    private static Set<String> getNamedGroupCandidates(String regex) {
        Set<String> namedGroups = new TreeSet<String>();

        Matcher m = Pattern.compile("\\(\\?<([a-zA-Z][a-zA-Z0-9]*)>").matcher(regex);

            while (m.find()) {
                namedGroups.add(m.group(1));
            }

            return namedGroups;
        }
    }
}

Тем не менее, существует предостережение. В настоящее время он не работает с регулярным выражением в Pattern.COMMENTS режиме.

Ответ 2

Это второй легкий подход к проблеме: мы будем называть непубличный метод namedGroups() в классе Pattern, чтобы получить Map<String, Integer>, который сопоставляет имена групп с номерами групп через API отражения Java. Преимущество этого подхода состоит в том, что нам не нужна строка, содержащая соответствие регулярному выражению для поиска точных названных групп.

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

Однако обратите внимание на недостатки:

  • Этот подход может не применяться, если код запущен в системе с ограничениями безопасности, чтобы запретить любые попытки получить доступ к непубличным методам (без модификатора, защищенных и частных методов).
  • Код применим только к JRE из Oracle или OpenJDK.
  • В будущих версиях код также может быть разбит, поскольку мы вызываем непубличный метод.
  • Также может произойти удар производительности от функции вызова через отражение. (В этом случае удар производительности в основном происходит из-за отражения, так как в методе namedGroups() мало что происходит). Я не знаю, как производительность влияет на общую производительность, поэтому, пожалуйста, сделайте измерение в своей системе.

import java.util.Collections;
import java.util.Map;
import java.util.Scanner;
import java.util.regex.Pattern;

import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

class RegexTester {
  public static void main(String args[]) {
    Scanner scanner = new Scanner(System.in);

    String regex = scanner.nextLine();
    // String regex = "(?<group>[a-z]*)[trick(?<nothing>ha)]\\Q(?<quoted>Q+E+)\\E(.*)(?<Another6group>\\w+)";
    Pattern p = Pattern.compile(regex);

    Map<String, Integer> namedGroups = null;
    try {
      namedGroups = getNamedGroups(p);
    } catch (Exception e) {
      // Just an example here. You need to handle the Exception properly
      e.printStackTrace();
    }

    System.out.println(namedGroups);
  }


  @SuppressWarnings("unchecked")
  private static Map<String, Integer> getNamedGroups(Pattern regex)
      throws NoSuchMethodException, SecurityException,
             IllegalAccessException, IllegalArgumentException,
             InvocationTargetException {

    Method namedGroupsMethod = Pattern.class.getDeclaredMethod("namedGroups");
    namedGroupsMethod.setAccessible(true);

    Map<String, Integer> namedGroups = null;
    namedGroups = (Map<String, Integer>) namedGroupsMethod.invoke(regex);

    if (namedGroups == null) {
      throw new InternalError();
    }

    return Collections.unmodifiableMap(namedGroups);
  }
}

Ответ 3

Вы хотите использовать небольшую библиотеку name-regexp. Это тонкая оболочка вокруг java.util.regex с поддержкой именованных групп захвата для пользователей Java 5 или 6.

Пример использования:

Pattern p = Pattern.compile("(?<user>.*)");
Matcher m = p.matcher("JohnDoe");
System.out.println(m.namedGroups()); // {user=JohnDoe}

Maven:

<dependency>
  <groupId>com.github.tony19</groupId>
  <artifactId>named-regexp</artifactId>
  <version>0.2.3</version>
</dependency>

Рекомендации:

Ответ 4

Это невозможно сделать с помощью стандартного API. Вы можете использовать отражение для доступа к этим:

final Field namedGroups = pattern.getClass().getDeclaredField("namedGroups");
namedGroups.setAccessible(true);
final Map<String, Integer> nameToGroupIndex = (Map<String, Integer>) namedGroups.get(pattern);

Используйте набор ключей карты, если вас не интересуют индексы.