已回答 正则表达式拆分

etl2016

活跃成员
已加入
2016年6月29日
留言内容
39
编程经验
3-5
你好,

考虑到两个条件,我正在尝试实现一种解决方案,以便能够将行拆分为字符串数组。首先-有些列具有多字符边界的文本限定。其次,一个多字符定界符。当两个功能中有共同的字符时,情况可能会变得复杂。另外,元字符(例如$和^)可能会带来更多挑战。看起来,正则表达式最适合此类目的。下面的一种实现方式在大多数情况下都可以使用,但是对于在文本限定符和/或定界符中选择的元字符而言,这种实现方式是无法解决的。

C#:
using System.Text.RegularExpressions;

public string[] Split(string expression, string delimiter,
            string qualifier, bool ignoreCase)
{
    string _Statement = String.Format
        ("{0}(?=(?:[^{1}]*{1}[^{1}]*{1})*(?![^{1}]*{1}))",
                        Regex.Escape(delimiter), Regex.Escape(qualifier));

    RegexOptions _Options = RegexOptions.Compiled | RegexOptions.Multiline;
    if (ignoreCase) _Options = _Options | RegexOptions.IgnoreCase;

    Regex _Expression = New Regex(_Statement, _Options);
    return _Expression.Split(expression);
}

上面的方法适用于大多数情况,但是不适用于涉及诸如$之类的元字符的情况(尤其是作为文本限定符的一部分。看起来需要对转义进行局部解释)

C#:
string input = "*|This is an ..  example*|..Am2..Cool!";
string input2 = "*|This is an $  example*|$Am2$Cool!";
string input3 = "$|This is an $  example$|$Am2$Cool!";
string input4 = "|$This is an $  example|$$Am2$Cool!";

foreach (string _Part in Split(input, "..", "*|", true))
Console.WriteLine(_Part);

foreach (string _Part in Split(input2, "$", "*|", true))
Console.WriteLine(_Part);

foreach (string _Part in Split(input3, "$", "$|", true)) // doesn't work correctly
Console.WriteLine(_Part);

foreach (string _Part in Split(input4, "$", "|$", true)) //  doesn't work correctly
Console.WriteLine(_Part);

您能否让我知道我们如何处理所有情况,包括那些将元字符作为文本限定符和/或定界符的一部分的情况?

谢谢你
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,605
地点
弗吉尼亚州切萨皮克
编程经验
10+
您知道,如果您向我们展示您的预期输出以及所获得的当前输出,将会极大地帮助您。

难道这真的是您的数据吗?或者就像过去的其他线程那样,您突然改变了数据或您要查询的内容的要求,因为您试图对问题的提问方式如此倾斜?
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,605
地点
弗吉尼亚州切萨皮克
编程经验
10+
如果您解释第7行的正则表达式的每个部分都试图实现什么,这也可能会有所帮助。
 

etl2016

活跃成员
已加入
2016年6月29日
留言内容
39
编程经验
3-5
感谢您的答复。

该程序应具有通用性,可以处理任何自由选择的文本限定符和定界符的传入提要。一些提要甚至可能没有文本限定符。当然,所有这些选择都作为参数传送到程序中,以相应地解释提要。

C#:
string input5 = "|$This is an $  example|$$Am2$Cool!|$$|";

在上面的示例中,如果text-qualifier为| $,而定界符为$,则预期的字符串数组如下:

a [0]是| $这是一个$示例| $
a [1]是Am2
a [2]很酷!
a [3]为空

在第一篇文章的第7行中,使用的正则表达式是在研究过程中发现的样本,它正试图逃避限定符和分隔符。事实证明,非正则表达式包含更多的代码行,而正则表达式(尽管隐含的)似乎是复杂字符串处理的通用选择,具有多种功能。两种选择都在探索中。

谢谢你。
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,605
地点
弗吉尼亚州切萨皮克
编程经验
10+
什么与众不同"text-qualifier" from a "delimiter" ?
 

etl2016

活跃成员
已加入
2016年6月29日
留言内容
39
编程经验
3-5
如果定界符出现在text-qualifier中,则不将其视为拆分条件。" good | morning"| 40 | 50 | 60会产生"good | morning"作为第一个数组元素,如果|是定界符,"是文字限定词。 40、50、60是下一个数组元素。谢谢
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,605
地点
弗吉尼亚州切萨皮克
编程经验
10+
是"text-qualifiers"总是配对?是否可以有未配对的文本限定符,例如:
C#:
"good|morning"|"40"|"50"|"60
请注意,60有一个双引号,但没有双引号。

怎样"text-qualifiers"逃脱了您的数据?如果您确实希望该字符序列出现在数据中而不被视为"text-qualifiers"?
 

etl2016

活跃成员
已加入
2016年6月29日
留言内容
39
编程经验
3-5
是的,文本限定符始终配对。下面两行都是有效的表示。
C#:
"good|morning"|"40"|"50"|"60"
"good|morning"|40|50|60

文本限定符由上游构造。因此,他们的程序将不得不选择一个适当的文本限定符,该限定符不应出现在其数据中。

但是,非常感谢您提出这种现实生活的可能性。作为更可靠的解决方案,可以忽略前面带有\反斜杠的文本限定符。

例如,在这种情况下,以下内容可能有效。

C#:
"hello \"$ good|morning"|40|50|60

在上面的示例中,数组[0]应为:
C#:
"hello \"$ good|morning"
.

需要按原样结转\的原因是,因为那是源发送它的方式。

谢谢你
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,605
地点
弗吉尼亚州切萨皮克
编程经验
10+
Okay. Next silly question: Are you married to the idea of using Split(), or is used my Match() or just plain or IndexOf() sufficient as along as in the end, you end up with an array of strings that look like they have been split using the delimiter, and protected by the "text-qualifiers"?

一个正常的问题:如果选择了分隔符或"text-qualifier" is the "\"?
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,605
地点
弗吉尼亚州切萨皮克
编程经验
10+
还有一个问题,会发生什么"text-qualifiers"不在数据字段的开头和结尾:
C#:
Pete "Maverick" Mitchell|Lieutenant|Pilot
 

etl2016

活跃成员
已加入
2016年6月29日
留言内容
39
编程经验
3-5
谢谢你。

C#:
Pete "Maverick" Mitchell|Lieutenant|Pilot
/* invalid, reason-1 : assuming quote is the text-qualifier, it is in the middle of data field and it is not escaped. If it really had to be in the middle, it needed an escape character.
reason-2 this column and other columns which are strings are not text-qualified.
*/
"Pete \"Maverick\" Mitchell"|"Lieutenant"|"Pilot"|40|50|60 // valid
"Pete \"Maverick\" Mitchell"|"Lieutenant"|"Pilot"|"40"|"50"|"60" // valid

另一方面,\通常被解释为转义元字符,因此,反对使用定界符或文本限定符之类的字符并将其推回源,要求进行审查。
 

etl2016

活跃成员
已加入
2016年6月29日
留言内容
39
编程经验
3-5
嗨,第一篇文章中使用的正则表达式方法可以承受多种情况。但是,它仍在改进,以处理转义的文本限定符作为数据字段的一部分出现的情况(如#11中的文章)

谢谢你
 

羊皮

退休程序员
工作人员
已加入
2018年9月5日
留言内容
1,979
地点
英国
编程经验
10+
为什么可以使用string.join,indexof和substring等使Regex感到头痛?
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,605
地点
弗吉尼亚州切萨皮克
编程经验
10+
因此,我在第9号帖子中提出的问题是,他是否真的同意使用正则表达式的想法。也许是时候考虑离婚了? :)

 

羊皮

退休程序员
工作人员
已加入
2018年9月5日
留言内容
1,979
地点
英国
编程经验
10+
您知道我也爱我的Reg ex,但出于理智和简单起见,请使用通常建议的方法。你有没有理由 @ etl2016 ?
 

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,605
地点
弗吉尼亚州切萨皮克
编程经验
10+
好吧,这是我的非Regex方法:
LineParser.cs:
using System.Collections.Generic;
using System.Linq;

namespace LineParser
{
    public abstract class LineParser
    {
        public string Delimiter { get; }
        public string BeginQuote { get; }
        public string EndQuote { get; }
        public char Escape { get; }

        public LineParser(string delimiter, string beginQuote, string endQuote, char escape = '\\')
        {
            Delimiter = delimiter;
            BeginQuote = beginQuote;
            EndQuote = endQuote;
            Escape = escape;
        }

        public LineParser(string delimiter, string quote, char escape = '\\')
            : this(delimiter, quote, quote, escape)
        {
        }

        public abstract IEnumerable<string> Parse(string input);

        public virtual string[] Split(string input) => Parse(input).ToArray();
    }
}

StringLineParser.cs:
using System;
using System.Collections.Generic;
using System.IO;

namespace LineParser
{
    public class StringLineParser : LineParser
    {

        public StringLineParser(string delimiter, string beginQuote, string endQuote, char escape = '\\')
            : base(delimiter, beginQuote, endQuote, escape)
        {
        }

        public StringLineParser(string delimiter, string quote, char escape = '\\')
            : this(delimiter, quote, quote, escape)
        {
        }

        int IndexOfUnescaped(ReadOnlySpan<char> input, string value)
        {
            var left = 0;
            while (!input.IsEmpty)
            {
                int index = input.IndexOf(value);
                if (index < 0)
                    return index;

                if (index == 0 || input[index - 1] != Escape)
                    return left + index;

                left += index + value.Length;
                input = input.Slice(index + value.Length);
            }
            return -1;
        }

        int GetEndOfQuotedString(ReadOnlySpan<char> input)
        {
            int end = IndexOfUnescaped(input.Slice(BeginQuote.Length), EndQuote);
            if (end < 0)
                throw new InvalidDataException($"Unmatched {BeginQuote}");
            end += BeginQuote.Length + EndQuote.Length;

            if (end < input.Length && !input.Slice(end).StartsWith(Delimiter))
                throw new InvalidDataException($"Expected {Delimiter} after {EndQuote}");

            return end;
        }

        int GetEndOfField(ReadOnlySpan<char> input)
        {
            int end = IndexOfUnescaped(input, Delimiter);
            if (end < 0)
                end = input.Length;

            var field = input[0..end];
            ValidateNoQuote(field, BeginQuote);
            ValidateNoQuote(field, EndQuote);

            return end;

            void ValidateNoQuote(ReadOnlySpan<char> field, string quote)
            {
                if (field.IndexOf(quote) >= 0)
                    throw new InvalidDataException($"Unexpected {quote} in field data.");
            }
        }

        IEnumerable<string> GetTokens(string input)
        {
            var inputMem = input.AsMemory();

            while (!inputMem.IsEmpty)
            {
                int end = Delimiter.Length;
                var span = inputMem.Span;

                if (span.StartsWith(BeginQuote))
                    end = GetEndOfQuotedString(span);
                else if (!span.StartsWith(Delimiter))
                    end = GetEndOfField(span);

                yield return inputMem[0..end].ToString();
                inputMem = inputMem.Slice(end);
            }
        }

        public override IEnumerable<string> Parse(string input)
        {
            if (input == null)
                yield break;

            string lastValue = null;
            foreach(var token in GetTokens(input))
            {
                var value = token;
                if (value == Delimiter)
                {
                    yield return lastValue ?? "";
                    value = null;
                }
                lastValue = value;
            }
            yield return lastValue ?? "";
        }
    }
}

StringLineParserTests.cs:
using System;
using Xunit;
using LineParser;
using System.IO;

namespace LineParser.Tests
{
    public class StringLineParserTests
    {
        [Fact]
        public void HandlesNull()
        {
            var parser = new StringLineParser("|", "<", ">");
            var results = parser.Split(null);
            Assert.Empty(results);
        }

        [Theory]
        [InlineData("hello<world>")]
        [InlineData("<hello>world")]
        public void HandlesBadQuotedData(string input)
        {
            var parser = new StringLineParser("|", "<", ">");
            Assert.Throws<InvalidDataException>(() => parser.Split(input));
        }

        [Fact]
        public void HandlesEmptyString()
        {
            var parser = new StringLineParser("|", "<", ">");
            var results = parser.Split("");
            Assert.Collection(results, v => Assert.Equal("", v));
        }

        [Fact]
        public void HandlesSingleDelimiter()
        {
            var parser = new StringLineParser("|", "<", ">");
            var results = parser.Split("|");
            Assert.Collection(results,
                              v => Assert.Equal("", v),
                              v => Assert.Equal("", v));
        }

        [Fact]
        public void HandlesTwoDelimiters()
        {
            var parser = new StringLineParser("|", "<", ">");
            var results = parser.Split("||");
            Assert.Collection(results,
                              v => Assert.Equal("", v),
                              v => Assert.Equal("", v),
                              v => Assert.Equal("", v));
        }

        [Theory]
        [InlineData("abc", "abc")]
        [InlineData("<abc>", "<abc>")]
        [InlineData("<a|b>", "<a|b>")]
        [InlineData(@"<a\|b>", @"<a\|b>")]
        [InlineData("<a|b|c>", "<a|b|c>")]
        [InlineData(@"<a\|b\|c>", @"<a\|b\|c>")]
        public void HandlesSingleValue(string input, string a)
        {
            var parser = new StringLineParser("|", "<", ">");
            var results = parser.Split(input);
            Assert.Collection(results, v => Assert.Equal(a, v));
        }

        [Theory]
        [InlineData("abc|123", "abc", "123")]
        [InlineData("<abc>|123", "<abc>", "123")]
        [InlineData("<abc>|<123>", "<abc>", "<123>")]
        [InlineData("abc|<123>", "abc", "<123>")]
        [InlineData("<abc>|", "<abc>", "")]
        [InlineData("|<123>", "", "<123>")]
        public void HandlesTwoValues(string input, string a, string b)
        {
            var parser = new StringLineParser("|", "<", ">");
            var results = parser.Split(input);
            Assert.Collection(results,
                              v => Assert.Equal(a, v),
                              v => Assert.Equal(b, v));
        }

        [Fact]
        public void HandlesThreeValues()
        {
            var parser = new StringLineParser("|", "<", ">");
            var results = parser.Split("abc|123|ghi");
            Assert.Collection(results,
                              v => Assert.Equal("abc", v),
                              v => Assert.Equal("123", v),
                              v => Assert.Equal("ghi", v));
        }
    }
}

和一些测试输出:
C#:
hello|world|28: [hello] [world] [28]
"hello"|world|28: ["hello"] [world] [28]
hello|"world|28": [hello] ["world|28"]
: []
|: [] []
world: [world]
"hello": ["hello"]
"hello\"test"|world|28: ["hello\"test"] [world] [28]
由。。。生产:
Program.cs:
using System;
using System.Linq;
using System.Text.RegularExpressions;

namespace LineParser
{
    class Program
    {
        static void DoIt(LineParser parser, string input)
        {
            Console.Write($"{input}: ");
            Console.WriteLine(string.Join(" ", parser.Parse(input).Select(s => $"[{s}]")));
        }


        static void Main(string[] args)
        {
            var parser = new StringLineParser("|", "\"");

            DoIt(parser, "hello|world|28");
            DoIt(parser, "\"hello\"|world|28");
            DoIt(parser, "hello|\"world|28\"");
            DoIt(parser, "");
            DoIt(parser, "|");
            DoIt(parser, "world");
            DoIt(parser, "\"hello\"");
            DoIt(parser, "\"hello\\\"test\"|world|28");
        }
    }
}
 

etl2016

活跃成员
已加入
2016年6月29日
留言内容
39
编程经验
3-5
谢谢跳伞者。我离开了Regex,转而采用了非Regex解决方案,并且经过测试,它在很多情况下都可以正常工作,到目前为止没有发现任何问题。是的,非Regex更具可读性,并且更易于调试。感谢上述,您的方法看起来比我的方法更健壮。
 

etl2016

活跃成员
已加入
2016年6月29日
留言内容
39
编程经验
3-5
嗨,跳伞者,谢谢。上面的实现是否特定于.net环境的特定风格?在VS2017下,我在4.6.1和Core 2.1中都尝试了上面的代码,它们具有不同的编译错误。

在4.6.1下,错误发生在ReadOnlySpan周围,该错误表示:找不到类型或名称空间
在Core 2.1下,在方法GetEndOfField中,存在3个错误:
1)第68行的结尾}似乎有些偏移,将ValidateNoQuote放入了其中,并将GetEndOfField引入了该方法,这是通过对齐结尾}
2)在GetEndOfField中,在var field = input [0..end]处,错误是:预期的标识符。
3)在GetEndOfField中的ValidateNoQuote(字段,BeginQuote)处,错误是:无法从char转换为ReadOnlySpan<char>。铸造(ReadOnlySpan<char>) didn't fix.

请让我知道,是否要使用任何支持的NuGet软件包或.net设置?

非常感谢
 

约翰·H

C#论坛主持人
工作人员
已加入
2011年4月23日
留言内容
1,076
地点
挪威
编程经验
10+
I see that StringLineParser line 57 uses range syntax input[0..end] which is only supported on c# 8.0 which means .Net Core 3.x, so that requires VS 2019.
 
最佳 底部