处理CSV文件中的逗号
我正在寻找关于如何处理正在创build,然后由我们的客户上传的csv文件的build议,并且可能会有逗号(例如公司名称)。
我们所看到的一些想法是:引用标识符(值“,”值“,”等)或使用| 而不是逗号。 最大的问题是我们必须简化,否则客户不会这样做。
正如其他人所说,你需要逃避价值,包括报价。 C Here中有一个支持引用值的CSV阅读器,包括embedded式引号和回车符。
顺便说一句,这是unit testing的代码。 我现在发布它,因为这个问题似乎出现了很多,而其他人可能不希望整个图书馆,当简单的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' }; }
对于2017年,csv完全指定 – RFC 4180。
这是一个非常常见的规范,并被许多库( 例子 )完全覆盖。
只需使用任何容易获得的csv库 – 也就是说RFC 4180。
实际上有一个CSV格式的规范,以及如何处理逗号:
包含换行符(CRLF),双引号和逗号的字段应用双引号括起来。
http://tools.ietf.org/html/rfc4180
所以,要bar,baz
foo
和bar,baz
,你这样做:
foo,"bar,baz"
另一个需要考虑的重要要求(也来自规范):
如果使用双引号将字段括起来,则出现在字段内的双引号必须通过在另一个双引号之前进行转义来进行转义。 例如:
"aaa","b""bb","ccc"
CSV格式使用逗号分隔值,包含回车符,换行符,逗号或双引号的值由双引号括起来。 包含双引号的值被引用,并且每个文字引号都被紧接的前面的引号转义:例如,3个值:
test list, of, items "go" he said
将被编码为:
test "list, of, items" """go"" he said"
任何字段都可以被引用,但只有包含逗号,CR / NL或引号的字段必须被引用。
CSV格式没有真正的标准 ,但几乎所有的应用程序都遵循这里logging的约定。 其他地方提到的RFC不是CSV的标准,它是在MIME中使用CSV的RFC,并且包含一些非常规和不必要的限制,使得它在MIME之外毫无用处。
我见过的很多CSV模块都不能容纳的一个问题是,多行可以在单个字段中编码,这意味着您不能假设每行都是单独的logging,您不需要在您的行中允许换行数据或准备处理这个。
把双引号放在string周围。 这通常是Excel所做的 。
Ala Eli,
你把双引号作为两个双引号。 例如“test1”,“foo”,“bar”,“test2”
你可以在字段中加双引号。 我不喜欢这种方法,因为它增加了另一个特殊的字符(双引号)。 只需定义一个转义字符(通常是反斜杠),然后在需要转义的地方使用它:
数据,更多的数据,更多的数据,甚至更多
您不必尝试匹配引号,而且parsing的例外也更less。 这也简化了您的代码。
有一个库可通过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" ); }
让客户端驱动文件格式:
,
是标准字段分隔符, "
是用于转义包含分隔符,引号或行结尾的字段的标准值。
要使用(例如) #
作为字段和'
转义:
var csv = new CsvReader( textReader ); csv.Configuration.Delimiter = "#"; csv.Configuration.Quote = '''; // read the file however meets your needs
更多文档
添加一个对Microsoft.VisualBasic的引用(是的,它说的是VisualBasic,但它也可以在C#中工作 – 记住最后它只是IL)。
使用Microsoft.VisualBasic.FileIO.TextFieldParser
类来parsingCSV文件以下是示例代码:
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
如果您使用的是* nix系统 ,请使用sed
并且只能在您的CSV 的特定字段中使用一个或多个不需要的逗号 ,则可以使用以下单行命令将其括在"
as RFC4180第2节提出:
sed -r 's/([^,]*,[^,]*,[^,]*,)(.*)(,.*,.*)/\1"\2"\3/' inputfile
根据不需要的逗号可能在哪个字段中,您必须更改/扩展正则expression式的捕获组(和replace)。
上面的例子将引号中的第四个字段(六个中)。
结合“ --in-place
您可以将这些更改直接应用于文件。
为了“构build”正确的正则expression式,有一个简单的原则:
- 对于CSV字段中带有不需要的逗号的字段中的每个字段,您都会写一个
[^,]*,
并将它们放在一个捕获组中。 - 对于包含您写入的不需要的逗号的字段
(.*)
。 - 对于有多余逗号的字段后面的每个字段,您都会写一个
,.*
然后将它们放在一个捕获组中。
以下是根据特定领域的不同可能的正则expression式/replace的简要概述。 如果没有给出,替代是\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
删除不需要的逗号(而不是引号),请参考此答案 。
您可以使用“;”等替代“分隔符” 或“|” 但最简单的可能只是引用大多数(体面的)CSV库和最合适的电子表格。
有关CSV分隔符的更多信息,以及用于描述分隔符和引用的标准格式的规范,请参阅此网页
如果您对如何parsing文件(例如使用CSV)进行更多的教育练习感兴趣,可以查看Julian Bucknall 撰写的这篇文章 。 我喜欢这篇文章,因为它把事情分解成小得多的问题,而这些问题远远不能克服。 你首先创build一个语法,一旦你有一个好的语法,把语法转换成代码是一个相对简单和有条不紊的过程。
本文使用C#并在底部有一个链接来下载代码。
如果您想重新发明轮子,以下内容可能适合您:
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,使用XML和库来读取和写入XML文件。
-
如果您必须使用CSV。 正确使用并使用免费库来parsing和存储CSV文件。
为了certificate1),大多数CSVparsing器都不能识别编码,所以如果你没有处理US-ASCII,那你就是要求麻烦了。 例如,excel 2002以本地编码的forms存储CSV,而没有任何关于编码的注释。 CSV标准没有被广泛采用:(另一方面,xml标准被很好地采用,并且处理编码相当好。
为了certificate2),几乎所有的语言都有大量的csvparsing器,所以即使解决scheme看起来很简单,也不需要重新发明轮子。
仅举几例:
-
为python使用生成在csv模块
-
perl检查CPAN和Text :: CSV
-
为PHP使用build立在fgetcsv / fputcsvfunction
-
为java检查SuperCVS库
如果你不打算在embedded式设备上parsing它,真的没有必要手工实现。
你可以像这样读取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++; }
我认为这个问题的最简单的解决scheme是让客户打开Excel中的CSV,然后按Ctrl + R来replace所有您想要的标识符的逗号。 这对于客户来说非常简单,只需要对代码进行一次更改就可以读取您select的分隔符。
首先,让我们问自己:“为什么我们觉得需要对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-only文本阅读器,这个decima 8218字符看起来不会像逗号。 如果这是你的情况,那么我build议围绕领域与逗号(或分号)双引号每个RFC 4128: https : //tools.ietf.org/html/rfc4180
我通常对可以有任何逗号或特殊字符的字段进行URL编码。 然后在任何可视媒体中使用/显示时进行解码。
(逗号变成%2C)
每种语言都应该有对URL进行URL编码和解码的方法。
例如,在Java中
URLEncoder.encode(myString,"UTF-8"); //to encode URLDecoder.decode(myEncodedstring, "UTF-8"); //to decode
我知道这是一个非常通用的解决scheme,对于用户想要手动查看csv文件内容的情况,这可能并不理想。
我通常在我的CSV文件parsing例程中执行此操作。 假设“行”variables是CSV文件中的一行,所有列的值都用双引号引起来。 在执行以下两行后,您将在“值”集合中获得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();
正如我在对哈波的回答中提到的那样,他的解决scheme很好,在大多数情况下都是有效的,但是在某些情况下,当逗号直接相邻时,它们不能在逗号分割。
这是因为正则expression式string行为意外作为一个vertabimstring。 为了得到这个行为是正确的,正则expression式string中的所有字符都需要手动转义,而不使用vertabim转义。
IE浏览器。 正则expression式应该是这个使用手动转义:
",(?=(?:[^\"\"]*\"\"[^\"\"]*\"\")*(?![^\"\"]*\"\"))"
",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))"
当使用一个vertabimstring@",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))"
performance为以下,你可以看到,如果你debugging正则expression式:
",(?=(?:[^"]*"[^"]*")*(?![^"]*"))"
所以总之,我推荐harpo的解决scheme,但要注意这个小问题!
我已经包含到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文件用分号( ;
)分隔。
像Microsoft Excel这样的程序可以用分号读取文件,并且可以从分隔符切换。 你甚至可以使用标签( \t
)作为分隔符。 从晚饭用户看到这个答案 。
使用制表符(\ t)分隔字段。