Нужны свежие глаза для регулярного выражения Java, которое является слишком жадным - PullRequest
2 голосов
/ 19 июля 2011

У меня есть строка вида:

canonical_class_name[key1="value1",key2="value2",key3="value3",...] 

Цель состоит в том, чтобы захватить canonical_class_name в группе и затем чередовать ключ = значение группы.В настоящее время он не соответствует тестовой строке (в следующей программе testString).

Должна быть хотя бы одна пара ключ / значение, но таких пар может быть много.

Вопрос : В настоящее время регулярное выражение правильно захватывает каноническое имя класса и первый ключ, но затем оно сожирает все до последней двойной кавычки, как мне сделать так, чтобы оно получало ленивые пары значений ключа?1012 * Вот регулярное выражение, которое объединяет следующая программа:

(\S+)\[\s*(\S+)\s*=\s*"(.*)"\s*(?:\s*,\s*(\S+)\s*=\s*"(.*)"\s*)*\]

В зависимости от ваших предпочтений вы можете найти версию программы более удобочитаемой.

Если моей программе передана строка:

org.myobject[key1=\"value1\", key2=\"value2\", key3=\"value3\"]

... это группы, которые я получаю:

Group1 contains: org.myobject<br/>
Group2 contains: key1<br/>
Group3 contains: value1", key2="value2", key3="value3<br/>

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

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class BasicORMParser {
     String regex =
            "canonicalName\\[ map (?: , map )*\\]"
            .replace("canonicalName", "(\\S+)")
            .replace("map", "key = \"value\"")
            .replace("key", "(\\S+)")
            .replace("value", "(.*)")
            .replace(" ", "\\s*"); 

    List<String> getGroups(String ormString){
        List<String> values = new ArrayList();
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(ormString);
        if (matcher.matches() == false){
            String msg = String.format("String failed regex validiation. Required: %s , found: %s", regex, ormString);
            throw new RuntimeException(msg);
        }
        if(matcher.groupCount() < 2){
            String msg = String.format("Did not find Class and at least one key value.");
            throw new RuntimeException(msg);
        }
        for(int i = 1; i < matcher.groupCount(); i++){
            values.add(matcher.group(i));
        }
        return values;
    }
}

Ответы [ 2 ]

4 голосов
/ 19 июля 2011

Вы практически ответили на вопрос сами: сделайте их ленивыми.То есть используйте ленивые (иначе говоря, не жадные или неохотные ) квантификаторы.Просто измените каждый (\S+) на (\S+?), а каждый (.*) на (.*?).Но если бы это был я, я бы изменил эти подвыражения, чтобы они никогда не могли совпадать слишком сильно, независимо от жадности.Например, вы можете использовать ([^\s\[]+) для имени класса, ([^\s=]+) для ключа и "([^"]*)" для значения.

Однако я не думаю, что это решит вашу реальную проблему.Как только вы получите его, чтобы он правильно соответствовал всем парам ключ / значение, вы обнаружите, что он только захватывает первую пару (группы № 2 и № 3) и последнюю пару (группы № 4).и № 5).Это потому, что каждый раз, когда (?:\s*,\s*(\S+)\s*=\s*"(.*)"\s*)* повторяется, эти две группы перезаписывают свое содержимое, и все, что они записали на предыдущей итерации, теряется.Там не обойтись, это как минимум двухступенчатая операция.Например, вы можете сопоставить все пары ключ / значение как блок, а затем выделить отдельные пары.

Еще одна вещь.Эта строка:

if(matcher.groupCount() < 2){

... вероятно, не делает то, что вы думаете, что делает.groupCount() - статическое свойство объекта Pattern;он говорит, сколько групп захвата в регулярном выражении.В случае успеха или неудачи совпадения, groupCount() всегда будет возвращать одно и то же значение - в данном случае, пять.Если совпадение прошло успешно, некоторые из групп захвата могут быть нулевыми (что указывает на то, что они не участвовали в матче), но их всегда будет пять.


РЕДАКТИРОВАТЬ: Я подозреваю, что этото, что вы пытались изначально:

Pattern p = Pattern.compile(
    "(?:([^\\s\\[]+)\\[|\\G)([^\\s=]+)=\"([^\"]*)\"[,\\s]*");

String s = "org.myobject[key1=\"value1\", key2=\"value2\", key3=\"value3\"]";
Matcher m = p.matcher(s);
while (m.find())
{
  if (m.group(1) != null)
  {
    System.out.printf("class : %s%n", m.group(1));
  }
  System.out.printf("key : %s, value : %s%n", m.group(2), m.group(3));
}

вывод:

class : org.myobject
key : key1, value : value1
key : key2, value : value2
key : key3, value : value3

Ключом к пониманию регулярного выражения является эта часть: (?:([^\s\[]+)\[|\G).На первом проходе это соответствует имени класса и открывающей квадратной скобке.После этого \G вступает во владение, привязывая следующий матч к позиции, где закончился предыдущий матч.

2 голосов
/ 19 июля 2011

Для сопоставления без жадности добавьте ? после шаблона. например, .*? соответствует наименьшему возможному числу символов.

...