Теги форума.Каков наилучший способ их реализации? - PullRequest
1 голос
/ 24 февраля 2011

Я создаю форум и хочу использовать теги в стиле форума, чтобы позволить пользователям ограниченно форматировать свои сообщения.В настоящее время я использую Regex для этого.По этому вопросу: Как использовать регулярные выражения C # для эмуляции тегов форума

Проблема в том, что регулярное выражение не различает вложенные теги.Вот пример того, как я реализовал этот метод:

    public static string MyExtensionMethod(this string text)
    {
         return TransformTags(text);
    }

    private static string TransformTags(string input)
    {
        string regex = @"\[([^=]+)[=\x22']*(\S*?)['\x22]*\](.+?)\[/(\1)\]";
        MatchCollection matches = new Regex(regex).Matches(input);
        for (int i = 0; i < matches.Count; i++)
        {
            var tag = matches[i].Groups[1].Value;
            var optionalValue = matches[i].Groups[2].Value;
            var content = matches[i].Groups[3].Value;

            if (Regex.IsMatch(content, regex))
            {
                content = TransformTags(content);
            }

            content = HandleTags(content, optionalValue, tag);

            input = input.Replace(matches[i].Groups[0].Value, content);
        }

        return input;
    }

    private static string HandleTags(string content, string optionalValue, string tag)
    {
        switch (tag.ToLower())
        {
            case "quote":
                return string.Format("<div class='quote'>{0}</div>", content);
            default:
                return string.Empty;
        }
    }

Теперь, если я отправляю что-то вроде [quote] This user posted [quote] blah [/quote] [/quote], он не может правильно определить вложенную цитату.Вместо этого он берет первый открывающий тег цитаты и помещает его в первый закрывающий тег цитаты.

Ребята, вы рекомендуете какие-либо решения?Можно ли изменить регулярное выражение для захвата вложенных тегов?Может быть, я не должен использовать регулярные выражения для этого?Любая помощь приветствуется.

Ответы [ 2 ]

2 голосов
/ 25 февраля 2011

Хотя использование «только» регулярных выражений возможно при использовании балансирующих групп, это довольно тяжелое вуду и по своей сути «хрупкое».Я предлагаю использовать регулярные выражения для поиска тегов открытия / закрытия (без попытки связать закрытие с открытием), пометить и собрать их в коллекцию (вероятно, в стек), а затем проанализировать их «вручную» (с помощью foreach).Таким образом, вы получаете лучшее из обоих: поиск тегов по регулярным выражениям и обработка их (и неправильно написанных) вручную.

class TagMatch
{
    public string Tag { get; set; }
    public Capture Capture { get; set; }
    public readonly List<string> Substrings = new List<string>();
}

static void Main(string[] args)
{
    var rx = new Regex(@"(?<OPEN>\[[A-Za-z]+?\])|(?<CLOSE>\[/[A-Za-z]+?\])|(?<TEXT>[^\[]+|\[)");
    var str = "Lorem [AA]ipsum [BB]dolor sit [/BB]amet, [ consectetur ][/AA]adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
    var matches = rx.Matches(str);

    var recurse = new Stack<TagMatch>();
    recurse.Push(new TagMatch { Tag = String.Empty });

    foreach (Match match in matches)
    {
        var text = match.Groups["TEXT"];

        TagMatch last;

        if (text.Success)
        {
            last = recurse.Peek();
            last.Substrings.Add(text.Value);
            continue;
        }

        var open = match.Groups["OPEN"];

        string tag;

        if (open.Success)
        {
            tag = open.Value.Substring(1, open.Value.Length - 2);
            recurse.Push(new TagMatch { Tag = tag, Capture = open.Captures[0] });
            continue;
        }

        var close = match.Groups["CLOSE"];

        tag = close.Value.Substring(2, close.Value.Length - 3);

        last = recurse.Peek();

        if (last.Tag == tag)
        {
            recurse.Pop();

            var lastLast = recurse.Peek();
            lastLast.Substrings.Add("**" + last.Tag + "**");
            lastLast.Substrings.AddRange(last.Substrings);
            lastLast.Substrings.Add("**/" + last.Tag + "**");
        }
        else
        {
            throw new Exception();
        }
    }

    if (recurse.Count != 1)
    {
        throw new Exception();
    }

    var sb = new StringBuilder();
    foreach (var str2 in recurse.Pop().Substrings)
    {
        sb.Append(str2);
    }

    var str3 = sb.ToString();
}

Это пример.Он чувствителен к регистру (но эту проблему легко решить).Он не обрабатывает «непарные» теги, потому что есть разные способы их обработки.Там, где вы найдете «бросить новое исключение», вам придется добавить свою обработку.Понятно, что это не «выпадающее» решение.Это всего лишь пример.По этой логике я не буду отвечать на вопросы типа «компилятор говорит мне, что мне нужно пространство имен» или «компилятор не может найти Regex».НО я буду более чем рад ответить на «сложные» вопросы, например, как можно сопоставить непарные теги или как добавить поддержку [AAA=bbb] тегов

(2nd BIG EDIT)

Бахахахах!Я ДЕЙСТВИТЕЛЬНО знал, что группировка была способом сделать это!

// Some classes

class BaseTagMatch {
    public Capture Capture;

    public override string ToString()
    {
        return String.Format("{1}: {2} [{0}]", GetType(), Capture.Index, Capture.Value.ToString());
    }
}

class BeginTag : BaseTagMatch
{
    public int Index;
    public Capture Options;
    public EndTag EndTag;
}

class EndTag : BaseTagMatch {
    public int Index;
    public BeginTag BeginTag;
}

class Text : BaseTagMatch
{
}

class UnmatchedTag : BaseTagMatch
{
}

// The code

var pattern =
    @"(?# line 01) ^" +
    @"(?# line 02) (" +
    // Non [ Text
    @"(?# line 03)   (?>(?<TEXT>[^\[]+))" +
    @"(?# line 04)   |" +
    // Immediately closed tag [a/]
    @"(?# line 05)   (?>\[  (?<TAG>  [A-Z]+  )  \x20*  =?  \x20*  (?<TAG_OPTION>(  (?<=  =  \x20*)  (  (?!  \x20*  /\])  [^\[\]\r\n]  )*  )?  )  (?<BEGIN_INNER_TEXT>)(?<END_INNER_TEXT>)  \x20*  /\]  )" +
    @"(?# line 06)   |" +
    // Matched open tag [a]
    @"(?# line 07)   \[  (?<TAG>  (?<OPEN>  [A-Z]+  )  )  \x20* =?  \x20* (?<TAG_OPTION>(  (?<=  =  \x20*)  (  (?!  \x20*  \])  [^\[\]\r\n]  )*  )?  )  \x20*  \]  (?<BEGIN_INNER_TEXT>)" +
    @"(?# line 08)   |" +
    // Matched close tag [/a]
    @"(?# line 09)   (?>(?<END_INNER_TEXT>)  \[/  \k<OPEN>  \x20*  \]  (?<-OPEN>))" +
    @"(?# line 10)   |" +
    // Unmatched open tag [a]
    @"(?# line 11)   (?>(?<UNMATCHED_TAG>  \[  [A-Z]+  \x20* =?  \x20* (  (?<=  =  \x20*)  (  (?!  \x20*  \])  [^\[\]\r\n]  )*  )?  \x20*  \]  )  )" +
    @"(?# line 12)   |" +
    // Unmatched close tag [/a]
    @"(?# line 13)   (?>(?<UNMATCHED_TAG>  \[/  [A-Z]+  \x20*  \]  )  )" +
    @"(?# line 14)   |" +
    // Single [ of Text (unmatched by other patterns)
    @"(?# line 15)   (?>(?<TEXT>\[))" +
    @"(?# line 16) )*" +
    @"(?# line 17) (?(OPEN)(?!))" +
    @"(?# line 18) $";

var rx = new Regex(pattern, RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase);

var match = rx.Match("[div=c:max max]asdf[p = 1   ] a [p=2] [b  =  p/pp   /] [q/] \n[a]sd [/z]  [ [/p]f[/p]asdffds[/DIV] [p][/p]");

////var tags = match.Groups["TAG"].Captures.OfType<Capture>().ToArray();
////var tagoptions = match.Groups["TAG_OPTION"].Captures.OfType<Capture>().ToArray();
////var begininnertext = match.Groups["BEGIN_INNER_TEXT"].Captures.OfType<Capture>().ToArray();
////var endinnertext = match.Groups["END_INNER_TEXT"].Captures.OfType<Capture>().ToArray();
////var text = match.Groups["TEXT"].Captures.OfType<Capture>().ToArray();
////var unmatchedtag = match.Groups["UNMATCHED_TAG"].Captures.OfType<Capture>().ToArray();

var tags = match.Groups["TAG"].Captures.OfType<Capture>().Select((p, ix) => new BeginTag { Capture = p, Index = ix, Options = match.Groups["TAG_OPTION"].Captures[ix] }).ToList();

Func<Capture, int, EndTag> func = (p, ix) =>
{
    var temp = new EndTag { Capture = p, Index = ix, BeginTag = tags[ix] };
    tags[ix].EndTag = temp;
    return temp;
};

var endTags = match.Groups["END_INNER_TEXT"].Captures.OfType<Capture>().Select((p, ix) => func(p, ix));
var text = match.Groups["TEXT"].Captures.OfType<Capture>().Select((p, ix) => new Text { Capture = p });
var unmatchedTags = match.Groups["UNMATCHED_TAG"].Captures.OfType<Capture>().Select((p, ix) => new UnmatchedTag { Capture = p });

// Here you have all the tags and the inner text neatly ordered and ready to be recomposed in a StringBuilder.
var allTags = tags.Cast<BaseTagMatch>().Union(endTags).Union(text).Union(unmatchedTags).ToList();
allTags.Sort((p, q) => p.Capture.Index - q.Capture.Index);

foreach (var el in allTags)
{
    var type = el.GetType();

    if (type == typeof(BeginTag))
    {

    }
    else if (type == typeof(EndTag))
    {

    }
    else if (type == typeof(UnmatchedTag))
    {

    }
    else
    {
        // Text
    }
}

Соответствие тегов без учета регистра, игнорирует теги, закрытые неправильно, поддерживает немедленно закрытые теги ([BR/]). И кто-то сказалс Regex это было невозможно ... Bwahahahahah!

TAG, TAGOPTION, BEGIN_INNER_TEXT и END_INNER_TEXT совпадают (они всегда имеют одинаковое количество элементов).TEXT и UNMATCHED_TAG не совпадают!TAG и TAG_OPTION являются авто-объяснительными (оба лишены бесполезных пробелов).Захваты BEGIN_INNER_TEXT и END_INNER_TEXT всегда пусты, но вы можете использовать их свойство Index, чтобы увидеть, где начинаются / заканчиваются теги.UNMATCHED_TAG содержит теги, которые были открыты, но не закрыты, или закрыты, но не опровергнуты.Он не содержит тегов, которые имеют неправильный формат (например, [123]).

В конце я беру TAG, END_INNER_TEXT (чтобы увидеть, где заканчиваются теги), TEXT и UNMATCHED_TAG и отсортировать их по индексу.Затем вы можете взять allTags, поместить его в foreach и для каждого элемента проверить его тип.Легко :-): -)

В качестве небольшой заметки, Regex - RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase.Первые два - чтобы было легче писать и читать, третий - семантический.[A] совпадает с [/a].

Необходимые показания:

http://www.codeproject.com/KB/recipes/Nested_RegEx_explained.aspx http://www.codeproject.com/KB/recipes/RegEx_Balanced_Grouping.aspx

0 голосов
/ 24 февраля 2011

Я не уверен, где регулярное выражение принесет вам пользу.Это было бы очень просто, но вы могли бы просто заменить [quote] на <div class="quote"> и [/ quote] на </div>.То же самое можно сказать и обо всех других тегах в стиле bbcode, которые вы хотите разрешить.

Другими словами, буквально переведите их в html, который вы хотите, чтобы они представляли.

...