Получить имена групп в java regex
Я пытаюсь получить как шаблон, так и строку и вернуть карту имени группы → согласованный результат.
Пример:
(?<user>.*)
Я хотел бы вернуться для карты, содержащей "пользователь" в качестве ключа и независимо от того, что оно соответствует его значению.
проблема в том, что я не могу получить имя группы из Java regex api. Я могу получить только согласованные значения по имени или по индексу. У меня нет списка имен групп, и ни Pattern, ни Matcher, похоже, не раскрывают эту информацию.
Я проверил его источник, и кажется, что информация там - она просто не отображается пользователю.
Я попробовал как Java java.util.regex, так и jregex. (и все равно, если кто-то предложил любую другую библиотеку, которая хороша, поддерживается и имеет высокую производительность, поддерживающую эту функцию).
Ответы
Ответ 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);
Используйте набор ключей карты, если вас не интересуют индексы.