解析csv,不要在单个或双引号内分割

时间:2015-12-15 15:58:42

标签: java csv opencsv

我尝试用java解析csv并遇到以下问题:第二列是一个用双引号括起来的String(也可能包含逗号),除非字符串本身包含双引号,那么整个字符串附有单引号。 e.g。

行可能会像这样:

someStuff,"hello", someStuff
someStuff,"hello, SO", someStuff
someStuff,'say "hello, world"', someStuff
someStuff,'say "hello, world', someStuff

someStuff是其他元素的占位符,也可以包含相同样式的引号

我正在寻找一种通用的方法来分隔逗号中的行,除非用单个或双引号括起来,以便将第二列作为String。第二列我的意思是字段:

  • 你好
  • 你好,SO
  • 说“你好,世界”
  • 说“你好,世界

我尝试过OpenCSV但失败了,因为只能指定一种类型的引用:

public class CSVDemo {

public static void main(String[] args) throws IOException {
    CSVDemo demo = new CSVDemo();
    demo.process("input.csv");
}

public void process(String fileName) throws IOException {
    String file = this.getClass().getClassLoader().getResource(fileName)
            .getFile();
    CSVReader reader = new CSVReader(new FileReader(file));
    String[] nextLine;
    while ((nextLine = reader.readNext()) != null) {
        System.out.println(nextLine[0] + " | " + nextLine[1] + " | "
                + nextLine[2]);
    }
}

}

使用opencsv的解决方案在最后一行失败,其中只有一个双引号括在单引号中:

someStuff | hello |  someStuff
someStuff | hello, SO |  someStuff
someStuff | 'say "hello, world"' |  someStuff
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 1

5 个答案:

答案 0 :(得分:2)

如果你真的不能使用真正的CSV解析器,你可以使用正则表达式。这通常不是一个好主意,因为总是存在无法处理的边缘情况,但如果格式严格按照您的描述,则可能有效。

public void test() {
    String[] tests = {"numeStuff,\"hello\", someStuff, someStuff",
        "numeStuff,\"hello, SO\", someStuff, someStuff",
        "numeStuff,'say \"hello, world\"', someStuff, someStuff"
    };
    /* Matches a field and a potentially empty separator.
     *
     *  ( - Field Group
     *     \"  - Start with a quote
     *     [^\"]*? - Non-greedy match on anything that is not a quote
     *     \" - End with a quote
     *   | - Or
     *     '  - Start with a strop
     *     [^']*? - Non-greedy match on anything that is not a strop
     *     ' - End with a strop
     *   | - Or
     *    [^\"'] - Not starting with a quote or strop
     *    [^,$]*? - Non-greedy match on anything that is not a comma or end-of-line
     *  ) - End field group
     *  ( - Separator group
     *   [,$] - Comma separator or end of line
     *  ) - End separator group
     */
    Pattern p = Pattern.compile("(\"[^\"]*?\"|'[^\']*?\'|[^\"'][^,\r\n]*?)([,\r\n]|$)");
    for (String t : tests) {
        System.out.println("Matching: " + t);
        Matcher m = p.matcher(t);
        while (m.find()) {
            System.out.println(m.group(1));
        }
    }
}

答案 1 :(得分:1)

opencsv似乎不支持开箱即用。您可以扩展com.opencsv.CSVParser并实现自己的算法来处理两种类型的引号。 This是您要更改的方法的来源,这里有一个存根来帮助您入门。

class MyCSVParser extends CSVParser{
    @Override
    private String[] parseLine(String nextLine, boolean multi) throws IOException{
        //Your algorithm here
    }
}

答案 2 :(得分:1)

基本上你只需跟踪,",'(修剪中间的内容)。

当您遇到其中一个时,请将相应的标志(例如,singleQuoteOpen,doubleQuoteOpen)设置为true以指示它们已打开并且您处于忽略逗号模式。

当您遇到相应的结束语时,请重置标志并继续切片元素。

要执行检查,请在每个逗号处停止(当不处于忽略逗号模式时)并查看下一个字符(如果有的话,并修剪)。

注意:正则表达式解决方案很好而且更短,但边缘情况的可定制性较低(至少没有大问题)。

答案 3 :(得分:0)

看起来opencv似乎不支持这一点。但是,请看看上一个问题和我的答案,以及其他答案,以防他们提供帮助 你:https://stackoverflow.com/a/15905916/1688441

下面是一个示例,请notInsideComma实际上不是"内部引号"。可以扩展以下代码以检查引号和双引号。

public static ArrayList<String> customSplitSpecific(String s)
{
    ArrayList<String> words = new ArrayList<String>();
    boolean notInsideComma = true;
    int start =0, end=0;
    for(int i=0; i<s.length()-1; i++)
    {
        if(s.charAt(i)==',' && notInsideComma)
        {
            words.add(s.substring(start,i));
            start = i+1;                
        }   
        else if(s.charAt(i)=='"')
        notInsideComma=!notInsideComma;
    }
    words.add(s.substring(start));
    return words;
}   

答案 4 :(得分:0)

如果每行使用单引号和双引号,则可以为每行选择相应的引用类型:

public class CSVDemo {
    public static void main(String[] args) throws IOException {
        CSVDemo demo = new CSVDemo();
        demo.process("input.csv");
    }

    public void process(String fileName) throws IOException {
        String file = this.getClass().getClassLoader().getResource(fileName)
                .getFile();

        CSVParser doubleParser = new CSVParser(',', '"');
        CSVParser singleParser = new CSVParser(',', '\'');

        String[] nextLine;

        try (BufferedReader br = new BufferedReader(new FileReader(file))) {
            String line;
            while ((line = br.readLine()) != null) {
                if (line.contains(",'") && line.contains("',")) {
                    nextLine = singleParser.parseLine(line);
                } else {
                    nextLine = doubleParser.parseLine(line);
                }

                System.out.println(nextLine[0] + " | " + nextLine[1] + " | "
                        + nextLine[2]);
            }
        }
    }
}