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

Сложное разделение строк

У меня есть строка вроде следующего:

[Testing.User]|Info:([Testing.Info]|Name:([System.String]|Matt)|Age:([System.Int32]|21))|Description:([System.String]|This is some description)

Вы можете посмотреть на это как на это дерево:

- [Testing.User]
- Info
        - [Testing.Info]
        - Name
                - [System.String]
                - Matt
        - Age
                - [System.Int32]
                - 21
- Description
        - [System.String]
        - This is some description

Как вы можете видеть, это строковая сериализация/представление класса Testing.User

Я хочу иметь возможность сделать split и получить следующие элементы в результирующем массиве:

 [0] = [Testing.User]
 [1] = Info:([Testing.Info]|Name:([System.String]|Matt)|Age:([System.Int32]|21))
 [2] = Description:([System.String]|This is some description)

Я не могу разделить на |, потому что это приведет к:

 [0] = [Testing.User]
 [1] = Info:([Testing.Info]
 [2] = Name:([System.String]
 [3] = Matt)
 [4] = Age:([System.Int32]
 [5] = 21))
 [6] = Description:([System.String]
 [7] = This is some description)

Как я могу получить ожидаемый результат?

Я не очень хорошо разбираюсь в регулярных выражениях, но я знаю, что это очень возможное решение для этого случая.

4b9b3361

Ответ 1

Уже есть более чем достаточно ответов на разделение, так что вот еще один подход. Если ваш ввод представляет древовидную структуру, почему бы не разобрать его на дерево? Следующий код был автоматически переведен с VB.NET, но он должен работать, насколько я его протестировал.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Treeparse
{
    class Program
    {
        static void Main(string[] args)
        {
            var input = "[Testing.User]|Info:([Testing.Info]|Name:([System.String]|Matt)|Age:([System.Int32]|21))|Description:([System.String]|This is some description)";
            var t = StringTree.Parse(input);
            Console.WriteLine(t.ToString());
            Console.ReadKey();
        }
    }

    public class StringTree
    {
        //Branching constants
        const string BranchOff = "(";
        const string BranchBack = ")";
        const string NextTwig = "|";

        //Content of this twig
        public string Text;
        //List of Sub-Twigs
        public List<StringTree> Twigs;
        [System.Diagnostics.DebuggerStepThrough()]
        public StringTree()
        {
            Text = "";
            Twigs = new List<StringTree>();
        }

        private static void ParseRecursive(StringTree Tree, string InputStr, ref int Position)
        {
            do {
                StringTree NewTwig = new StringTree();
                do {
                    NewTwig.Text = NewTwig.Text + InputStr[Position];
                    Position += 1;
                } while (!(Position == InputStr.Length || (new String[] { BranchBack, BranchOff, NextTwig }.ToList().Contains(InputStr[Position].ToString()))));
                Tree.Twigs.Add(NewTwig);
                if (Position < InputStr.Length && InputStr[Position].ToString() == BranchOff) { Position += 1; ParseRecursive(NewTwig, InputStr, ref Position); Position += 1; }
                if (Position < InputStr.Length && InputStr[Position].ToString() == BranchBack)
                    break; // TODO: might not be correct. Was : Exit Do
                Position += 1;
            } while (!(Position >= InputStr.Length || InputStr[Position].ToString() == BranchBack));
        }

        /// <summary>
        /// Call this to parse the input into a StringTree objects using recursion
        /// </summary>
        public static StringTree Parse(string Input)
        {
            StringTree t = new StringTree();
            t.Text = "Root";
            int Start = 0;
            ParseRecursive(t, Input, ref Start);
            return t;
        }

        private void ToStringRecursive(ref StringBuilder sb, StringTree tree, int Level)
        {
            for (int i = 1; i <= Level; i++)
            {
                sb.Append("   ");
            }
            sb.AppendLine(tree.Text);
            int NextLevel = Level + 1;
            foreach (StringTree NextTree in tree.Twigs)
            {
                ToStringRecursive(ref sb, NextTree, NextLevel);
            }
        }

        public override string ToString()
        {
            var sb = new System.Text.StringBuilder();
            ToStringRecursive(ref sb, this, 0);
            return sb.ToString();
        }

    }
}

Результат (нажмите):

Вы получаете значения каждого node со связанными над ним знаками в древовидной структуре, и тогда вы можете делать с ним все, что захотите, например, легко показать структуру в элементе управления TreeView:

enter image description here

Ответ 2

Использование regex lookahead

Вы можете использовать регулярное выражение следующим образом:

(\[.*?])|(\w+:.*?)\|(?=Description:)|(Description:.*)

Рабочая демонстрация

Идея этого регулярного выражения состоит в том, чтобы захватить в группы 1, 2 и 3 то, что вы хотите.

Вы можете легко увидеть эту диаграмму:

Regular expression visualization

Информация о матче

MATCH 1
1.  [0-14]   `[Testing.User]`
MATCH 2
2.  [15-88]  `Info:([Testing.Info]|Name:([System.String]|Matt)|Age:([System.Int32]|21))`
MATCH 3
3.  [89-143] `Description:([System.String]|This is some description)`

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

С другой стороны, если вы не любите над regex выше, вы можете использовать другое, подобное этому:

(\[.*?])\|(.*)\|(Description:.*)

Regular expression visualization

Рабочая демонстрация

Или даже заставляя хотя бы один символ:

(\[.+?])\|(.+)\|(Description:.+)

Regular expression visualization

Ответ 3

Предполагая, что ваши группы могут быть помечены как

  • [Anything.Anything]
  • Все: ReallyAnything (только буквы и цифры: тогда любое количество символов) после первого канала
  • Все что угодно: ReallyAnything (только буквы и цифры: тогда любое крепление символов) после последнего канала

Затем у вас есть шаблон, например:

"(\\[\\w+\\.\\w+\\])\\|(\\w+:.+)\\|(\\w+:.+)";
  • (\\[\\w+\\.\\w+\\]) Эта группа захвата получит "[Testing.User]", но не ограничивается только тем, что она "[Testing.User]"
  • \\|(\\w+:.+) Эта группа захвата получит данные после первого канала и остановится до последнего канала. В этом случае "Info: ([Testing.Info] | Name: ([System.String] | Matt) | Age: ([System.Int32] | 21))", но не ограничивается им, начиная с "Info:"
  • \\|(\\w+:.+) Та же группа захвата, что и предыдущая, но фиксирует все, что есть после последнего канала, в этом случае "Описание: ([System.String] | Это некоторое описание)", но не ограничивается началом с Описание:

Теперь, если вы должны добавить еще один канал, за которым следует больше данных (|Anything:SomeData), тогда Description: будет частью группы 2, а группа 3 теперь будет "Anything:SomeData".

Код выглядит так:

using System;
using System.Text.RegularExpressions;

public class Program
{
    public static void Main()
    {
        String text = "[Testing.User]|Info:([Testing.Info]|Name:([System.String]|Matt)|Age:([System.Int32]|21))|Description:([System.String]|This is some description)";
        String pattern = "(\\[\\w+\\.\\w+\\])\\|(\\w+:.+)\\|(\\w+:.+)";

        Match match = Regex.Match(text, pattern);
        if (match.Success)
        {
            Console.WriteLine(match.Groups[1]);
            Console.WriteLine(match.Groups[2]);
            Console.WriteLine(match.Groups[3]); 
        }
    }
}

Результаты:

[Testing.User]
Info:([Testing.Info]|Name:([System.String]|Matt)|Age:([System.Int32]|21))
Description:([System.String]|This is some description)

См. рабочий пример здесь... https://dotnetfiddle.net/DYcZuY

См. рабочий пример, если я добавлю другое поле, следующее здесь в формате шаблона... https://dotnetfiddle.net/Mtc1CD

Ответ 4

Для этого вам нужно использовать балансировочные группы, которая является функцией регулярного выражения, исключающей механизм регулярных выражений .net. Это счетная система, когда найдена открывающая скобка, счетчик увеличивается, когда обнаружено закрытие, счетчик уменьшается, тогда вам нужно только проверить, не имеет ли счетчик нуля, если скобки будут сбалансированы. Это единственный способ убедиться, что вы находитесь внутри или вне круглой скобки:

using System;
using System.Text.RegularExpressions;

public class Example
{
   public static void Main()
   {
       string input = @"[Testing.User]|Info:([Testing.Info]|Name:([System.String]|Matt)|Age:([System.Int32]|21))|Description:([System.String]|This is some description)";

       string pattern = @"(?:[^|()]+|\((?>[^()]+|(?<Open>[(])|(?<-Open>[)]))*(?(Open)(?!))\))+";

       foreach (Match m in Regex.Matches(input, pattern)) 
           Console.WriteLine(m.Value);
   }
}

демо

подробнее:

(?:
    [^|()]+    # all that is not a parenthesis or a pipe
  |            # OR
               # content between parenthesis (eventually nested)
    \(              # opening parenthesis
     # here is the way to obtain balanced parens
    (?> # content between parens
        [^()]+        # all that is not parenthesis 
      |               # OR
        (?<Open>[(])  # an opening parenthesis (increment the counter)
      |
        (?<-Open>[)]) # a closing parenthesis (decrement the counter)
    )*  # repeat as needed
    (?(Open)(?!)) # make the pattern fail if the counter is not zero

    \)
)+

(?(open) (?!) ) является условным утверждением.

(?!) - всегда ложный подшаблон (пустой отрицательный просмотр), что означает: не следует ничего

Этот шаблон соответствует всем, что не является каналом и строками, заключенными между скобками.

Ответ 5

Regex не лучший подход для такого рода проблем, вам может понадобиться написать какой-то код для анализа ваших данных, я сделал простой пример, который позволяет выполнить этот простой случай. Основная идея здесь состоит в том, что вы хотите разделить только, если | не находится внутри круглых скобок, поэтому я отслеживаю подсчет скобок. Вам нужно будет немного поработать над ситуациями с угрозами, в которых, например, скобки являются частью раздела описания, но, как я сказал, это просто начальная точка:

static IEnumerable<String> splitSpecial(string input)
{
    StringBuilder builder = new StringBuilder();
    int openParenthesisCount = 0;

    foreach (char c in input)
    {
        if (openParenthesisCount == 0 && c == '|')
        {
            yield return builder.ToString();
            builder.Clear();
        }
        else
        {
            if (c == '(')
                openParenthesisCount++;
            if (c == ')')
                openParenthesisCount--;
            builder.Append(c);
        }
    }
    yield return builder.ToString();
}

static void Main(string[] args)
{
    string input = "[Testing.User]|Info:([Testing.Info]|Name:([System.String]|Matt)|Age:([System.Int32]|21))|Description:([System.String]|This is some description)";
    foreach (String split in splitSpecial(input))
    {
        Console.WriteLine(split);
    }
    Console.ReadLine();
}

Ouputs:

[Testing.User]
Info:([Testing.Info]|Name:([System.String]|Matt)|Age:([System.Int32]|21))
Description:([System.String]|This is some description)

Ответ 6

Это не очень надежное решение, но если вы знаете, что ваши три элемента верхнего уровня исправлены, вы можете жестко закодировать их в своем регулярном выражении.

(\[Testing\.User\])\|(Info:.*)\|(Description:.*)

Это регулярное выражение создаст одно совпадение с тремя группами внутри него, как вы ожидали. Вы можете проверить его здесь: http://derekslager.com/blog/posts/2007/09/a-better-dotnet-regular-expression-tester.ashx

Изменить: Здесь приведен полный рабочий пример С#

using System;
using System.Text.RegularExpressions;

namespace ConsoleApplication3
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            const string input = @"[Testing.User]|Info:([Testing.Info]|Name:([System.String]|Matt)|Age:([System.Int32]|21))|Description:([System.String]|This is some description)";
            const string pattern = @"(\[Testing\.User\])\|(Info:.*)\|(Description:.*)";

            var match = Regex.Match(input, pattern);
            if (match.Success)
            {
                for (int i = 1; i < match.Groups.Count; i++)
                {
                    Console.WriteLine("[" + i + "] = " + match.Groups[i]);
                }
            }

            Console.ReadLine();
        }
    }
}