Поиск строк в кавычках с экранированными кавычками в C # с помощью регулярного выражения - PullRequest
40 голосов
/ 27 января 2010

Я пытаюсь найти весь цитируемый текст в одной строке.

Пример:

"Some Text"
"Some more Text"
"Even more text about \"this text\""

Мне нужно получить:

  • "Some Text"
  • "Some more Text"
  • "Even more text about \"this text\""

\"[^\"\r]*\" дает мне все, кроме последнего, из-за экранированных кавычек.

Я прочитал о \"[^\"\\]*(?:\\.[^\"\\]*)*\" работе, но я получаю сообщение об ошибке во время выполнения:

parsing ""[^"\]*(?:\.[^"\]*)*"" - Unterminated [] set.

Как мне это исправить?

Ответы [ 10 ]

79 голосов
/ 28 января 2010

То, что у вас есть, является примером техники «развернутого цикла» Фридла, но у вас, кажется, есть некоторая путаница относительно того, как выразить это как строковый литерал.Вот как это должно выглядеть в компиляторе регулярных выражений:

"[^"\\]*(?:\\.[^"\\]*)*"

Начальная "[^"\\]* соответствует кавычке, за которой следует ноль или более любых символов, кроме кавычек или обратной косой черты.Одна только эта часть, вместе с окончательным ", будет соответствовать простой строке в кавычках без встроенных escape-последовательностей, как "this" или "".

Если встречает обратную косую черту, \\. потребляет обратную косую черту и все, что следует за ней, а [^"\\]* (снова) потребляет все до следующей обратной косой черты или кавычки.Эта часть повторяется столько раз, сколько необходимо, пока не появится неэкранированная кавычка (или она не достигнет конца строки и попытка совпадения не удастся).

Обратите внимание, что это будет соответствовать "foo\"- в \"foo\"-"bar",Это может показаться недостатком в регулярном выражении, но это не так;это вход , который недопустим.Цель состояла в том, чтобы сопоставить строки в кавычках, необязательно содержащие кавычки с обратной косой чертой, встроенные в другой текст - почему бы не быть кавычек вне строк в кавычках?Если вам действительно нужно это поддерживать, у вас есть гораздо более сложная проблема, требующая совсем другого подхода.

Как я уже сказал, выше приведено то, как регулярное выражение должно выглядеть для компилятора регулярного выражения.Но вы пишете это в форме строкового литерала, и они, как правило, обрабатывают определенные символы специально - то есть, обратную косую черту и кавычки.К счастью, дословные строки C # избавляют вас от необходимости двойной обратной косой черты;вам просто нужно избегать каждой кавычки с другой кавычкой:

Regex r = new Regex(@"""[^""\\]*(?:\\.[^""\\]*)*""");

Таким образом, правило заключается в двойных кавычках для компилятора C # и двойной обратной косой черте для компилятора регулярных выражений - красиво и легко.Это конкретное регулярное выражение может выглядеть немного неловко, с тремя кавычками на обоих концах, но рассмотрим альтернативный вариант:

Regex r = new Regex("\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"");

В Java вы всегда должны писать их таким образом.: - (

12 голосов
/ 10 сентября 2010

Regex для захвата строк (с \ для экранирования символов), для механизма .NET:

(?>(?(STR)(?(ESC).(?<-ESC>)|\\(?<ESC>))|(?!))|(?(STR)"(?<-STR>)|"(?<STR>))|(?(STR).|(?!)))+   

Вот «дружественная» версия:

(?>                            | especify nonbacktracking
   (?(STR)                     | if (STRING MODE) then
         (?(ESC)               |     if (ESCAPE MODE) then
               .(?<-ESC>)      |          match any char and exits escape mode (pop ESC)
               |               |     else
               \\(?<ESC>)      |          match '\' and enters escape mode (push ESC)
         )                     |     endif
         |                     | else
         (?!)                  |     do nothing (NOP)
   )                           | endif
   |                           | -- OR
   (?(STR)                     | if (STRING MODE) then
         "(?<-STR>)            |     match '"' and exits string mode (pop STR)
         |                     | else
         "(?<STR>)             |     match '"' and enters string mode (push STR)
   )                           | endif
   |                           | -- OR
   (?(STR)                     | if (STRING MODE) then
         .                     |     matches any character
         |                     | else
         (?!)                  |     do nothing (NOP)  
   )                           | endif
)+                             | REPEATS FOR EVERY CHARACTER

На основании http://tomkaminski.com/conditional-constructs-net-regular-expressions примеров. Это зависит от баланса котировок. Я использую это с большим успехом. Используйте его с флагом Singleline.

Чтобы поиграть с регулярными выражениями, я рекомендую Rad Software Designer Regular Expression Designer , который имеет приятную вкладку "Language Elements" с быстрым доступом к некоторым основным инструкциям. Он основан на движке регулярных выражений .NET.

4 голосов
/ 27 января 2010
"(\\"|\\\\|[^"\\])*"

должно работать. Совпадение с экранированной кавычкой, экранированной обратной косой чертой или любым другим символом, кроме кавычки или обратной косой черты. Повторение.

В C #:

StringCollection resultList = new StringCollection();
Regex regexObj = new Regex(@"""(\\""|\\\\|[^""\\])*""");
Match matchResult = regexObj.Match(subjectString);
while (matchResult.Success) {
    resultList.Add(matchResult.Value);
    matchResult = matchResult.NextMatch();
} 

Редактировать: добавлен экранированный обратный слеш в список для правильной обработки "This is a test\\".

Пояснение:

Сначала соответствует символу кавычки.

Затем альтернативы оцениваются слева направо. Двигатель сначала пытается соответствовать экранированной кавычке. Если это не соответствует, он пытается избежать обратной косой черты. Таким образом, он может различать "Hello \" string continues" и "String ends here \\".

Если один из них не совпадает, то разрешается что-либо еще, кроме символа кавычки или обратной косой черты. Затем повторите.

Наконец, сопоставьте заключительную цитату.

3 голосов
/ 27 января 2010

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

Что касается вашей проблемы, я бы попробовал четыре / вместо двух:

\"[^\"\\\\]*(?:\\.[^\"\\\\]*)*\"
2 голосов
/ 27 января 2010

Регулярное выражение

(?<!\\)".*?(?<!\\)"

также будет обрабатывать текст, начинающийся с экранированной кавычки:

\"Some Text\" Some Text "Some Text", and "Some more Text" an""d "Even more text about \"this text\""
1 голос
/ 02 августа 2013

Хорошо, ответ Алана Мура хорош, но я бы немного его изменил, чтобы сделать его более компактным. Для компилятора регулярных выражений:

"([^"\\]*(\\.)*)*"

Сравните с выражением лица Алана Мура:

"[^"\\]*(\\.[^"\\]*)*"

Объяснение очень похоже на объяснение Алана Мура:

Первая часть " соответствует кавычке.

Вторая часть [^"\\]* соответствует нулю или более любых символов, кроме кавычек или обратной косой черты.

И последняя часть (\\.)* соответствует обратной косой черте и любому последующему символу. Обратите внимание на *, говоря, что эта группа необязательна.

Описанные части, вместе с окончательным " (то есть "[^"\\]*(\\.)*"), будут соответствовать: «Некоторый текст» и «Еще больше текста \» », но не будут совпадать:« Еще больше текста об этом » текст \ "".

Чтобы сделать это возможным, нам нужна часть: [^"\\]*(\\.)* повторяется столько раз, сколько необходимо, пока не появится неэкранированная кавычка (или она не достигнет конца строки и неудачная попытка сопоставления). Я обернул эту часть скобками и добавил звездочку. Теперь он соответствует: «Некоторый текст», «Еще больше текста», «Еще больше текста об этом тексте» и «Привет».

В коде C # это будет выглядеть так:

var r = new Regex("\"([^\"\\\\]*(\\\\.)*)*\"");

Кстати, порядок двух основных частей: [^"\\]* и (\\.)* не имеет значения. Вы можете написать:

"([^"\\]*(\\.)*)*"

или

"((\\.)*[^"\\]*)*"

Результат будет таким же.

Теперь нам нужно решить еще одну проблему: \"foo\"-"bar". Текущее выражение будет соответствовать "foo\"-", но мы хотим сопоставить его с "bar". Я не знаю

почему бы избежать экранированных кавычек за пределами цитируемых строк

но мы можем легко это реализовать, добавив в начало следующую часть: (\G|[^\\]). Это говорит о том, что мы хотим, чтобы матч начинался в том месте, где закончился предыдущий матч, или после любого символа, кроме обратной косой черты. Зачем нам нужно \G? Это для следующего случая, например: "a""b".

Обратите внимание, что (\G|[^\\])"([^"\\]*(\\.)*)*" соответствует -"bar" в \"foo\"-"bar". Итак, чтобы получить только "bar", нам нужно указать группу и при необходимости дать ей имя, например «MyGroup». Тогда код C # будет выглядеть так:

[TestMethod]
public void RegExTest()
{
    //Regex compiler: (?:\G|[^\\])(?<MyGroup>"(?:[^"\\]*(?:\.)*)*")
    string pattern = "(?:\\G|[^\\\\])(?<MyGroup>\"(?:[^\"\\\\]*(?:\\\\.)*)*\")";
    var r = new Regex(pattern, RegexOptions.IgnoreCase);

    //Human readable form:       "Some Text"  and  "Even more Text\""     "Even more text about  \"this text\""      "Hello\\"      \"foo\"  - "bar"  "a"   "b" c "d"
    string inputWithQuotedText = "\"Some Text\" and \"Even more Text\\\"\" \"Even more text about \\\"this text\\\"\" \"Hello\\\\\" \\\"foo\\\"-\"bar\" \"a\"\"b\"c\"d\"";
    var quotedList = new List<string>();
    for (Match m = r.Match(inputWithQuotedText); m.Success; m = m.NextMatch())
        quotedList.Add(m.Groups["MyGroup"].Value);

    Assert.AreEqual(8, quotedList.Count);
    Assert.AreEqual("\"Some Text\"", quotedList[0]);
    Assert.AreEqual("\"Even more Text\\\"\"", quotedList[1]);
    Assert.AreEqual("\"Even more text about \\\"this text\\\"\"", quotedList[2]);
    Assert.AreEqual("\"Hello\\\\\"", quotedList[3]);
    Assert.AreEqual("\"bar\"", quotedList[4]);
    Assert.AreEqual("\"a\"", quotedList[5]);
    Assert.AreEqual("\"b\"", quotedList[6]);
    Assert.AreEqual("\"d\"", quotedList[7]);
}
1 голос
/ 18 июля 2013

Простой ответ без использования ? -

"([^\\"]*(\\")*)*\"

или, как дословная строка

@"^""([^\\""]*(\\"")*(\\[^""])*)*"""

Это просто означает:

  • найти первое "
  • найти любое количество символов, которые не являются \ или "
  • найти любое количество экранированных кавычек \"
  • найти любое количество экранированных символов, которые не являются кавычками
  • повторяйте последние три команды, пока не найдете "

Я полагаю, что это работает так же хорошо, как ответ @Alan Moore, но, для меня, легче понять. Он также принимает несогласованные ("несбалансированные") кавычки.

1 голос
/ 09 апреля 2011

Аналогично RegexBuddy, опубликованному @Blankasaurus, RegexMagic также помогает.

1 голос
/ 27 января 2010

Я знаю, что это не самый чистый метод, но на вашем примере я проверю символ перед ", чтобы увидеть, является ли он \. Если это так, я бы проигнорировал цитату.

0 голосов
/ 27 января 2010

Любой шанс, который вам нужно сделать: \"[^\"\\\\]*(?:\\.[^\"\\\\]*)*\"

...