首页 文章

在CSV文件中处理逗号

提问于
浏览
428

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

我们正在研究的一些想法是:引用标识符(值“,”值“,”等)或使用|而不是逗号 . 最大的问题是我们必须让它变得简单,否则客户就不会这样做 .

23 回答

  • 3

    我通常对可以有任何逗号或任何特殊字符的字段进行URL编码 . 然后在任何视觉媒体中使用/显示时对其进行解码 .

    (逗号变为%2C)

    每种语言都应该有对URL进行URL编码和解码的方法 .

    例如,在java中

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

    我知道这是一个非常通用的解决方案,对于用户想要手动查看csv文件内容的情况,它可能不太理想 .

  • 0

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

    CSV格式使用逗号分隔值,包含回车符,换行符,逗号或双引号的值由双引号括起 . 引用包含双引号的值,并使用前一个引号对每个文字引号进行转义:例如,3个值:

    test
    list, of, items
    "go" he said
    

    将被编码为:

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

    可以引用任何字段,但只能引用包含逗号,CR / NL或引号的字段 .

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

    我见过的许多CSV模块都不能容纳的事实是,在一个字段中可以编码多行,这意味着你不能假设每一行都是一个单独的记录,你要么不允许你的换行符数据或准备处理此事 .

  • 3

    在字符串周围加上双引号 . 这通常是what Excel does .

    Ala Eli,

    你以两个双引号来逃避双引号 . 例如 . “测试1”,“富”,“酒吧”,“TEST2”

  • 0

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

    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();
    }
    
  • 2

    正如我在对harpo的回答中所提到的,他的解决方案很好并且在大多数情况下都适用,但是在某些情况下,当逗号彼此直接相邻时,它无法在逗号上分割 .

    这是因为正则表达式字符串意外地表现为vertabim字符串 . 为了使这种行为正确,需要手动转义正则表达式字符串中的所有“字符而不使用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;
    }
    
  • 3

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

    您可以像这样阅读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++;
             }
    
  • 2
    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;
            }
        }
    
  • 0

    对于2017年,csv已完全指定 - RFC 4180 .

    这是一个非常常见的规范,并且被许多库完全覆盖(example) .

    Simply use any easily-available csv library - 也就是说RFC 4180 .


    实际上有CSV格式的规范以及如何处理逗号:

    包含换行符(CRLF),双引号和逗号的字段应括在双引号中 .

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

    因此,要获得值 foobar,baz ,请执行以下操作:

    foo,"bar,baz"
    

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

    如果使用双引号括起字段,则必须通过在其前面加上另一个双引号来转义出现在字段内的双引号 . 例如:“aaa”,“b”“bb”,“ccc”

  • 0

    由于这是关于一般做法,让我们从拇指规则开始:

    • 不要使用CSV,请将XML与库一起使用来读取和写入xml文件 .

    • 如果必须使用CSV . 正确执行并使用免费库来解析和存储CSV文件 .

    至证明1),大多数CSV解析器不能识别编码,所以如果你不处理US-ASCII,你就会遇到麻烦 . 例如,excel 2002将CSV存储在本地编码中,而没有任何关于编码的注释 . CSV标准没有被广泛采用:( . 另一方面,xml标准很好用,它很好地处理编码 .

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

    仅举几例:

    • for python使用csv模块构建

    • 用于perl检查CPAN和Text::CSV

    • for php使用fgetcsv / fputcsv函数构建

    • for java check SuperCVS library

    实际上,如果您不打算在嵌入式设备上解析它,则无需手动实现 .

  • 37

    首先,让我们问自己,“为什么我们觉得需要以不同的方式处理逗号的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
    }
    

    替换函数中的第二个逗号字符是十进制8218 .

    请注意,如果您的客户端可能只有ascii文本阅读器,则此decima 8218字符将不会像逗号一样 . 如果这是你的情况,那么我建议使用逗号(或分号)围绕字段,每个RFC 4128使用双引号:https://tools.ietf.org/html/rfc4180

  • -2

    添加对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
    
  • 369

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

  • 0

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

    对于more on CSV delimiters and a spec for a standard format for describing delimiters and quoting see this webpage

  • 7

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

    • 将所有文字 " 替换为

    • 在字符串周围加上双引号

    您还可以使用Excel使用的那个:

    • 将所有文字 " 替换为 ""

    • 在字符串周围加上双引号

    请注意其他人建议仅执行上面的步骤2,但这不适用于 " 后跟 , 的行,就像在CSV中您希望单个列包含字符串 hello",world ,因为CSV会读取:

    "hello",world"
    

    这被解释为具有两列的行: helloworld"

  • 6

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

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

    您不必尝试匹配引号,并且您解析的异常更少 . 这也简化了您的代码 .

  • 0

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

    Example to map to a class:

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

    Example to read individual fields:

    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" );
    }
    

    Letting the client drive the file format:
    , 是标准字段分隔符, " 是用于转义包含分隔符,引号或行结尾的字段的标准值 .

    要使用(例如) # 表示字段,使用 ' 表示转义:

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

    More Documentation

  • 70

    在欧洲,我们有这个问题必须早于这个问题 . 在欧洲,我们使用逗号作为小数点 . 请看下面这个数字:

    | 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 )作为分隔符 . 见this answer from Supper User .

  • 2

    我使用了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;
    }
    
  • 208

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

  • 0

    如果您对如何解析文件的更多教育练习感兴趣(使用CSV作为示例),您可以查看Julian Bucknall的this article . 我喜欢这篇文章,因为它将问题分解为更小的问题这是不太难以克服的 . 首先创建一个语法,一旦你有一个好的语法,将语法转换为代码是一个相对简单和有条理的过程 .

    本文使用C#并在底部有一个链接来下载代码 .

  • 0

    如果您使用 *nix-system ,可以访问 sed 并且可以有一个或多个 unwanted commas only in a specific field 的CSV,您可以使用以下单行,以便将它们包含在 " 中,因为RFC4180 Section 2建议:

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

    根据不需要的逗号所在的字段,您必须更改/扩展正则表达式的捕获组(以及替换) .
    上面的示例将第四个字段(六个中)括在引号中 .

    enter image description here

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

    为了“构建”正确的正则表达式,有一个简单的原则可以遵循:

    • 对于CSV中包含不需要的逗号的字段之前的每个字段,您都要编写一个 [^,]*, 并将它们全部放在一个捕获组中 .

    • 对于包含不需要的逗号的字段,您可以编写 (.*) .

    • 对于包含不需要的逗号的字段后面的每个字段,您编写一个 ,.* 并将它们全部放在一个捕获组中 .

    以下是根据具体字段对不同可能的正则表达式/替换进行的简短概述 . 如果没有给出,替换是 \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 的不需要的逗号而不是用引号括起它们,请参阅this answer .

相关问题