处理CSV文件中的逗号

我正在寻找关于如何处理正在创建的csv文件的建议,然后由我们的客户上传,并且可能在值中有逗号,如公司名称。

我们正在考虑的一些想法是:带引号的标识符(value "," values ","等等)或使用|代替逗号。最大的问题是我们必须让它变得简单,否则客户就不会这么做。

610899 次浏览

在字符串周围加双引号。这通常是Excel的功能

阿拉巴马州伊莱,

将双引号转义为2 双引号。如。 “test1",“foo"“bar",“test2" < / p >

实际上有一个CSV格式的规范,RFC 4180和如何处理逗号:

包含换行符(CRLF)、双引号和逗号的字段应该用双引号括起来。

http://tools.ietf.org/html/rfc4180

因此,要有值foobar,baz,你要这样做:

foo,"bar,baz"

另一个需要考虑的重要需求(同样来自规范):

如果使用双引号括起字段,则使用双引号 在字段中出现时,必须在字段前面加上 另一个双引号。例如:< / p >

"aaa","b""bb","ccc"

正如其他人所说,您需要转义包含引号的值。这是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' };
}

您可以在字段周围加上双引号。我不喜欢这种方法,因为它增加了另一个特殊字符(双引号)。只需定义一个转义字符(通常是反斜杠),并在需要转义的地方使用它:

data,more data,more data\, even,yet more

You don't have to try to match quotes, and you have fewer exceptions to parse. This simplifies your code, too.

由于这是关于一般实践,让我们从经验法则开始:

  1. 不要使用CSV,使用带有库的XML来读取&取而代之的是编写XML文件。

  2. 如果必须使用CSV。正确操作并使用免费库来解析和存储CSV文件。

为了证明1),大多数CSV解析器都不知道编码,所以如果你不处理US-ASCII,你就是在自找麻烦。 例如,excel2002以本地编码存储CSV,而没有任何关于编码的说明。CSV标准没有被广泛采用:(。 另一方面,xml标准被很好地采用,它处理编码非常好

为了证明2),几乎所有语言都有大量的csv解析器,所以即使解决方案看起来非常简单,也没有必要重新发明轮子。

举几个例子:

  • 对于python使用build in csv模块

  • 为perl检查CPAN和文本::CSV

  • 对于PHP,使用内置的fgetcsv/fputcsv函数

  • for java check SuperCVS

实际上,如果你不打算在嵌入式设备上解析它,就没有必要手动实现它。

CSV格式使用逗号分隔值,包含回车、换行、逗号或双引号的值用双引号括起来。包含双引号的值会被引用,并且每个文字引号都被紧挨着的前引号转义:例如,以下3个值:

test
list, of, items
"go" he said

将被编码为:

test
"list, of, items"
"""go"" he said"

任何字段都可以加引号,但只有包含逗号、CR/NL或引号必须的字段才可以加引号。

CSV格式没有真正的标准,但几乎所有应用程序都遵循记录的这里<强> < / >强约定。在其他地方提到的RFC不是CSV的标准,它是一个用于在MIME中使用CSV的RFC,它包含了一些非常规的和不必要的限制,使它在MIME之外无用。

我所见过的许多CSV模块不适应的一个问题是,可以在单个字段中编码多行,这意味着您不能假设每一行都是一个单独的记录,您要么需要不允许数据中出现换行,要么准备好处理这个问题。

添加对Microsoft的引用。VisualBasic(是的,它说的是VisualBasic,但它在c#中也一样好用——记住,最后它都是IL)。

使用Microsoft.VisualBasic.FileIO.TextFieldParser类解析CSV文件

 Dim parser As TextFieldParser = New TextFieldParser("C:\mar0112.csv")
parser.TextFieldType = FieldType.Delimited
parser.SetDelimiters(",")


While Not parser.EndOfData
'Processing row
Dim fields() As String = parser.ReadFields
For Each field As String In fields
'TODO: Process field


Next
parser.Close()
End While

有一个库可以通过nuget来处理几乎任何格式良好的CSV (.net) - CsvHelper

映射到类的示例:

var csv = new CsvReader( textReader );
var records = csv.GetRecords<MyClass>();

读取单个字段的示例:

var csv = new CsvReader( textReader );
while( csv.Read() )
{
var intField = csv.GetField<int>( 0 );
var stringField = csv.GetField<string>( 1 );
var boolField = csv.GetField<bool>( "HeaderName" );
}
< p > 让客户端驱动文件格式: < br > ,是标准的字段分隔符,"是用于转义包含分隔符、引号或行结束符的字段的标准值

使用(例如)#用于字段,使用'用于转义:

var csv = new CsvReader( textReader );
csv.Configuration.Delimiter = "#";
csv.Configuration.Quote = ''';
// read the file however meets your needs

< a href = " http://joshclose.github。>更多文档 . io/CsvHelper/" rel="nofollow noreferrer">更多文档 . io/CsvHelper/" rel="nofollow noreferrer">

您可以使用其他“分隔符”,如“;”或“|”,但最简单的可能只是引用,这是大多数(体面的)CSV库和大多数体面的电子表格所支持的。

对于更多关于CSV分隔符和描述分隔符和引用的标准格式的规范,请参阅此网页

您可以像这样读取csv文件。

这利用了分割和空格。

ArrayList List = new ArrayList();
static ServerSocket Server;
static Socket socket;
static ArrayList<Object> list = new ArrayList<Object>();




public static void ReadFromXcel() throws FileNotFoundException
{
File f = new File("Book.csv");
Scanner in = new Scanner(f);
int count  =0;
String[] date;
String[] name;
String[] Temp = new String[10];
String[] Temp2 = new String[10];
String[] numbers;
ArrayList<String[]> List = new ArrayList<String[]>();
HashMap m = new HashMap();


in.nextLine();
date = in.nextLine().split(",");
name = in.nextLine().split(",");
numbers = in.nextLine().split(",");
while(in.hasNext())
{
String[] one = in.nextLine().split(",");
List.add(one);
}
int xount = 0;
//Making sure the lines don't start with a blank
for(int y = 0; y<= date.length-1; y++)
{
if(!date[y].equals(""))
{
Temp[xount] = date[y];
Temp2[xount] = name[y];
xount++;
}
}


date = Temp;
name =Temp2;
int counter = 0;
while(counter < List.size())
{
String[] list = List.get(counter);
String sNo = list[0];
String Surname = list[1];
String Name = list[2];
for(int x = 3; x < list.length; x++)
{
m.put(numbers[x], list[x]);
}
Object newOne = new newOne(sNo, Name, Surname, m, false);
StudentList.add(s);
System.out.println(s.sNo);
counter++;
}

如果你对如何解析一般文件(以CSV为例)更有教育意义的练习感兴趣,你可以查看Julian Bucknall的这篇文章。我喜欢这篇文章,因为它把事情分解成更小的问题,这些问题不那么难以克服。首先创建一个语法,一旦您有了一个好的语法,将语法转换为代码是一个相对简单和有条理的过程。

本文使用c#,并在底部有一个下载代码的链接。

如果你在*nix系统上,可以访问sed,并且你的CSV中可以有一个或多个不需要的逗号只在特定字段,你可以使用下面的一行程序将它们包含在"中,就像RFC4180建议的那样:

sed -r 's/([^,]*,[^,]*,[^,]*,)(.*)(,.*,.*)/\1"\2"\3/' inputfile
根据不需要的逗号可能在哪个字段中,您必须更改/扩展正则表达式的捕获组(和替换)。
上面的例子将第四个字段(六个字段中的第四个)括在引号中

enter image description here

结合--in-place-option,可以将这些更改直接应用到文件中。

为了“建造”;正确的正则表达式,有一个简单的原则要遵循:

  1. 对于CSV中来自之前的每个字段(带有不需要的逗号的字段),您编写一个[^,]*,并将它们放在一个捕获组中。
  2. 对于包含不需要的逗号的字段,可以写入(.*)
  3. 对于每个带有不需要的逗号的字段,您编写一个,.*并将它们放在一个捕获组中。

下面是根据特定字段的不同可能的正则表达式/替换的简短概述。如果没有给出,则替换为\1"\2"\3

([^,]*)(,.*)                     #first field, regex
"\1"\2                           #first field, substitution


(.*,)([^,]*)                     #last field, regex
\1"\2"                           #last field, substitution




([^,]*,)(.*)(,.*,.*,.*)          #second field (out of five fields)
([^,]*,[^,]*,)(.*)(,.*)          #third field (out of four fields)
([^,]*,[^,]*,[^,]*,)(.*)(,.*,.*) #fourth field (out of six fields)

如果你想用sed删除不需要的逗号,而不是用引号将它们括起来,请参考这个答案

我认为这个问题最简单的解决方案是让客户在excel中打开csv,然后按ctrl + r将所有逗号替换为任何你想要的标识符。这对客户来说非常简单,只需要对代码进行一次更改就可以读取所选的分隔符。

如果你想重新发明轮子,下面的方法可能对你有用:

public static IEnumerable<string> SplitCSV(string line)
{
var s = new StringBuilder();
bool escaped = false, inQuotes = false;
foreach (char c in line)
{
if (c == ',' && !inQuotes)
{
yield return s.ToString();
s.Clear();
}
else if (c == '\\' && !escaped)
{
escaped = true;
}
else if (c == '"' && !escaped)
{
inQuotes = !inQuotes;
}
else
{
escaped = false;
s.Append(c);
}
}
yield return s.ToString();
}

首先,让我们问问自己,“为什么我们觉得需要在CSV文件中以不同的方式处理逗号?”

对我来说,答案是,“因为当我将数据导出到CSV文件时,字段中的逗号消失了,我的字段被分离成多个字段,其中逗号出现在原始数据中。”(这是因为逗号是CSV字段分隔符。)

根据您的情况,分号也可以用作CSV字段分隔符。

根据我的要求,我可以使用一个字符,例如,单个低9引号,它看起来像一个逗号。

所以,下面是你在Go中如何做到这一点:

// Replace special CSV characters with single low-9 quotation mark
func Scrub(a interface{}) string {
s := fmt.Sprint(a)
s = strings.Replace(s, ",", "‚", -1)
s = strings.Replace(s, ";", "‚", -1)
return s
}

Replace函数中的第二个逗号字符是十进制8218。

请注意,如果您的客户端可能只有ascii文本阅读器,那么这个十进制8218字符看起来不会像逗号。如果这是你的情况,那么我建议在RFC 4128: https://www.rfc-editor.org/rfc/rfc4180中用逗号(或分号)和双引号包围字段

我通常url编码的字段可以有任何逗号或任何特殊字符。然后解码,当它被使用/显示在任何视觉媒体。

(逗号变为%2C)

每种语言都应该有url编码和解码字符串的方法。

例如,在Java中

URLEncoder.encode(myString,"UTF-8"); //to encode
URLDecoder.decode(myEncodedstring, "UTF-8"); //to decode

我知道这是一个非常普遍的解决方案,它可能不是理想的情况下,用户想要查看csv文件的内容,手动。

我通常在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();

使用制表符(\t)分隔字段。

正如我在对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;
}

在欧洲,我们有这个问题必须早于这个问题。在欧洲,我们用逗号来表示小数点。请看下面的数字:

| American      | Europe        |
| ------------- | ------------- |
| 0.5           | 0,5           |
| 3.14159265359 | 3,14159265359 |
| 17.54         | 17,54         |
| 175,186.15    | 175.186,15    |

因此,CSV文件不能使用逗号分隔符。由于这个原因,欧洲的CSV文件由分号(;)分隔。

像微软Excel这样的程序可以读取带有分号的文件,也可以从分隔符切换到分号。你甚至可以使用制表符(\t)作为分隔符。看到这是来自Supper User的回答

我发现的最简单的解决方案是LibreOffice使用的:

  1. 将所有的"文字替换为
  2. 在字符串周围加上双引号

你也可以使用Excel使用的:

  1. 将所有的"文字替换为""
  2. 在字符串周围加上双引号

注意,其他人建议只执行上面的第2步,但这对于"后面跟着,的行不起作用,就像在CSV中,你想要有一个字符串hello",world的单列,因为CSV会读为:

"hello",world"

它被解释为有两个列的行:helloworld"

    public static IEnumerable<string> LineSplitter(this string line, char
separator, char skip = '"')
{
var fieldStart = 0;
for (var i = 0; i < line.Length; i++)
{
if (line[i] == separator)
{
yield return line.Substring(fieldStart, i - fieldStart);
fieldStart = i + 1;
}
else if (i == line.Length - 1)
{
yield return line.Substring(fieldStart, i - fieldStart + 1);
fieldStart = i + 1;
}


if (line[i] == '"')
for (i++; i < line.Length && line[i] != skip; i++) { }
}


if (line[line.Length - 1] == separator)
{
yield return string.Empty;
}
}

我使用Csvreader库,但通过使用它,我从列值中的逗号(,)爆炸获得数据。

所以如果你想插入CSV文件数据,其中包含逗号(,)在大多数列值,你可以使用以下函数。 作者链接=> https://gist.github.com/jaywilliams/385876

function csv_to_array($filename='', $delimiter=',')
{
if(!file_exists($filename) || !is_readable($filename))
return FALSE;


$header = NULL;
$data = array();
if (($handle = fopen($filename, 'r')) !== FALSE)
{
while (($row = fgetcsv($handle, 1000, $delimiter)) !== FALSE)
{
if(!$header)
$header = $row;
else
$data[] = array_combine($header, $row);
}
fclose($handle);
}
return $data;
}

我使用papaParse库来解析CSV文件,并拥有键-值对(键/头/ CSV文件-值的第一行)。

下面是我举的例子:

< a href = " https://codesandbox。Io /embed/llqmrp96pm" rel="nofollow noreferrer">https://codesandbox.io/embed/llqmrp96pm

它有一个dummy.csv文件来演示CSV解析。

我在reactJS中使用过它,尽管它很容易在任何语言编写的应用程序中复制。

这里有一个简单的变通方法:

你可以使用希腊数字符号代替(U+0375)

看上去就像这样

使用这种方法也可以节省很多资源…

在NuGet上使用SoftCircuits。CsvParser即可。它将为您处理所有这些细节,并有效地处理非常大的文件。如果需要,它甚至可以通过将列映射到对象属性来导入/导出对象。此外,我的测试显示,它的平均速度比流行的CsvHelper快近4倍。

一个示例可能有助于说明如何在.csv文件中显示逗号。创建一个简单的文本文件,如下所示:

将此文本文件另存为后缀为“。csv”的文本文件,在Windows 10下使用Excel 2000打开。

< p > aa、bb、cc, d, d "在电子表格演示中,下面的行应该看起来像上面的行,只是下面在d之间显示了一个逗号而不是分号。" aa,bb,cc,"d,d",即使在Excel

aa,bb,cc,"d,d",即使在Excel 2000中也是如此 aa,bb,cc,"d,d",即使在Excel 2000中也是如此 aa,bb,cc,"d, d",即使在Excel 2000

aa,bb,cc, " d,d",在Excel 2000中由于第一个引号前面的空格而失败 aa,bb,cc, " d,d",这在Excel 2000中失败,因为在第一个引用之前有空格 aa,bb,cc, " d, d",这在Excel 2000中失败,因为第一个引号

aa,bb,cc,"d,d ",即使在Excel 2000中,即使在第二引号前后有空格,这也是有效的。 aa,bb,cc,"d,d ",即使在Excel 2000中,即使在第2个引号前后有空格,这也是有效的。 aa,bb,cc,"d, d ",即使在Excel 2000中,即使在第二引号前后有空格,这也是有效的

规则:如果你想在csv文件的单元格(字段)中显示一个逗号: "用双引号开始和结束字段,但避免第一个引号前有空格"

我知道已经13年了,但我们遇到了类似的情况,客户端向我们输入了一个CSV,值带有逗号,有2个用例:

  1. 如果客户端使用windows Excel客户端来写入CSV(通常在windows环境中是这种情况),则逗号被添加到值中。 李enter image description here < / >

CSV的实际文本值:

3786962,1st Meridian Care Services,John,"Person A,Person B, Person C, Person D",Voyager
  1. 如果客户端以编程的方式向你发送excel,那么他应该坚持RFC4180并将值包含在“quotes"中。例子:

Col1, Col2, "a, b, c", Col4