复杂字符串拆分

11

我有一个类似以下的字符串:

[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的字符串序列化/表示。

我希望能够执行拆分操作并在生成的数组中得到以下元素:

 [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)

我该如何得到我期望的结果?

我对正则表达式不是很熟悉,但我知道在这种情况下它是一个非常可行的解决方案。


1
你必须将其序列化为那种格式吗?或者可以将其放入类似 JSON 或 XML 的东西中,这样更容易反序列化吗? - Steve
@Steve 很不幸,是的。它必须是这种格式 :( - Matias Cicero
这使得它相当困难,因为它没有任何规律的格式。 - Steve
这个任务是为Testing.User的序列化编写反序列化器吗?如果不是,是否可以找到Testing.User的反序列化器并进行自己的重新工程?上面建议将其序列化为另一种格式,如果你/你的团队能够决定切换序列化库,我强烈建议这样做,尤其是如果它是定制的。在当今时代,序列化不应该需要重新开发。 :) - h.j.k.
6个回答

7

使用正则表达式的前瞻

您可以使用以下这个正则表达式:

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

演示

这个正则表达式的想法是捕获你想要的内容到123组中。

你可以通过下面的图表很容易地看到它:

正则表达式可视化

匹配信息

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)`

正则表达式

另一方面,如果你不喜欢上面的正则表达式,你可以使用下面这个:

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

正则表达式可视化

演示链接

甚至可以强制匹配至少一个字符:

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

Regular expression visualization


6

已经有足够的分割答案了,这里提供另一种方法。如果您的输入表示树形结构,为什么不将其解析为树呢? 下面的代码是从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();
        }

    }
}

结果(点击):

您可以以树形结构获取每个节点及其相关的子值,并可以对其进行任意操作,例如在TreeView控件中轻松显示结构:

enter image description here


3
假设你的分组可以标记为:
  1. [任意.任意]
  2. 任意:真正的任意(只有字母和数字:然后是任意数量的字符),在第一个管道符之后
  3. 任意:真正的任意(只有字母和数字:然后是任意数量的字符),在最后一个管道符之后
那么你就有了这样的模式:
"(\\[\\w+\\.\\w+\\])\\|(\\w+:.+)\\|(\\w+:.+)";
  • (\\[\\w+\\.\\w+\\]) 此捕获组将获取 "[Testing.User]",但不仅限于它只是 "[Testing.User]"
  • \\|(\\w+:.+) 此捕获组将获取第一个管道符后面的数据,并在最后一个管道符之前停止。在这种情况下,"Info:([Testing.Info]|Name:([System.String]|Matt)|Age:([System.Int32]|21))",但不限于以 "Info:" 开头
  • \\|(\\w+:.+) 与上一个捕获组相同,但捕获最后一个管道符后面的任何内容,在本例中为 "Description:([System.String]|This is some description)",但不限于以 "Description:" 开头

现在如果您添加另一个管道符,后跟更多数据 (|Anything:SomeData),那么 Description: 将成为第二个组的一部分,第三个组现在将是 "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


3
为了实现这个目标,您需要使用平衡组,这是一种正则表达式功能,仅适用于.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);
   }
}

demo

模式详情:

(?:
    [^|()]+    # 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) (?!) )是一个条件语句。

(?!)是一个始终为假的子模式(一个空的负向先行断言),意思是:不跟随任何内容

此模式匹配除了管道符和括号之间包含的字符串以外的所有内容。


2

正则表达式并不是解决这类问题的最佳方法,您可能需要编写一些代码来解析数据。我做了一个简单的示例,可以实现您的简单情况。基本思路是只在 | 不在括号内时才进行分割,因此我跟踪了括号计数。您需要处理一些特殊情况,例如括号是描述部分的一部分,但正如我所说,这只是一个起点:

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();
}

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

1
这不是一个很好/健壮的解决方案,但如果您知道您的三个顶级项目是固定的,那么您可以将它们硬编码到您的正则表达式中。
(\[Testing\.User\])\|(Info:.*)\|(Description:.*)

这个正则表达式将创建一个匹配,其中包含三个组,就像你期望的那样。你可以在这里进行测试: http://derekslager.com/blog/posts/2007/09/a-better-dotnet-regular-expression-tester.ashx 编辑:这里有一个完整的可用的C#示例。
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();
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接