我正在寻找关于如何处理正在创建的csv文件的建议,然后由我们的客户上传,并且可能在值中有逗号,如公司名称。
我们正在考虑的一些想法是:带引号的标识符(value "," values ","等等)或使用|代替逗号。最大的问题是我们必须让它变得简单,否则客户就不会这么做。
我正在寻找关于如何处理正在创建的csv文件的建议,然后由我们的客户上传,并且可能在值中有逗号,如公司名称。
我们正在考虑的一些想法是:带引号的标识符(value "," values ","等等)或使用|代替逗号。最大的问题是我们必须让它变得简单,否则客户就不会这么做。
当前回答
我通常在CSV文件解析例程中这样做。假设“line”变量是CSV文件中的一行,所有列的值都用双引号括起来。执行以下两行代码后,您将在“values”集合中获得CSV列。
// The below two lines will split the columns as well as trim the DBOULE QUOTES around values but NOT within them
string trimmedLine = line.Trim(new char[] { '\"' });
List<string> values = trimmedLine.Split(new string[] { "\",\"" }, StringSplitOptions.None).ToList();
其他回答
正如其他人所说,您需要转义包含引号的值。这是c#中的一个小型CSV读取器,支持加引号的值,包括嵌入引号和回车。
顺便说一下,这是单元测试的代码。我现在发布它是因为这个问题似乎经常出现,其他人可能不想要整个库,而简单的CSV支持就可以了。
你可以这样使用它:
using System;
public class test
{
public static void Main()
{
using ( CsvReader reader = new CsvReader( "data.csv" ) )
{
foreach( string[] values in reader.RowEnumerator )
{
Console.WriteLine( "Row {0} has {1} values.", reader.RowIndex, values.Length );
}
}
Console.ReadLine();
}
}
这些是课程。注意,您可以使用Csv。Escape函数来编写有效的CSV。
using System.IO;
using System.Text.RegularExpressions;
public sealed class CsvReader : System.IDisposable
{
public CsvReader( string fileName ) : this( new FileStream( fileName, FileMode.Open, FileAccess.Read ) )
{
}
public CsvReader( Stream stream )
{
__reader = new StreamReader( stream );
}
public System.Collections.IEnumerable RowEnumerator
{
get {
if ( null == __reader )
throw new System.ApplicationException( "I can't start reading without CSV input." );
__rowno = 0;
string sLine;
string sNextLine;
while ( null != ( sLine = __reader.ReadLine() ) )
{
while ( rexRunOnLine.IsMatch( sLine ) && null != ( sNextLine = __reader.ReadLine() ) )
sLine += "\n" + sNextLine;
__rowno++;
string[] values = rexCsvSplitter.Split( sLine );
for ( int i = 0; i < values.Length; i++ )
values[i] = Csv.Unescape( values[i] );
yield return values;
}
__reader.Close();
}
}
public long RowIndex { get { return __rowno; } }
public void Dispose()
{
if ( null != __reader ) __reader.Dispose();
}
//============================================
private long __rowno = 0;
private TextReader __reader;
private static Regex rexCsvSplitter = new Regex( @",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))" );
private static Regex rexRunOnLine = new Regex( @"^[^""]*(?:""[^""]*""[^""]*)*""[^""]*$" );
}
public static class Csv
{
public static string Escape( string s )
{
if ( s.Contains( QUOTE ) )
s = s.Replace( QUOTE, ESCAPED_QUOTE );
if ( s.IndexOfAny( CHARACTERS_THAT_MUST_BE_QUOTED ) > -1 )
s = QUOTE + s + QUOTE;
return s;
}
public static string Unescape( string s )
{
if ( s.StartsWith( QUOTE ) && s.EndsWith( QUOTE ) )
{
s = s.Substring( 1, s.Length - 2 );
if ( s.Contains( ESCAPED_QUOTE ) )
s = s.Replace( ESCAPED_QUOTE, QUOTE );
}
return s;
}
private const string QUOTE = "\"";
private const string ESCAPED_QUOTE = "\"\"";
private static char[] CHARACTERS_THAT_MUST_BE_QUOTED = { ',', '"', '\n' };
}
实际上,CSV格式有一个规范,RFC 4180以及如何处理逗号:
包含换行符(CRLF)、双引号和逗号的字段应该用双引号括起来。
http://tools.ietf.org/html/rfc4180
所以,要有值foo和bar,baz,你这样做:
foo,"bar,baz"
另一个需要考虑的重要需求(同样来自规范):
如果使用双引号括起字段,则使用双引号 在字段中出现时,必须在字段前面加上 另一个双引号。例如: “aaa级”、“b”“bb”、“ccc”
我使用papaParse库来解析CSV文件,并拥有键-值对(键/头/ CSV文件-值的第一行)。
下面是我举的例子:
https://codesandbox.io/embed/llqmrp96pm
它有一个dummy.csv文件来演示CSV解析。
我在reactJS中使用过它,尽管它很容易在任何语言编写的应用程序中复制。
我认为这个问题最简单的解决方案是让客户在excel中打开csv,然后按ctrl + r将所有逗号替换为任何你想要的标识符。这对客户来说非常简单,只需要对代码进行一次更改就可以读取所选的分隔符。
正如我在对harpo的回答的评论中提到的,他的解决方案在大多数情况下都很好,但是在某些情况下,当逗号直接相邻时,它无法在逗号上分割。
这是因为Regex字符串意外地表现为vertabim字符串。 为了获得正确的行为,regex字符串中的所有“字符都需要手动转义,而不使用vertabim转义。
Ie。正则表达式应该是这样的,使用手动转义:
",(?=(?:[^\"\"]*\"\"[^\"\"]*\"\")*(?![^\"\"]*\"\"))"
这转化为 ",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))"
当使用一个vertabim字符串 @",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))" 它表现为以下你可以看到如果你调试正则表达式:
",(?=(?:[^"]*"[^"]*")*(?![^"]*"))"
总之,我推荐harpo的解决方案,但要注意这个小陷阱!
我已经在CsvReader中包含了一些可选的故障保护,以便在发生此错误时通知您(如果您有预先知道的列数):
if (_expectedDataLength > 0 && values.Length != _expectedDataLength)
throw new DataLengthException(string.Format("Expected {0} columns when splitting csv, got {1}", _expectedDataLength, values.Length));
可以通过构造函数注入:
public CsvReader(string fileName, int expectedDataLength = 0) : this(new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
_expectedDataLength = expectedDataLength;
}