在某些情况下使用双引号解析 CSV

Posted

技术标签:

【中文标题】在某些情况下使用双引号解析 CSV【英文标题】:Parse CSV with double quote in some cases 【发布时间】:2011-12-09 16:08:47 【问题描述】:

我有带有格式的 csv:

a1, a2, a3, "a4,a5", a6

只有带 , 的字段才会有引号

使用Java,如何轻松解析这个?我尽量避免使用开源 CSV 解析器作为公司政策。谢谢。

【问题讨论】:

不知道很容易,CSV 有一些微妙的边缘情况:转义引号 - 使用多种样式;和字段值中的换行符——如果你必须用它们发生的 CSV 行报告错误,这很有趣。如果您不能使用现有的解析器并且可能不得不处理这些,请编写一个解析器。 (如果不允许使用解析器生成器,这样做也很有趣。) 如果公司要求没有开源库(无论许可证),并且您需要简单解析的帮助... @Inerdia,解析大概30行手写代码,不需要生成器。 Parsing CSV in java 的可能重复项 【参考方案1】:

下面的代码似乎运行良好,可以处理引号内的引号。

final static Pattern quote = Pattern.compile("^\\s*\"((?:[^\"]|(?:\"\"))*?)\"\\s*,");

public static List<String> parseCsv(String line) throws Exception
       
    List<String> list = new ArrayList<String>();
    line += ",";

    for (int x = 0; x < line.length(); x++)
    
        String s = line.substring(x);
        if (s.trim().startsWith("\""))
        
            Matcher m = quote.matcher(s);
            if (!m.find())
                throw new Exception("CSV is malformed");
            list.add(m.group(1).replace("\"\"", "\""));
            x += m.end() - 1;
        
        else
        
            int y = s.indexOf(",");
            if (y == -1)
                throw new Exception("CSV is malformed");
            list.add(s.substring(0, y));
            x += y;
        
    
    return list;

【讨论】:

【参考方案2】:

这是给你的一些代码,我希望使用这里的代码不算开源,这是。

package bestsss.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class SplitCSVLine 
    public static String[] splitCSV(BufferedReader reader) throws IOException
        return splitCSV(reader, null, ',', '"');
    

    /**
     * 
     * @param reader - some line enabled reader, we lazy
     * @param expectedColumns - convenient int[1] to return the expected
     * @param separator - the C(omma) SV (or alternative like semi-colon) 
     * @param quote - double quote char ('"') or alternative
     * @return String[] containing the field
     * @throws IOException
     */
    public static String[] splitCSV(BufferedReader reader, int[] expectedColumns, char separator, char quote) throws IOException       
        final List<String> tokens = new ArrayList<String>(expectedColumns==null?8:expectedColumns[0]);
        final StringBuilder sb = new StringBuilder(24);

        for(boolean quoted=false;;sb.append('\n')) //lazy, we do not preserve the original new line, but meh
            final String line = reader.readLine();
            if (line==null)
                break;
            for (int i = 0, len= line.length(); i < len; i++)  
                final char c = line.charAt(i);
                if (c == quote) 
                    if( quoted   && i<len-1 && line.charAt(i+1) == quote )//2xdouble quote in quoted 
                        sb.append(c);
                        i++;//skip it
                    else
                        if (quoted)
                            //next symbol must be either separator or eol according to RFC 4180
                            if (i==len-1 || line.charAt(i+1) == separator)
                                quoted = false;
                                continue;
                            
                         else//not quoted
                            if (sb.length()==0)//at the very start
                                quoted=true;
                                continue;
                            
                        
                        //if fall here, bogus, just add the quote and move on; or throw exception if you like to
                        /*
                        5.  Each field may or may not be enclosed in double quotes (however
                           some programs, such as Microsoft Excel, do not use double quotes
                           at all).  If fields are not enclosed with double quotes, then
                           double quotes may not appear inside the fields.
                      */ 
                        sb.append(c);                   
                    
                 else if (c == separator && !quoted) 
                    tokens.add(sb.toString());
                    sb.setLength(0); 
                 else 
                    sb.append(c);
                
            
            if (!quoted)
                break;      
        
        tokens.add(sb.toString());//add last
        if (expectedColumns !=null)
            expectedColumns[0] = tokens.size();
        return tokens.toArray(new String[tokens.size()]);
    
    public static void main(String[] args) throws Throwable
        java.io.StringReader r = new java.io.StringReader("222,\"\"\"zzzz\", abc\"\" ,   111   ,\"1\n2\n3\n\"");
        System.out.println(java.util.Arrays.toString(splitCSV(new BufferedReader(r))));
    

【讨论】:

【参考方案3】:

我遇到了同样的问题(但在 Python 中),我发现解决它的一种方法是: 当你得到该行时,检查任何引号,如果有引号,将字符串拆分为引号,并将结果数组的偶数索引结果拆分为逗号。奇数索引字符串应该是完整的引用值。

我不是 Java 编码员,所以把它当作伪代码...

line = String[];
    if ('"' in row)
        vals = row.split('"');
        for (int i =0; i<vals.length();i+=2)
            line+=vals[i].split(',');
        
        for (int j=1; j<vals.length();j+=2)
            line+=vals[j];
        
    
    else
        line = row.split(',')
    

或者,使用正则表达式。

【讨论】:

【参考方案4】:

您可以将Matcher.find 与以下正则表达式一起使用:

\s*("[^"]*"|[^,]*)\s*

这里有一个更完整的例子:

String s = "a1, a2, a3, \"a4,a5\", a6";
Pattern pattern = Pattern.compile("\\s*(\"[^\"]*\"|[^,]*)\\s*");
Matcher matcher = pattern.matcher(s);
while (matcher.find()) 
    System.out.println(matcher.group(1));

在线查看:ideone

【讨论】:

更一般地说,在 CSV 文件中,只要包含分隔符、换行符和/或引号,值就会用引号括起来…… @Mark,双引号("")用来表示单个"。另外,使用regExp也不过分 这不起作用,因为它会在元素之间添加空字符串,如果 csv 中有空单元格,则会产生问题。 这是一个更好的答案(不添加空字符串):***.com/a/15739087/1068385

以上是关于在某些情况下使用双引号解析 CSV的主要内容,如果未能解决你的问题,请参考以下文章

Python解析CSV忽略带双引号的逗号

Boost tokenizer 无法解析具有双引号字段的 csv 文件

正则表达式在csv中找到缺少的双引号

Windows cmd.exe 命令传递 Perl 系统函数在某些情况下需要双引号吗?

停止访问将两组双引号写入 csv

Access 解析 CSV 文件中的双引号的问题