41

パターンと文字列の両方を受け取り、グループ名のマップを返そうとしています->一致した結果。

例:

(?<user>.*)

「ユーザー」をキーとして、それに一致するものを値として含むマップを返したいと思います。

問題は、Java 正規表現 API からグループ名を取得できないように見えることです。一致した値は、名前またはインデックスでのみ取得できます。グループ名のリストがなく、Pattern も Matcher もこの情報を公開していないようです。ソースを確認したところ、情報がそこにあるかのように見えます-ユーザーに公開されていないだけです.

Java の java.util.regex と jregex の両方を試しました。(そして、誰かがこの機能をサポートする優れた、サポートされた、パフォーマンスの高い他のライブラリを提案したかどうかはあまり気にしません)。

4

6 に答える 6

51

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モードの正規表現では機能しません。

于 2013-03-23T16:13:10.023 に答える
22

namedGroups()これは、問題に対する 2 番目の簡単なアプローチです。Pattern クラスの非公開メソッドを呼び出して、Java Reflection APIMap<String, Integer>を介してグループ名をグループ番号にマップする を取得します。このアプローチの利点は、正確な名前のグループを見つけるために、正規表現との一致を含む文字列が必要ないことです。

個人的には、正規表現に一致するものが入力文字列の中に存在しない正規表現の名前付きグループを知ることは役に立たないので、それはあまり利点ではないと思います。

ただし、欠点に注意してください

  • このアプローチは、コードがセキュリティ制限のあるシステムで実行され、非パブリック メソッド (修飾子なし、保護されたメソッド、およびプライベート メソッド) へのアクセスを拒否する場合には適用されない場合があります。
  • このコードは、Oracle または OpenJDK の JRE にのみ適用されます。
  • 非公開メソッドを呼び出しているため、コードは将来のリリースでも壊れる可能性があります。
  • リフレクションを介して関数を呼び出すと、パフォーマンスが低下する場合もあります。(この場合、パフォーマンスへの影響は主にリフレクション オーバーヘッドから発生します。これは、メソッド内であまり処理が行われないためです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);
  }
}
于 2013-03-24T07:28:23.007 に答える
6

小さな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}

メイヴン:

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

参考文献:

于 2015-04-14T13:41:32.037 に答える
2

次のように、正規表現のグループのパターンを「実際の」パターンに使用して、グループの名前を取得しました。

        List<String> namedGroups = new ArrayList<String>();
    {
        String normalized = matcher.pattern().toString();
        Matcher mG = Pattern.compile("\\(\\?<(.+?)>.*?\\)").matcher(normalized);

        while (mG.find()) {
            for (int i = 1; i <= mG.groupCount(); i++) {
                namedGroups.add(mG.group(i));
            }
        }
    }

次に、名前と値を HashMap<String, String> に追加しました。

        Map<String, String> map = new HashMap<String, String>(matcher.groupCount());
        
        namedGroups.stream().forEach(name -> {      
            if (matcher.start(name) > 0) {
                map.put(name, matcher.group(name));
            } else {
                map.put(name, "");
            }
        });
于 2020-11-25T20:52:00.920 に答える
0

標準 API でこれを行う方法はありません。リフレクションを使用してこれらにアクセスできます。

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

インデックスを気にしない場合は、マップのキー セットを使用します。

于 2018-11-08T09:30:04.730 に答える