2

質問は非常に簡単です。


CSVファイルは次のようになります。

1, "John", "John Joy"

各列を取得したい場合は、String[] splits = line.split(",");


CSVファイルが次のようになっている場合はどうなりますか?

1, "John", "Joy, John"

したがって、二重引用符のペアの中にコンマがあります。「ジョイ、ジョン」を完全な部分として欲しいので、上記の分割はもう機能しません。


では、この状況に対処するためのエレガントでシンプルなアルゴリズムはありますか?


編集:

正式なCSV解析とは見なさないでください。分割する必要があるユースケースとしてCSVを使用しています。

私が本当に必要としているのは、適切なCSVパーサーではなく、二重引用符を考慮して1行をコンマで適切に分割できるアルゴリズムが必要なだけです。

4

5 に答える 5

4

カスタム実装を作成する代わりに、この目的のために既存のライブラリを使用することをお勧めします (学習のためにこれを行わない場合)。CSV には、カスタム実装で見逃す可能性のある詳細がいくつかあり、通常、ライブラリは十分にテストされているためです。

CSV ファイルを読み取る (場合によっては書き込む) ための Java ライブラリをお勧めできますか?

編集

文字列を解析するメソッドを作成しましたが、十分にテストしていないため、完全には機能しない可能性があります。それはあなたにとっての出発点に過ぎず、さらに改善することができます.

    String inputString = "1, \"John\",\"Joy, John\"";
    char quote = '"';
    List<String> csvList = new ArrayList<String>();
    boolean inQuote = false;
    int lastStart = 0;
    for (int i = 0; i < inputString.length(); i++) {
        if ((i + 1) == inputString.length()) {
            //if this is the last character
            csvList.add(inputString.substring(lastStart, i + 1));
        }
        if (inputString.charAt(i) == quote) {
            //if the character is quote
            if (inQuote) {
                inQuote = false;
                continue; //escape
            }
            inQuote = true;
            continue;
        }
        if (inputString.charAt(i) == ',') {
            if (inQuote) continue;
            csvList.add(inputString.substring(lastStart, i));
            lastStart = i + 1;
        }
    }
    System.out.println(csvList);

あなたへの質問

1, "John", ""Joy, John"" そのような文字列("Joy, John" の 2 つの引用符)が得られたらどうしますか?

于 2012-11-26T11:39:44.790 に答える
1
// use regxep with matcher

String string1 = "\"John\", \"John Joy\"";
String string2 = "\"John\", \"Joy, John\"";
Pattern pattern = Pattern.compile("\"[^\"]+\"");

Matcher matcher = pattern.matcher(string1);
System.out.println("string1: " + string1);
int start = 0;
while(matcher.find(start)){
    System.out.println(matcher.group());
    start = matcher.end() + 1;
    if(start > string1.length())
    break;
}

matcher = pattern.matcher(string2);
System.out.println("string2: " + string2);
start = 0;
while(matcher.find(start)){
    System.out.println(matcher.group());
    start = matcher.end() + 1;
    if(start > string2.length())
    break;
}
于 2012-11-27T06:03:32.653 に答える
0

正規表現の使用は非常にエレガントです。
申し訳ありませんが、私はJava正規表現に精通していないため、私の例はLuaにあります:(
この例では、引用テキスト内に改行文字が含まれている可能性があり、元の引用文字が引用テキスト内で2倍になることは考慮されていません)

--- file.csv
1, "John", "John Joy"
2, "John", "Joy, John"

--- Lua code
for line in io.lines 'file.csv' do
   print '==='
   for _, s in (line..','):gmatch '%s*("?)(.-)%1%s*,' do
      print(s)
   end
end

--- Output
===
1
John
John Joy
===
2
John
Joy, John
于 2012-11-26T16:43:03.217 に答える
0

正規表現から始めることができます:

[^",]*|"[^"]*"

コンマを含まない引用符で囲まれていない文字列または引用符で囲まれた文字列のいずれかに一致します。ただし、次のような多くの質問があります。

  1. 入力のコンマの後に本当にスペースがありますか? または、より一般的には、正確にフィールドの最初の文字にない引用符を許可しますか?

  2. 引用符を含むフィールドをどのように引用符で囲みますか?

その質問にどのように答えるかによって、異なる正規表現になる可能性があります。(実際、CSV 解析ライブラリを使用するための慣習的なアドバイスは、コーナー ケースの処理に関するものではありません。ライブラリを解析しています。CSV はめちゃくちゃです。)

私が使用してある程度成功した正規表現の 1 つ (ただし、CSV 互換ではありません) は次のとおりです。

(?:[^",]|"[^"]*")*

これは最初のものと非常によく似ていますが、連結されたフィールドをいくつでも許可するため、次の両方がすべて単一のフィールドとして認識されます。

"John"", Mary"
John", "Mary

CSV 標準では、最初のものを次のものとして扱います。

John", Mary    -- internal quote

2 番目の引用符を通常の文字として扱い、2 つのフィールドができます。だからYMMV。

いずれにせよ、適切な正規表現を決定すれば、アルゴリズムは単純です。私は Java の専門家ではないので、疑似コードで説明します。

repeat:
   match the regex at the current position
     and append the result to the result;
   if the match fails:
     report error
   if the match goes to the end of the string:
     done
   if the next character is a ',':
     advance the position by one
   otherwise:
     report error

正規表現によっては、エラーを報告する 2 つの条件が当てはまらない場合があります。一般に、引用されたフィールドが終了していない場合、最初のものはトリガーされます(そして、引用されたフィールドで改行を許可するかどうかを決定する必要があります-CSVはそうします)。私が提供した最初の正規表現を使用し、引用符で囲まれた文字列の直後にコンマを付けなかった場合、2 つ目の問題が発生する可能性があります。

于 2012-11-26T16:54:54.230 に答える
-1

最初に文字列を引用符で分割します。奇数セグメントには引用されたコンテンツがあります。1 つでもコンマでもう一度分割する必要があります。この質問のように、引用されたテキストに引用符がエスケープされていないログで使用します。

    boolean quoted = false;
    for(String q : str.split("\"")) {
        if(quoted)
            System.out.println(q.trim());
        else
            for(String s : q.split(","))
                if(!s.trim().isEmpty())
                    System.out.println(s.trim());
        quoted = !quoted;
    }
于 2015-05-15T07:28:01.193 に答える