在CSV文件中处理逗号


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

所以,为了获得foobar,baz,你这样做:

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

这里是班级。注:您可以使用Csv.Escape函数写入有效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
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系统,可以访问sed,并且在csv的特定字段中只能有一个或多个不需要的逗号,您可以使用以下一行来将它们括在"中,正如rfc4180第2节建议的那样:

1
sed -r 's/([^,]*,[^,]*,[^,]*,)(.*)(,.*,.*)/\1"\2"\3/' inputfile

根据不需要的逗号在哪个字段中,您必须更改/扩展regex(和替换)的捕获组。上面的示例将第四个字段(共六个)用引号括起来。

enter image description here

--in-place选项结合使用,您可以直接将这些更改应用于文件。

为了"构建"正确的regex,需要遵循一个简单的原则:

  • 对于csv中出现在带有不需要的逗号的字段之前的每个字段,编写一个[^,]*,,并将它们放在一个捕获组中。
  • 对于包含不需要的逗号的字段,编写(.*)
  • 对于带有不需要的逗号的字段之后的每个字段,您编写一个,.*,并将它们放在一个捕获组中。
  • 以下是根据特定字段的不同可能的正则表达式/替换的简短概述。如果没有给出,则替换为\1"\2"\3

    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字符串@",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))"时,它的行为如下:如果您调试regex,可以看到:

    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).

    使用Microsoft.VisualBasic.FileIO.TextFieldParser这是本样本代码:

    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这样的程序可以用分号读取文件,并且可以从分隔符切换。甚至可以使用制表符(\t作为分隔符。请参见晚餐用户的回答。


    如果你想重新发明轮子,以下可能对你有用:

    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,但这不适用于"后面跟着,的行,就像在csv中一样,在csv中,您希望使用字符串hello",world的单个列,如csv所示:

    1
    "hello",world"

    它被解释为一行,有两列:helloworld"


    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将所有逗号替换为所需的任何标识符。这对客户来说非常简单,只需要在代码中更改一次就可以读取所选的分隔符。


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