正規表現は不正な角括弧を扱うことができません

これまでのスマートフォンのおかげで、カスタムBBCodeスタイルのタグをテキストブロックに変換するのに役立つこの驚くべき再帰的な正規表現が得られました。

/// 
/// Static class containing common regular expression strings.
/// 
public static class RegularExpressions
{
    /// 
    /// Expression to find all root-level BBCode tags. Use this expression recursively to obtain nested tags.
    /// 
    public static string BBCodeTags
    {
        get
        {
            return @"
                    (?>
                      \[ (?[^][/=\s]+) \s*
                      (?: = \s* (?[^][]*) \s*)?
                      ]
                    )

                    (?
                      (?>
                        \[(?[^][/=\s]+)[^][]*]
                        |
                        \[/(?<-innertag>\k)]
                        |
                        [^][]+
                      )*
                      (?(innertag)(?!))
                    )

                    \[/\k]
                    ";
        }
    }
}

この正規表現は美しく動作し、すべてのタグを再帰的に照合します。このような:

[code]  
    some code  
    [b]some text [url=http://www.google.com]some link[/url][/b]  
[/code]

正規表現は、私が欲しいものを正確に行い、 [code] タグと一致します。タグ、オプション値、およびコンテンツの3つのグループに分割されます。タグはタグ名(この場合は「コード」)です。オプションの値は、equals( = )記号の後にある値です(存在する場合)。また、コンテンツは開始タグと終了タグの間のすべてです。

正規表現は、ネストされたタグを一致させるために再帰的に使用できます。だから、 [code] でマッチングした後、もう一度コンテンツグループに対して実行すると [b] タグと一致します。次のコンテンツグループで再度実行すると、 [url] タグと一致します。

そのすべては素晴らしくて美味しいですが、それは1つの問題で困っています。不正な角括弧は扱えません。

[code]This is a successful match.[/code]

[code]This is an [ unsuccessful match.[/code]

[code]This is also an [unsuccessful] match.[/code]

私は本当に正規表現を吸うが、もし誰かが、この正規表現を調整して、不正な括弧(開始タグを構成していないか、一致する終了タグを持たないかっこ)を正しく無視する方法を知っていれば、 、私は非常に感謝するだろう:D

前もって感謝します!

編集

この表現を使用する方法を知りたい場合は、ようこそをご覧ください。

3
stackoverflow.com/questions/7018321/… そこに行く。これは、この正規表現が元々提供されていた問題です:)
追加された 著者 Chev,
@ジョン、私は人々が私が反対していると思う理由を理解していない。私はそうではありません。私は選択肢があります。しかし、私は手作業のパーサを書こうとしました、そして、それはかなり早く私の頭の上に行きました。あなたが良い選択をしている場合は、それを投稿して自由に感じてください。それまでは、「合格」を歓迎します。
追加された 著者 Chev,
残念ながら、これは誰かが私に提供してくれました。私はその質問を捜し求めます。たぶんそこに詳細があるかもしれません。
追加された 著者 Chev,
私は本当にこの質問のために-1に値するとは思わない。あなたが正規表現を好んでいないからといって、私の質問に何か問題があったというわけではありません。私は非常にはっきりしていて、この質問をまとめるのに時間を割いた。これは、あなたが好きではない言語でコードを書くので、質問をする親指と同じです。私の質問に何か間違っていると思われる場合は、コメントして、それが何であるか教えてください。
追加された 著者 Chev,
マッチを破る不正な角括弧以外は完璧に動作します。
追加された 著者 Chev,
@MiguelAngelo私は過去にこれを手作業で解析しようとしましたが、完全に渦巻いて制御不能になり、複雑さが増したようです。誰かが私に、あなたが示唆していることの反対をして、この表現を使用するように言いました。おそらく、正しい方向に私を指差してくれるサンプルを見せることができれば。
追加された 著者 Chev,
あなたの好みの方法を示すコードサンプルがあれば、私はそれを公開しています。これが私がこの作業を簡単に手に入れることができた唯一の方法であり、これまでのところうまくいきました。だから、もし私が私にもっと良い解決策を見せなければ、私はそれに固執するでしょう。
追加された 著者 Chev,
ところで、HTMLやBBCodeのようなネストされた文法を解析するために、この構造が.Net Regex Implementation に追加されたので、私はあなたを助けることができるかもしれないと思うが、 (私のエミュレータはそれをサポートしていません) - 答えが返って来るまで(または私がしなければ)幸運を祈っています:私は、この構造のテストとデバッグを扱うように設定されたプラットフォームを持っていません!
追加された 著者 Code Jockey,
@AlexFord正規表現はこの仕事のためのツールではない部分へようこそ。たとえあなたがこれを修正しても、あなたはどのように[[[]]]]などを扱うでしょうか?申し訳ありませんが動作しません。代わりにレクサー/パーサーコンボを使用してください。パーフェクトはあらゆる可能な状況を説明するものであり、これはそうではありません。
追加された 著者 FailedDev,
@AlexFord:その答えを見ると、恐ろしいものが見えます。完全に機能すると思われる魔法のブラックボックスですが、理解したり説明する能力はありません。私がこの暗い贈り物を受け入れるべきかどうかは、それがほしいと思うように振る舞いを始める前に時間の問題である。そして、それは正規表現の神秘的な力に比べて貧しい私です。 合格
追加された 著者 Jon,
@AlexFord:維持不能、バグではない。
追加された 著者 Jon,
@MiguelAngelo - デバッグは私の意見では最も重要な要件ではなく、単体テストです。正規表現エンジンはブラックボックスで、使用するライブラリとよく似ています。
追加された 著者 Kobi,
これは、維持できないコードです...それを取り除き、代わりに命令的なコードロジックを使用してください。 =)
追加された 著者 Miguel Angelo,
私はそれが動作することを知っている...しかし、正規表現が完了したら、神だけが何であるか知っている! Regexesはメンテナンス可能ではなく、一度やってしまうことです。あなたがそれを変更する必要がある場合は、それをもう一度理解するのは地獄です。このコードを維持することを計画している場合は、ステップバイステップデバッグ可能というものに置き換えて、必要に応じて将来容易に微調整することができます。
追加された 著者 Miguel Angelo,

3 答え

私はデバッグ可能な、開発者に優しい方法であなたの文字列を解析できるプログラムを作った。それらの正規表現のような小さなコードではありませんが、それは良い面を持っています:あなたはそれをデバッグし、必要に応じて微調整することができます。

実装は降下再帰パーサーですが、何らかのコンテキストデータが必要な場合は、それはすべて ParseContext クラスの中にあります。

それはかなり長いですが、私は正規表現ベースのソリューションよりも優れていると考えています。

これをテストするには、コンソールアプリケーションを作成し、 Program.cs 内のすべてのコードを次のコードに置き換えます。

using System.Collections.Generic;
namespace q7922337
{
    static class Program
    {
        static void Main(string[] args)
        {
            var result1 = Match.ParseList("[code]This is a successful match.[/code]");
            var result2 = Match.ParseList("[code]This is an [ unsuccessful match.[/code]");
            var result3 = Match.ParseList("[code]This is also an [unsuccessful] match.[/code]");
            var result4 = Match.ParseList(@"
                        [code]  
                            some code  
                            [b]some text [url=http://www.google.com]some link[/url][/b]  
                        [/code]");
        }
        class ParseContext
        {
            public string Source { get; set; }
            public int Position { get; set; }
        }
        abstract class Match
        {
            public override string ToString()
            {
                return this.Text;
            }
            public string Source { get; set; }
            public int Start { get; set; }
            public int Length { get; set; }
            public string Text { get { return this.Source.Substring(this.Start, this.Length); } }
            protected abstract bool ParseInternal(ParseContext context);
            public bool Parse(ParseContext context)
            {
                var result = this.ParseInternal(context);
                this.Length = context.Position - this.Start;
                return result;
            }
            public bool MarkBeginAndParse(ParseContext context)
            {
                this.Start = context.Position;
                var result = this.ParseInternal(context);
                this.Length = context.Position - this.Start;
                return result;
            }
            public static List ParseList(string source)
                where T : Match, new()
            {
                var context = new ParseContext
                {
                    Position = 0,
                    Source = source
                };
                var result = new List();
                while (true)
                {
                    var item = new T { Source = source, Start = context.Position };
                    if (!item.Parse(context))
                        break;
                    result.Add(item);
                }
                return result;
            }
            public static T ParseSingle(string source)
                where T : Match, new()
            {
                var context = new ParseContext
                {
                    Position = 0,
                    Source = source
                };
                var result = new T { Source = source, Start = context.Position };
                if (result.Parse(context))
                    return result;
                return null;
            }
            protected List ReadList(ParseContext context)
                where T : Match, new()
            {
                var result = new List();
                while (true)
                {
                    var item = new T { Source = this.Source, Start = context.Position };
                    if (!item.Parse(context))
                        break;
                    result.Add(item);
                }
                return result;
            }
            protected T ReadSingle(ParseContext context)
                where T : Match, new()
            {
                var result = new T { Source = this.Source, Start = context.Position };
                if (result.Parse(context))
                    return result;
                return null;
            }
            protected int ReadSpaces(ParseContext context)
            {
                int startPos = context.Position;
                int cnt = 0;
                while (true)
                {
                    if (startPos + cnt >= context.Source.Length)
                        break;
                    if (!char.IsWhiteSpace(context.Source[context.Position + cnt]))
                        break;
                    cnt++;
                }
                context.Position = startPos + cnt;
                return cnt;
            }
            protected bool ReadChar(ParseContext context, char p)
            {
                int startPos = context.Position;
                if (startPos >= context.Source.Length)
                    return false;
                if (context.Source[startPos] == p)
                {
                    context.Position = startPos + 1;
                    return true;
                }
                return false;
            }
        }
        class Tag : Match
        {
            protected override bool ParseInternal(ParseContext context)
            {
                int startPos = context.Position;
                if (!this.ReadChar(context, '['))
                    return false;
                this.ReadSpaces(context);
                if (this.ReadChar(context, '/'))
                    this.IsEndTag = true;
                this.ReadSpaces(context);
                var validName = this.ReadValidName(context);
                if (validName != null)
                    this.Name = validName;
                this.ReadSpaces(context);
                if (this.ReadChar(context, ']'))
                    return true;
                context.Position = startPos;
                return false;
            }
            protected string ReadValidName(ParseContext context)
            {
                int startPos = context.Position;
                int endPos = startPos;
                while (char.IsLetter(context.Source[endPos]))
                    endPos++;
                if (endPos == startPos) return null;
                context.Position = endPos;
                return context.Source.Substring(startPos, endPos - startPos);
            }
            public bool IsEndTag { get; set; }
            public string Name { get; set; }
        }
        class TagsGroup : Match
        {
            public TagsGroup()
            {
            }
            protected TagsGroup(Tag openTag)
            {
                this.Start = openTag.Start;
                this.Source = openTag.Source;
                this.OpenTag = openTag;
            }
            protected override bool ParseInternal(ParseContext context)
            {
                var startPos = context.Position;
                if (this.OpenTag == null)
                {
                    this.ReadSpaces(context);
                    this.OpenTag = this.ReadSingle(context);
                }
                if (this.OpenTag != null)
                {
                    int textStart = context.Position;
                    int textLength = 0;
                    while (true)
                    {
                        Tag tag = new Tag { Source = this.Source, Start = context.Position };
                        while (!tag.MarkBeginAndParse(context))
                        {
                            if (context.Position >= context.Source.Length)
                            {
                                context.Position = startPos;
                                return false;
                            }
                            context.Position++;
                            textLength++;
                        }
                        if (!tag.IsEndTag)
                        {
                            var tagGrpStart = context.Position;
                            var tagGrup = new TagsGroup(tag);
                            if (tagGrup.Parse(context))
                            {
                                if (textLength > 0)
                                {
                                    if (this.Contents == null) this.Contents = new List();
                                    this.Contents.Add(new Text { Source = this.Source, Start = textStart, Length = textLength });
                                    textStart = context.Position;
                                    textLength = 0;
                                }
                                this.Contents.Add(tagGrup);
                            }
                            else
                            {
                                textLength += tag.Length;
                            }
                        }
                        else
                        {
                            if (tag.Name == this.OpenTag.Name)
                            {
                                if (textLength > 0)
                                {
                                    if (this.Contents == null) this.Contents = new List();
                                    this.Contents.Add(new Text { Source = this.Source, Start = textStart, Length = textLength });
                                    textStart = context.Position;
                                    textLength = 0;
                                }
                                this.CloseTag = tag;
                                return true;
                            }
                            else
                            {
                                textLength += tag.Length;
                            }
                        }
                    }
                }
                context.Position = startPos;
                return false;
            }
            public Tag OpenTag { get; set; }
            public Tag CloseTag { get; set; }
            public List Contents { get; set; }
        }
        class Text : Match
        {
            protected override bool ParseInternal(ParseContext context)
            {
                return true;
            }
        }
    }
}

このコードを使用していると、パーサがあいまいになって最適化が必要なことが判明した場合は、ParseContextで辞書を使用してみてください。詳細は http://ja.wikipedia.org/wiki/Top-強い>。私はそれが非常に興味深いと思う。

私は昼休みを見て、理解できるかどうかを見ていきます。もしそうでないなら、私はおそらくあなたをもう一度盗んでいるでしょう!あなたはすばらしい助けをしてきました。
追加された 著者 Chev,
これは部分的に機能しています。 [url = http://www.google.com]リンク[/ url] のように等号の後に「オプションの値」を扱うようには思われませんが、あなたは私のためにすべてをする(あなたがそう傾けられていない限り:P)。私がしばらく時間がたつと、デバッグを試み、ここで何が起こっているのかを把握して、オプションの値で動作するように修正することができます。すべての努力のために道にもう一度感謝します。私があなたのルートに行くなら、私は喜んでマークを受け入れます:)
追加された 著者 Chev,
こんにちは、私はそれがオプションの値を解析するようにしていない。 = \ ...しかし、このルールをコードに含めることは非常に簡単です。コードに関するご質問は、できるだけ早くお手伝いします。
追加された 著者 Miguel Angelo,

The first change is pretty simple - you can get it by changing [^][]+, which is responsible for matching the free text, to .. This seems a little crazy, perhaps, but it's actually safe, because you are using a possessive group (?> ), so all the valid tags will be matched by the first alternation - \[(?[^][/=\s]+)[^][]*] - and cannot backtrack and break the tags.
(Remember to enable the Singleline flag, so . matches newlines)

2番目の要件 [unsuccessful] は、あなたの目標に反しているようです。始めからのアイデアは、閉じられていないタグと一致する です。クローズされていないタグを許可すると、 \ [(。*?)\]。*?[/ \ 1] のフォームのすべての一致が有効になります。良くない。最高でも、一致することが許可されていないタグをホワイトリストに登録することができます。

両方の変更の例:

(?>
\[ (?[^][/=\s]+) \s*
(?: = \s* (?[^][]*) \s*)?
\]
)
  (?
    (?>
       \[(?:unsuccessful)\]  # self closing
       |
       \[(?[^][/=\s]+)[^][]*]
       |
       \[/(?<-innertag>\k)]
       |
       .
    )*
    (?(innertag)(?!))
  )
\[/\k\]

Regex Heroの作業例

1
追加された
"第二の要件、[失敗]はあなたの目標に反しているように思えます。最初からのアイデアは、これらの非クローズドタグに一致させることではありません。私は[[失敗]は[/失敗した]終了タグを持たないことを認識し、[コード]タグのプレーンな内容として[不成功]を認識して、外部タグ([コード]起こっていることは、[失敗]がコンテンツの一部であるときに[コード]タグが一致しないことです。
追加された 著者 Chev,
@Alex - そのような解析では通常、文書全体の解析が必要です。私はそれについて少し考えます...
追加された 著者 Kobi,
@AlexFord - もう一つの答えを追加しました。あまり説明しなかった、私は恐れている。幸運にも:)。 (ああ、パターンを書くのに半分の時間がかかり、答えを書くのに半分かかりました...)
追加された 著者 Kobi,

OK。別の試みがあります。これはもう少し複雑です。
この考え方は、テキスト全体をstartからextにマッチさせ、それを単一の Match に解析することです。ほとんどの場合、.Net Balancing Groupsでは、キャプチャを細かく調整することができます。すべてのポジションを覚えておき、必要な方法でキャプチャします。
私が思い描いたパターンは次のとおりです。

\A
(?)
(?:
    # Open tag
    (?)          # capture the content between tags
    (?)                      # Keep the starting postion of the tag
    (?>\[(?[^][/=\s]+)[^\]\[]*\])     # opening tag
    (?)                  # start another content capture
    |
    # Close tag
    (?)          # capture the content in the tag
    \[/\k\](?)  # closing tag, keep the content in the  group
    (?<-TagName>)
    (?)                  # start another content capture
    |
    .           # just match anything. The tags are first, so it should match
                # a few if it can. (?(TagName)(?!)) keeps this in line, so
                # unmatched tags will not mess with the resul
)*
(?)          # capture the content after the last tag
\Z
(?(TagName)(?!))

Remember - the balancing group (?) captures into A all text since B was last captured (and pops that position from B's stack).

これで文字列を解析できます:

Match match = Regex.Match(sample, pattern, RegexOptions.Singleline |
                                           RegexOptions.IgnorePatternWhitespace);

あなたの興味深いデータはすべてのタグ(それらのうちのいくつかは他のものに含まれています)と match.Groups ["Content"]を含む match.Groups ["Tag"] Captures .Captures は、タグの内容とタグ間の内容を含んでいます。たとえば、すべてのブランクがない場合は、以下が含まれます。

  • いくつかのコード
  • テキスト
  • これも成功した一致です。
  • これも[失敗した一致]です。
  • これも[失敗した]一致です。

これは完全に解析されたドキュメントにかなり近いですが、ドキュメントの正確な順序と構造を把握するためにインデックスと長さを使用する必要があります(ただし、すべてのキャプチャを並べ替えるよりも複雑ではありません)

この時点で、私は他人が言ったことを述べるでしょう - パーザを書くのは良い時期かもしれませんが、このパターンはあまりよくありません...

0
追加された
@コビー私はあなたの答えは-1に値するとは思わない。それはおそらく理想的な解決策ではありませんが、私が尋ねた質問に直接答えます。私は答えが出ている限り、それはよく考えられていて助けになったと言うでしょう。
追加された 著者 Chev,
-1:私はそれが動作すると思うが、それは私が叫んで逃げたいと思う
追加された 著者 John Saunders,
ところで、2つの答えがありますか?
追加された 著者 John Saunders,
@ジョン - それは公正で、-1を扱うことは、あなたが:)を記述している感覚に比べて軽微な不便さです。私は2つの答えを残すつもりだった、私は彼らが十分に異なると思う。
追加された 著者 Kobi,