web-dev-qa-db-ja.com

正規表現を使用してC#でエスケープされた引用符で引用符付き文字列を見つける

引用されたテキストをすべて1行で見つけようとしています。

例:

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

どうすれば修正できますか?

40
Joshua Lowry

そこには、フリーデルの「展開されたループ」テクニックの例がありますが、それを文字列リテラルとして表現する方法について混乱があるようです。以下は、正規表現コンパイラにどのように見えるかです。

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

最初の"[^"\\]*は、引用符またはバックスラッシュ以外の文字がゼロ個以上続く引用符に一致します。その部分だけで、最後の"とともに、"this"""などのエスケープシーケンスが埋め込まれていない単純な引用符付き文字列に一致します。

doesがバックスラッシュに遭遇すると、\\.はバックスラッシュとそれに続くものを消費し、[^"\\]*(再び)はすべてを消費します次のバックスラッシュまたは引用符まで。その部分は、エスケープされていない引用符が現れるまで(または文字列の最後に到達して一致が失敗するまで)必要な回数繰り返されます。

これは"foo\"-\"foo\"-"bar"と一致することに注意してください。これは正規表現の欠陥を明らかにするように見えるかもしれませんが、そうではありません。無効なのはinputです。目標は、必要に応じて他のテキストに埋め込まれた引用符で囲まれた文字列を一致させることでした。引用符付き文字列のエスケープされた引用符outsideそれを本当にサポートする必要がある場合は、はるかに複雑な問題が発生し、非常に異なるアプローチが必要になります。

前述のとおり、上記は正規表現が正規表現コンパイラにどのように見えるかです。ただし、文字列リテラルの形式で記述しているため、特定の文字、つまりバックスラッシュと引用符を特別に扱う傾向があります。幸いなことに、C#の逐語的な文字列により、バックスラッシュを二重にエスケープする手間が省けます。各引用符を別の引用符でエスケープする必要があります。

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

したがって、ルールは、C#コンパイラの二重引用符と正規表現コンパイラの二重バックスラッシュです。この特定の正規表現は、両端に3つの引用符が付いているため、少し厄介に見えるかもしれませんが、代替案を検討してください。

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

Javaでは、alwaysそれらをそのように記述する必要があります。 :-(

79
Alan Moore

文字列をキャプチャするための正規表現(\(文字エスケープの場合)、. 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の正規表現エンジンに基づいています。

12
Ricardo Nolde
"(\\"|\\\\|[^"\\])*"

動作するはずです。エスケープされた引用符、エスケープされた円記号、または引用符または円記号以外の文字のいずれかに一致します。繰り返す。

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

どちらかが一致しない場合は、引用符またはバックスラッシュ文字以外のすべてが許可されます。繰り返します。

最後に、閉じ引用符と一致します。

4
Tim Pietzcker

RegexBuddy を取得することをお勧めします。テストセットのすべてが一致することを確認するまで、試してみます。

あなたの問題に関しては、私は2つではなく4つの/を試します:

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

正規表現

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

エスケープされた引用符で始まるテキストも処理します。

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

これが最もクリーンな方法ではないことはわかっていますが、あなたの例では、"の前の文字をチェックして、\かどうかを確認します。もしそうなら、私は引用を無視します。

1
Krill

@Blankasaurusが投稿したRegexBuddyと同様に、 RegexMagic も役立ちます。

1
Emre

そうですね、Alan Mooreの答えは良いですが、もっとコンパクトにするために少し修正します。正規表現コンパイラの場合:

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

アラン・ムーアの表現と比較してください:

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

説明はアラン・ムーアの説明と非常に似ています。

最初の部分_"_は引用符と一致します。

2番目の部分_[^"\\]*_は、引用符またはバックスラッシュ以外の任意の文字の0個以上と一致します。

そして最後の部分_(\\.)*_はバックスラッシュとそれに続く単一の文字にマッチします。このグループはオプションであると言って、*に注意してください。

説明されている部分は、最後の_"_(つまり"[^"\\]*(\\.)*")とともに一致します: "Some Text"および "Even more Text \" "、しかし一致しません:" Even more text about 「このテキスト」。

それを可能にするために、次の部分が必要です:[^"\\]*(\\.)*は、エスケープされていない引用符が現れるまで(または文字列の最後に到達して、一致の試行が失敗するまで)必要な回数繰り返されます。そこで、その部分を括弧で囲み、アスタリスクを追加しました。これで、「Some Text」、「Even more Text \」、「Even more text about\"this text \"」および「Hello \\」に一致します。

C#コードでは、次のようになります。

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

ところで、2つの主要な部分の順序:_[^"\\]*_と_(\\.)*_は関係ありません。あなたは書ける:

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

または

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

結果は同じになります。

次に、別の問題を解決する必要があります:_\"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
Alex

?を使用しない単純な答えは

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

または、逐語的な文字列として

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

それはただ意味します:

  • 最初の"を見つける
  • \または"ではない任意の数の文字を検索します
  • エスケープされた引用符をいくつでも見つける\"
  • 引用符ではない任意の数のエスケープされた文字を見つける
  • "が見つかるまで最後の3つのコマンドを繰り返します

@Alan Mooreの答えと同じように機能すると思いますが、私にとっては理解しやすいです。不一致(「不均衡」)の引用符も受け入れます。

1

あなたがする必要がある可能性:\"[^\"\\\\]*(?:\\.[^\"\\\\]*)*\"

0
Fried Hoeben