C#,正则expression式:如何parsing逗号分隔的值,其中一些值可能被引用的string本身包含逗号

在C#中,使用Regex类,如何parsing逗号分隔的值,其中一些值可能是引用的string本身包含逗号?

 using System ; using System.Text.RegularExpressions ; class Example { public static void Main ( ) { string myString = "cat,dog,\"0 = OFF, 1 = ON\",lion,tiger,'R = red, G = green, B = blue',bear" ; Console.WriteLine ( "\nmyString is ...\n\t" + myString + "\n" ) ; Regex regex = new Regex ( "(?<=,(\"|\')).*?(?=(\"|\'),)|(^.*?(?=,))|((?<=,).*?(?=,))|((?<=,).*?$)" ) ; Match match = regex.Match ( myString ) ; int j = 0 ; while ( match.Success ) { Console.WriteLine ( j++ + " \t" + match ) ; match = match.NextMatch() ; } } } 

输出(部分)如下所示:

 0 cat 1 dog 2 "0 = OFF 3 1 = ON" 4 lion 5 tiger 6 'R = red 7 G = green 8 B = blue' 9 bear 

但是, 期望的输出是:

 0 cat 1 dog 2 0 = OFF, 1 = ON 3 lion 4 tiger 5 R = red, G = green, B = blue 6 bear 

试试这个正则expression式:

 "[^"\r\n]*"|'[^'\r\n]*'|[^,\r\n]* 

  Regex regexObj = new Regex(@"""[^""\r\n]*""|'[^'\r\n]*'|[^,\r\n]*"); Match matchResults = regexObj.Match(input); while (matchResults.Success) { Console.WriteLine(matchResults.Value); matchResults = matchResults.NextMatch(); } 

。OUPUTS:

  • “0 =关,1 =开”
  • 狮子
  • 'R =红色,G =绿色,B =蓝色'

注:这个正则expression式解决scheme将适用于您的情况,但我build议您使用像FileHelpers专业库。

为什么不听从专家的build议, 不要推出自己的CSVparsing器 。

你的第一个想法是,“我需要处理引号内的逗号。”

你的下一个想法是,“哦,废话,我需要处理引号内引号。引号引号。双引号。单引号…”

这是疯狂的道路。 不要写你自己的。 find一个广泛的unit testing覆盖面,碰到所有困难的部分,并走了你的地狱图书馆。 对于.NET,使用免费的开源的FileHelpers库 。

这不是一个正则expression式,但我已经使用Microsoft.VisualBasic.FileIO.TextFieldParser来完成这个CSV文件。 是的,在C#应用程序中添加对Microsoft.VisualBasic的引用可能会感觉有点奇怪,甚至可能有点肮脏,但嘿它有效。

啊,RegEx。 现在你有两个问题。 ;)

我会使用一个标记器/parsing器,因为它非常简单,而且更重要的是,为了以后的维护更容易阅读。

这工作,例如:

 using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Text; class Program { static void Main(string[] args) { string myString = "cat,dog,\"0 = OFF, 1 = ON\",lion,tiger,'R = red, G = green, B = blue',bear"; Console.WriteLine("\nmyString is ...\n\t" + myString + "\n"); CsvParser parser = new CsvParser(myString); Int32 lineNumber = 0; foreach (string s in parser) { Console.WriteLine(lineNumber + ": " + s); } Console.ReadKey(); } } internal enum TokenType { Comma, Quote, Value } internal class Token { public Token(TokenType type, string value) { Value = value; Type = type; } public String Value { get; private set; } public TokenType Type { get; private set; } } internal class StreamTokenizer : IEnumerable<Token> { private TextReader _reader; public StreamTokenizer(TextReader reader) { _reader = reader; } public IEnumerator<Token> GetEnumerator() { String line; StringBuilder value = new StringBuilder(); while ((line = _reader.ReadLine()) != null) { foreach (Char c in line) { switch (c) { case '\'': case '"': if (value.Length > 0) { yield return new Token(TokenType.Value, value.ToString()); value.Length = 0; } yield return new Token(TokenType.Quote, c.ToString()); break; case ',': if (value.Length > 0) { yield return new Token(TokenType.Value, value.ToString()); value.Length = 0; } yield return new Token(TokenType.Comma, c.ToString()); break; default: value.Append(c); break; } } // Thanks, dpan if (value.Length > 0) { yield return new Token(TokenType.Value, value.ToString()); } } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } internal class CsvParser : IEnumerable<String> { private StreamTokenizer _tokenizer; public CsvParser(Stream data) { _tokenizer = new StreamTokenizer(new StreamReader(data)); } public CsvParser(String data) { _tokenizer = new StreamTokenizer(new StringReader(data)); } public IEnumerator<string> GetEnumerator() { Boolean inQuote = false; StringBuilder result = new StringBuilder(); foreach (Token token in _tokenizer) { switch (token.Type) { case TokenType.Comma: if (inQuote) { result.Append(token.Value); } else { yield return result.ToString(); result.Length = 0; } break; case TokenType.Quote: // Toggle quote state inQuote = !inQuote; break; case TokenType.Value: result.Append(token.Value); break; default: throw new InvalidOperationException("Unknown token type: " + token.Type); } } if (result.Length > 0) { yield return result.ToString(); } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } 

只是添加我今天上午的解决scheme。

 var regex = new Regex("(?<=^|,)(\"(?:[^\"]|\"\")*\"|[^,]*)"); foreach (Match m in regex.Matches("<-- input line -->")) { var s = m.Value; } 

正如你所看到的,你需要调用每行的 regex.Matches()。 然后,它将返回一个MatchCollection,其中包含与列相同数量的项目。 显然,每个匹配的Value属性是parsing的值。

这仍然是一个正在进行的工作,但它愉快地分析CSVstring,如:

 2,3.03,"Hello, my name is ""Joshua""",A,B,C,,,D 

CSV 不规则 。 除非你的regex语言有足够的能力来处理csvparsing的有状态(不太可能,MS不是),那么任何纯正则expression式解决scheme都是一个等待发生的错误列表,当你点击一个新的input源时,由最后一个正则expression式。

因为语法很简单,CSV读取并不复杂,但是即使如此,您也必须考虑:引号引号,引号内的逗号,引号内的新行,空字段。

因此,您应该使用别人的CSVparsing器。 我build议.Net的CSVReader

function:

  private List<string> ParseDelimitedString (string arguments, char delim = ',') { bool inQuotes = false; bool inNonQuotes = false; //used to trim leading WhiteSpace List<string> strings = new List<string>(); StringBuilder sb = new StringBuilder(); foreach (char c in arguments) { if (c == '\'' || c == '"') { if (!inQuotes) inQuotes = true; else inQuotes = false; }else if (c == delim) { if (!inQuotes) { strings.Add(sb.Replace("'", string.Empty).Replace("\"", string.Empty).ToString()); sb.Remove(0, sb.Length); inNonQuotes = false; } else { sb.Append(c); } } else if ( !char.IsWhiteSpace(c) && !inQuotes && !inNonQuotes) { if (!inNonQuotes) inNonQuotes = true; sb.Append(c); } } strings.Add(sb.Replace("'", string.Empty).Replace("\"", string.Empty).ToString()); return strings; } 

用法

  string myString = "cat,dog,\"0 = OFF, 1 = ON\",lion,tiger,'R = red, G = green, B = blue',bear, text"; List<string> strings = ParseDelimitedString(myString); foreach( string s in strings ) Console.WriteLine( s ); 

输出:

 cat dog 0 = OFF, 1 = ON lion tiger R = red, G = green, B = blue bear text 

我在该版本中发现了一些错误,例如,在值中带有单引号的非引号string。

我同意使用FileHelper库时,但是该库要求您知道您的数据将看起来像…我需要一个通用的parsing器。

所以我已经更新了以下代码,并认为我会分享…

  static public List<string> ParseDelimitedString(string value, char delimiter) { bool inQuotes = false; bool inNonQuotes = false; bool secondQuote = false; char curQuote = '\0'; List<string> results = new List<string>(); StringBuilder sb = new StringBuilder(); foreach (char c in value) { if (inNonQuotes) { // then quotes are just characters if (c == delimiter) { results.Add(sb.ToString()); sb.Remove(0, sb.Length); inNonQuotes = false; } else { sb.Append(c); } } else if (inQuotes) { // then quotes need to be double escaped if ((c == '\'' && c == curQuote) || (c == '"' && c == curQuote)) { if (secondQuote) { secondQuote = false; sb.Append(c); } else secondQuote = true; } else if (secondQuote && c == delimiter) { results.Add(sb.ToString()); sb.Remove(0, sb.Length); inQuotes = false; } else if (!secondQuote) { sb.Append(c); } else { // bad,as,"user entered something like"this,poorly escaped,value // just ignore until second delimiter found } } else { // not yet parsing a field if (c == '\'' || c == '"') { curQuote = c; inQuotes = true; inNonQuotes = false; secondQuote = false; } else if (c == delimiter) { // blank field inQuotes = false; inNonQuotes = false; results.Add(string.Empty); } else { inQuotes = false; inNonQuotes = true; sb.Append(c); } } } if (inQuotes || inNonQuotes) results.Add(sb.ToString()); return results; } 

因为这个问题:正则expression式来parsingcsv嵌套引号

在这里报告,是更通用的,因为正则expression式不是真正的解决这个问题的正确方法(即我有很多问题与灾难性的回溯( http://www.regular-expressions.info/catastrophic.html

这里也是一个简单的Pythonparsing器实现

 def csv_to_array(string): stack = [] match = [] matches = [] for c in string: # do we have a quote or double quote? if c == "\"": # is it a closing match? if len(stack) > 0 and stack[-1] == c: stack.pop() else: stack.append(c) elif (c == "," and len(stack) == 0) or (c == "\n"): matches.append("".join(match)) match = [] else: match.append(c) return matches