Dealing with commas in a CSV file
我正在寻找有关如何处理正在创建、然后由客户上载的csv文件的建议,这些文件的值中可能有逗号,例如公司名称。
我们正在研究的一些想法是:带引号的标识符(值、值等)或使用而不是逗号。最大的问题是我们必须让它变得简单,否则客户就不会这么做。
2017年,CSV是完全具体的-RFC 4180。
这是一个非常常见的规格,由许多图书馆完全覆盖(例如)。
Simply use any easily-available CSV library-that is to say RFC 4180.
CSV Format和How to handle commas:
BLCK1/
http://tools.ietf.org/html/rfc4180
所以,为了获得
1 | foo,"bar,baz" |
另一项重要要求是考虑(另见SPEC):
If double-quotes are used to enclose fields, then a double-quote
appearing inside a field must be escaped by preceding it with
another double quote. For example:
1 "aaa","b""bb","ccc"
如其他人所说,你需要逃避价值,包括分摊会费。这是一个小CSV阅读器在C?这包括嵌入式配额和载运回报。
以这种方式,这是单位测试码。我现在寄这封信是因为这个问题看起来像是一个大问题,而其他人可能不需要一个整体的图书馆,只要简单的CSV支持。
你可以把它当作:
ZZU1
这里是班级。注:您可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | 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 +=" " + 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 = { ',', '"', ' ' }; } |
The CSV format use commas to separate values,values which contains carriage returns,linefeeds,commas,or double contributions are surrounded by double-contributions.Values that contain doubles are quoted and each literal quote is escaped by an immediately preceding quote:for example,the 3 values:
1 2 3 | test list, of, items "go" he said |
Would be coded as:
1 2 3 | test "list, of, items" """go"" he said" |
任何一个领域都可能是quoted,但只包括COMAS,CR/NL,或必须缴纳分摊会费的领域。
CSV格式没有实际的标准,但在这里,几乎所有的应用程序都跟着公约文件。The RFC that was mentioned elsewhere is not a standard for CSV,it is an RFC for using CSV within mime and contains some unconventional and unnecessary limitations that make it useless outside of mime.
a gotcha that many CSV modules I have seen not accommodate is the fact that multiple lines can be coded in a single field which means you can't accept that each line is a separate record,you need to not allow newlines in your data or be prepared to handle this.
把双重摊款围在弦上。这是一般的例外。
Ala Eli
you escape a double quote as two
double quotes. E.g.
"test1","foo""bar","test2"
你可以把双重摊款放在田野周围。我不喜欢这种方法,因为它增加了另一个特殊的特征(双商)。只是确定一个逃避特征(通常是后台),并在你需要逃避的地方使用它:
1 | data,more data,more data\, even,yet more |
你不必尝试对阵配额,你也有一些例外。这简化了你的密码。
有一个库可以通过nuget处理几乎所有格式良好的csv(.net)-csvhelper
映射到类的示例:
1 2 | var csv = new CsvReader( textReader ); var records = csv.GetRecords<MyClass>(); |
读取单个字段的示例:
1 2 3 4 5 6 7 | 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" ); } |
号
让客户机驱动文件格式:
用(例如)
1 2 3 4 | var csv = new CsvReader( textReader ); csv.Configuration.Delimiter ="#"; csv.Configuration.Quote = '''; // read the file however meets your needs |
更多文档
如果您使用的是*nix系统,可以访问
1 | sed -r 's/([^,]*,[^,]*,[^,]*,)(.*)(,.*,.*)/\1"\2"\3/' inputfile |
根据不需要的逗号在哪个字段中,您必须更改/扩展regex(和替换)的捕获组。上面的示例将第四个字段(共六个)用引号括起来。
。
与
为了"构建"正确的regex,需要遵循一个简单的原则:
以下是根据特定字段的不同可能的正则表达式/替换的简短概述。如果没有给出,则替换为
1 2 3 4 5 6 7 8 9 10 | ([^,]*)(,.*) #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) |
。
如果要删除不需要的逗号(带EDOCX1[4]),而不是用引号括起来,请参阅此答案。
正如我对Harpo答案的评论中所提到的,他的解决方案很好,在大多数情况下都有效,但是在某些情况下,当逗号直接相邻时,它无法在逗号上拆分。
这是因为regex字符串意外地表现为一个可转换字符串。为了使这个行为正确,需要手动转义regex字符串中的所有"字符,而不使用Vertabim转义。
也就是说,正则表达式应该是使用手动转义的:
翻译成
当使用一个Vertabim字符串
1 | ",(?=(?:[^"]*"[^"]*")*(?![^"]*"))" |
。
所以总的来说,我推荐哈珀的解决方案,但要注意这个小问题!
我在csvreader中加入了一些可选的failsafe,以便在出现此错误时通知您(如果您有预先知道的列数):
1 2 | if (_expectedDataLength > 0 && values.Length != _expectedDataLength) throw new DataLengthException(string.Format("Expected {0} columns when splitting csv, got {1}", _expectedDataLength, values.Length)); |
这可以通过构造函数注入:
1 2 3 4 | public CsvReader(string fileName, int expectedDataLength = 0) : this(new FileStream(fileName, FileMode.Open, FileAccess.Read)) { _expectedDataLength = expectedDataLength; } |
。
Add a reference to the Microsoft.Visualbasic(yes,it says visualbasic but it works in C 35;just as well-remember that the end it is all just il).
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 |
您可以使用诸如";"或""之类的其他"分隔符",但最简单的方法可能只是引用大多数(合适的)csv库和最合适的电子表格支持的内容。
有关csv分隔符和描述分隔符和引用的标准格式的规范的更多信息,请参见本网页。
在欧洲,这个问题必须早于这个问题。在欧洲,我们都用逗号作为小数点。请参阅下面的数字:
1 2 3 4 5 6 | | American | Europe | | ------------- | ------------- | | 0.5 | 0,5 | | 3.14159265359 | 3,14159265359 | | 17.54 | 17,54 | | 175,186.15 | 175.186,15 | |
。
所以不能对csv文件使用逗号分隔符。因此,欧洲的csv文件用分号分隔(
像Microsoft Excel这样的程序可以用分号读取文件,并且可以从分隔符切换。甚至可以使用制表符(
如果你想重新发明轮子,以下可能对你有用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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为例)的更具教育意义的练习感兴趣,可以查看JulianBucknall的这篇文章。我喜欢这篇文章,因为它把事情分解成小得多的问题,而这些问题更不难克服。首先创建一个语法,一旦有了一个好的语法,将语法转换为代码是一个相对简单和有条理的过程。
本文使用C并在底部有一个链接来下载代码。
我使用paparse库对csv文件进行解析,并使用键值对(key/header/csv文件值的第一行)。
下面是我使用的示例:
https://codesandbox.io/embed/llqmrp96pm
里面有dummy.csv文件,可以进行csv解析演示。
我在ReactJS中使用过它,尽管在用任何语言编写的应用程序中复制它既简单又容易。
您可以这样读取csv文件。
这就利用了分割,并考虑了空间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | 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文件以不同的方式处理逗号?"
对于我来说,答案是,"因为当我将数据导出到一个csv文件中时,字段中的逗号消失,字段被分隔成多个字段,其中逗号出现在原始数据中。"(这是因为逗号是csv字段分隔符。)
根据您的情况,分号也可以用作csv字段分隔符。
根据我的要求,我可以使用一个像逗号一样的字符,例如,单个低9引号。
所以,下面是如何在Go中做到这一点:
1 2 3 4 5 6 7 | // 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函数中的第二个逗号字符是decimal 8218。
请注意,如果您的客户机可能只有ASCII文本阅读器,那么这个decima 8218字符看起来不会像逗号。如果这是您的情况,那么我建议根据RFC 4128用逗号(或分号)包围该字段:https://tools.ietf.org/html/rfc4180
我通常对字段进行URL编码,这些字段可以有任何逗号或任何特殊字符。然后在任何视觉媒体中使用/显示时对其进行解码。
(逗号变为%2c)
每种语言都应该有URL编码和解码字符串的方法。
例如,在Java中
1 2 | URLEncoder.encode(myString,"UTF-8"); //to encode URLDecoder.decode(myEncodedstring,"UTF-8"); //to decode |
号
我知道这是一个非常通用的解决方案,对于用户想要手动查看csv文件内容的情况来说,这可能不是理想的解决方案。
我通常在我的csv文件解析例程中这样做。假设"line"变量是csv文件中的一行,并且所有列的值都用双引号括起来。在执行下面的两行之后,您将在"values"集合中获得csv列。
1 2 3 | // 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(); |
As this is about general practices let's start from rules of the thumb:
不要使用CSV,用XML与图书馆一起阅读和写入XML文件。
如果你需要使用CSV请预先准备并使用一个自由图书馆,以便存储CSV文件。
如果你没有和美国信息交换标准码做交易,那么最常见的CSV Parsers are not excoding away so if you are not dealing with US-ASCII-you're asking for disable.For excel 2002 is storing the CSV in local encoding without any note about the encoding.The CSV standard isn't widely adopted:()在另一个XML标准中,它得到了很好的采用,并处理了很好的编码。
为了公正起见(2),CSV parsers around for almost all language so there is no need to reinvent the wheel even if the solutions looks pretty simple.
To name few:
-
在CSV模块中为Python使用建造
-
For perl check cpan and text::csv
-
For PHP use build in FGETCSV/FPUTCSV functions
-
爪哇超级CVS图书馆
如果你不在嵌入式设备上安装,就不需要用手来实现这一点。
我发现最简单的解决方案是libreoffice使用的解决方案:
您也可以使用Excel使用的:
请注意,其他人建议只执行上面的步骤2,但这不适用于
1 | "hello",world" |
它被解释为一行,有两列:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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; } |
我认为解决这个问题最简单的方法是让客户在Excel中打开csv,然后按ctrl+r将所有逗号替换为所需的任何标识符。这对客户来说非常简单,只需要在代码中更改一次就可以读取所选的分隔符。
使用制表符( )分隔字段。