Подтвердить что ты не робот

Поиск цитируемых строк с экранированными кавычками в С# с использованием регулярного выражения

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

Пример:

"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.

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

4b9b3361

Ответ 1

У вас есть пример метода Friedl "развернутый цикл", но у вас, похоже, есть путаница в том, как выразить его как строковый литерал. Вот как это должно выглядеть для компилятора регулярных выражений:

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

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

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

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

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

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

Итак, это правило - двойные кавычки для компилятора С# и двойные обратные косые черты для компилятора regex - приятный и легкий. Это конкретное регулярное выражение может выглядеть немного неудобно, с тремя кавычками в обоих концах, но рассмотрим альтернативу:

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

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

Ответ 2

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 Regular Expression Designer, в котором есть хорошая вкладка "Языковые элементы" с быстрым доступом к некоторым основные инструкции. Он основан на .NET regex engine.

Ответ 3

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

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

В С#:

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 \\".

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

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

Ответ 4

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

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

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

Ответ 5

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

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

также обрабатывает текст, который начинается с скрытой цитаты:

\"Some Text\" Some Text "Some Text", and "Some more Text" an""d "Even more text about \"this text\""

Ответ 6

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

Ответ 7

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

Ответ 8

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

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

или, как стенографическая строка

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

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

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

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

Ответ 9

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

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

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

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

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

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

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

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

Детали, описанные вместе с финальным " (т.е. "[^"\\]*(\\.)*"), будут совпадать: "Some Text" и "Even more Text", но не будут совпадать: "Еще больше текста об этом" text\"".

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

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

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

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

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

или

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

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

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

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

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

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

[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]);
}

Ответ 10

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