Regex для извлечения котируемых строк и символа запроса

У меня есть язык, который определяет строку как ограничиваемую одиночными или двойными кавычками, где разделитель спрятан внутри строки, удваивая его. Например, все следующие строки являются юридическими:

'This isn''t easy to parse.'
'Then John said, "Hello Tim!"'
"This isn't easy to parse."
"Then John said, ""Hello Tim!"""

У меня есть набор строк (определенный выше), ограниченный тем, что не содержит цитаты. То, что я пытаюсь сделать с помощью регулярных выражений, заключается в анализе каждой строки в списке. Например, вот вход:

"Некоторые строки # 1" ИЛИ "Некоторые строки # 2" И "Некоторые строки" # 3 "XOR
'Some" String "# 4' HOWDY" Some "" String "" # 5" FOO 'Some' 'String' '# 6'

Регулярное выражение для определения того, является ли строка такой формы тривиальным:

^(?:"(?:[^"]|"")*"|'(?:[^']|'')*')(?:\s+[^"'\s]+\s+(?:"(?:[^"]|"")*"|'(?:[^']|'')*')*

После выполнения вышеуказанного выражения, чтобы проверить, имеет ли он такую ​​форму, мне нужно другое регулярное выражение, чтобы получить каждую строку с разделителем из ввода. Я планирую сделать это следующим образом:

Pattern pattern = Pattern.compile("What REGEX goes here?");
Matcher matcher = pattern.matcher(inputString);
int startIndex = 0;
while (matcher.find(startIndex))
{
    String quote        = matcher.group(1);
    String quotedString = matcher.group(2);
    ...
    startIndex = matcher.end();
}

Я хотел бы регулярное выражение, которое фиксирует символ кавычки в группе # 1, и текст внутри кавычек в группе # 2 (я использую Java Regex). Итак, для вышеприведенного ввода я ищу регулярное выражение, которое выдает следующий результат в каждой итерации цикла:

Loop 1: matcher.group(1) = "
        matcher.group(2) = Some String #1
Loop 2: matcher.group(1) = '
        matcher.group(2) = Some String #2
Loop 3: matcher.group(1) = "
        matcher.group(2) = Some 'String' #3
Loop 4: matcher.group(1) = '
        matcher.group(2) = Some "String" #4
Loop 5: matcher.group(1) = "
        matcher.group(2) = Some ""String"" #5
Loop 6: matcher.group(1) = '
        matcher.group(2) = Some ''String'' #6

Шаблоны, которые я пробовал до сих пор (un-escaped, а затем escape-код для Java-кода):

(["'])((?:[^\1]|\1\1)*)\1
"([\"'])((?:[^\\1]|\\1\\1)*)\\1"

(?<quot>")(?<val>(?:[^"]|"")*)"|(?<quot>')(?<val>(?:[^']|'')*)'
"(?<quot>\")(?<val>(?:[^\"]|\"\")*)\"|(?<quot>')(?<val>(?:[^']|'')*)'"

Оба они не работают при попытке скомпилировать шаблон.

Возможно ли такое регулярное выражение?

Ответ 1

Сделайте класс утилиты, который соответствует вам:

class test {
    private static Pattern pd = Pattern.compile("(\")((?:[^\"]|\"\")*)\"");
    private static Pattern ps = Pattern.compile("(')((?:[^']|'')*)'");
    public static Matcher match(String s) {
        Matcher md = pd.matcher(s);
        if (md.matches()) return md;
        else return ps.matcher(s);
    }
}

Ответ 2

Я не уверен, что это то, о чем вы просите, но вы можете просто написать код для синтаксического анализа строки и получить желаемые результаты (символ цитаты и внутренний текст) вместо обычного выражения.

class Parser {

  public static ParseResult parse(String str)
  throws ParseException {

    if(str == null || (str.length() < 2)){
      throw new ParseException();
    }

    Character delimiter = getDelimiter(str);

    // Remove delimiters
    str = str.substring(1, str.length() -1);

    // Unescape escaped quotes in inner string
    String escapedDelim = "" + delimiter + delimiter;
    str = str.replaceAll(escapedDelim, "" + delimiter);

    return new ParseResult(delimiter, str);
  }

  private static Character getDelimiter(String str)
  throws ParseException {
    Character firstChar = str.charAt(0);
    Character lastChar = str.charAt(str.length() -1);

    if(!firstChar.equals(lastChar)){
      throw new ParseException(String.format(
            "First char (%s) doesn't match last char (%s) for string %s",
           firstChar, lastChar, str
      ));
    }

    return firstChar;
  }

}
class ParseResult {

  public final Character delimiter;
  public final String contents;

  public ParseResult(Character delimiter, String contents){
    this.delimiter = delimiter;
    this.contents = contents;
  }

}
class ParseException extends Exception {

  public ParseException(){
    super();
  }

  public ParseException(String msg){
    super(msg);
  }

}

Ответ 3

Используйте это регулярное выражение:

"^('|\")(.*)\\1$"

Некоторые тестовые коды:

public static void main(String[] args) {
    String[] tests = {
            "'This isn''t easy to parse.'",
            "'Then John said, \"Hello Tim!\"'",
            "\"This isn't easy to parse.\"",
            "\"Then John said, \"\"Hello Tim!\"\"\""};
    Pattern pattern = Pattern.compile("^('|\")(.*)\\1$");
    Arrays.stream(tests).map(pattern::matcher).filter(Matcher::find).forEach(m -> System.out.println("1=" + m.group(1) + ", 2=" + m.group(2)));
}

Вывод:

1=', 2=This isn''t easy to parse.
1=', 2=Then John said, "Hello Tim!"
1=", 2=This isn't easy to parse.
1=", 2=Then John said, ""Hello Tim!""

Если вам интересно, как захватить цитируемый текст в тексте:

Это регулярное выражение соответствует всем вариантам и фиксирует цитату в группе 1 и цитируемый текст в группе 6:

^((')|("))(.*?("\3|")(.*)\5)?.*\1$

Смотрите живая демонстрация.


Вот несколько тестовых кодов:

public static void main(String[] args) {
    String[] tests = {
            "'This isn''t easy to parse.'",
            "'Then John said, \"Hello Tim!\"'",
            "\"This isn't easy to parse.\"",
            "\"Then John said, \"\"Hello Tim!\"\"\""};
    Pattern pattern = Pattern.compile("^((')|(\"))(.*?(\"\\3|\")(.*)\\5)?.*\\1$");
    Arrays.stream(tests).map(pattern::matcher).filter(Matcher::find)
      .forEach(m -> System.out.println("quote=" + m.group(1) + ", quoted=" + m.group(6)));
}

Вывод:

quote=', quoted=null
quote=', quoted=Hello Tim!
quote=", quoted=null
quote=", quoted=Hello Tim!

Ответ 4

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

Кроме того, такой простой синтаксический анализ может легко поддерживать такие вещи, как обратные слэш-экраны и преобразование последовательностей обратной косой черты в символы (например, "\n" преобразование в символ новой строки).

Ответ 5

Это можно сделать очень легко с помощью простого регулярного выражения, как показано ниже

private static Object[] checkPattern(String name, String regex) {
    List<String> matchedString = new ArrayList<>();
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(name);
    while (matcher.find()) {
        if (matcher.group().length() > 0) {
            matchedString.add(matcher.group());
        }
    }
    return matchedString.toArray();
}


@Test
public void quotedtextMultipleQuotedLines() {
    String text = "He said, \"I am Tom\". She said, \"I am Lisa\".";
    String quoteRegex = "(\"[^\"]+\")";
    String[] strArray = {"\"I am Tom\"", "\"I am Lisa\""};
    assertArrayEquals(strArray, checkPattern(text, quoteRegex));
}

Мы получаем строки как элементы массива здесь.