2012-05-02 26 views
3

次の正規表現がcsv文字列を行単位で分割できる場合。これは、(引用符/二重引用符の間にIE)CSV値の中に含まれている改行文字をスキップするように適合させることができる方法引用符で囲まれた改行をスキップしてCSV文字列を分割する

var lines = csv.split(/\r|\r?\n/g); 

例:

2,"Evans & Sutherland","230-132-111AA",,"Visual","P\r\nCB",,1,"Offsite",\r\n 

私はスキップしようとしている部分が中に含まれる改行です:

2,"Evans & Sutherland","230-132-111AA",,"Visual","P 
CB",,1,"Offsite", 

あなたがそれを見ていない場合は、ここで目に見える改行付きバージョンがあります"PCB"エントリの真ん中です。

更新:

私はおそらく前にこれを言及してきたはずですが、これはjquery-csvと呼ばれる専用のCSV解析するライブラリの一部です。より良いコンテキストを提供するために、私は以下の現在のパーサー実装を追加しました。

ここでエントリを検証して解析するためのコード(すなわち、1つの行)です:

$.csvEntry2Array = function(csv, meta) { 
    var meta = (meta !== undefined ? meta : {}); 
    var separator = 'separator' in meta ? meta.separator : $.csvDefaults.separator; 
    var delimiter = 'delimiter' in meta ? meta.delimiter : $.csvDefaults.delimiter; 

    // build the CSV validator regex 
    var reValid = /^\s*(?:D[^D\\]*(?:\\[\S\s][^D\\]*)*D|[^SD\s\\]*(?:\s+[^SD\s\\]+)*)\s*(?:S\s*(?:D[^D\\]*(?:\\[\S\s][^D\\]*)*D|[^SD\s\\]*(?:\s+[^SD\s\\]+)*)\s*)*$/; 
    reValid = RegExp(reValid.source.replace(/S/g, separator)); 
    reValid = RegExp(reValid.source.replace(/D/g, delimiter)); 

    // build the CSV line parser regex 
    var reValue = /(?!\s*$)\s*(?:D([^D\\]*(?:\\[\S\s][^D\\]*)*)D|([^SD\s\\]*(?:\s+[^SD\s\\]+)*))\s*(?:S|$)/g; 
    reValue = RegExp(reValue.source.replace(/S/g, separator), 'g'); 
    reValue = RegExp(reValue.source.replace(/D/g, delimiter), 'g'); 

    // Return NULL if input string is not well formed CSV string. 
    if (!reValid.test(csv)) { 
    return null; 
    } 

    // "Walk" the string using replace with callback. 
    var output = []; 
    csv.replace(reValue, function(m0, m1, m2) { 
    // Remove backslash from any delimiters in the value 
    if (m1 !== undefined) { 
     var reDelimiterUnescape = /\\D/g;    
     reDelimiterUnescape = RegExp(reDelimiterUnescape.source.replace(/D/, delimiter), 'g'); 
     output.push(m1.replace(reDelimiterUnescape, delimiter)); 
    } else if (m2 !== undefined) { 
     output.push(m2); 
    } 
    return ''; 
    }); 

    // Handle special case of empty last value. 
    var reEmptyLast = /S\s*$/; 
    reEmptyLast = RegExp(reEmptyLast.source.replace(/S/, separator)); 
    if (reEmptyLast.test(csv)) { 
    output.push(''); 
    } 

    return output; 
}; 

注:私はまだテストしていないが、私は、私はおそらくメインに最後の試合を組み込むことができると思いますが、分割/コールバック。それは作品がthis answerを見てみましょうreges方法

$.csv2Array = function(csv, meta) { 
    var meta = (meta !== undefined ? meta : {}); 
    var separator = 'separator' in meta ? meta.separator : $.csvDefaults.separator; 
    var delimiter = 'delimiter' in meta ? meta.delimiter : $.csvDefaults.delimiter; 
    var skip = 'skip' in meta ? meta.skip : $.csvDefaults.skip; 

    // process by line 
    var lines = csv.split(/\r\n|\r|\n/g); 
    var output = []; 
    for(var i in lines) { 
    if(i < skip) { 
     continue; 
    } 
    // process each value 
    var line = $.csvEntry2Array(lines[i], { 
     delimiter: delimiter, 
     separator: separator 
    }); 
    output.push(line); 
    } 

    return output; 
}; 

の内訳については:

このは、スプリット・バイ・ラインの部分を行うコードです。私のものはやや適合したバージョンです。一重引用符と二重引用符のマッチングを統合して、1つのテキスト区切り文字にマッチさせ、区切り文字/区切り文字を動的にしました。それはEntiriesを検証する素晴らしい仕事をしますが、私が上に追加した行分割ソリューションはかなり虚弱で、私が上で説明したエッジケースで壊れています。

私は、有効なエントリを抽出して(エントリパーサに渡す)文字列を見回す、または解析に失敗した行を示すエラーを返す不良データで失敗するソリューションを探しています。

更新:

  • 0:

    splitLines: function(csv, delimiter) { 
        var state = 0; 
        var value = ""; 
        var line = ""; 
        var lines = []; 
        function endOfRow() { 
        lines.push(value); 
        value = ""; 
        state = 0; 
        }; 
        csv.replace(/(\"|,|\n|\r|[^\",\r\n]+)/gm, function (m0){ 
        switch (state) { 
         // the start of an entry 
         case 0: 
         if (m0 === "\"") { 
          state = 1; 
         } else if (m0 === "\n") { 
          endOfRow(); 
         } else if (/^\r$/.test(m0)) { 
          // carriage returns are ignored 
         } else { 
          value += m0; 
          state = 3; 
         } 
         break; 
         // delimited input 
         case 1: 
         if (m0 === "\"") { 
          state = 2; 
         } else { 
          value += m0; 
          state = 1; 
         } 
         break; 
         // delimiter found in delimited input 
         case 2: 
         // is the delimiter escaped? 
         if (m0 === "\"" && value.substr(value.length - 1) === "\"") { 
          value += m0; 
          state = 1; 
         } else if (m0 === ",") { 
          value += m0; 
          state = 0; 
         } else if (m0 === "\n") { 
          endOfRow(); 
         } else if (m0 === "\r") { 
          // Ignore 
         } else { 
          throw new Error("Illegal state"); 
         } 
         break; 
         // un-delimited input 
         case 3: 
         if (m0 === ",") { 
          value += m0; 
          state = 0; 
         } else if (m0 === "\"") { 
          throw new Error("Unquoted delimiter found"); 
         } else if (m0 === "\n") { 
          endOfRow(); 
         } else if (m0 === "\r") { 
          // Ignore 
         } else { 
          throw new Error("Illegal data"); 
         } 
          break; 
         default: 
         throw new Error("Unknown state"); 
        } 
        return ""; 
        }); 
        if (state != 0) { 
        endOfRow(); 
        } 
        return lines; 
    } 
    

    それが取ったすべてがラインスプリッタのための4つの状態であるエントリ

  • 1の開始:以下が引用されています
  • 2:2番目の見積もりが発生しました
  • 3:以下は引用されていません

これはほぼ完全なパーサーです。私の使用事例では、私は単にCSVデータを処理するためのより洗練されたアプローチを提供できるように、ラインスプリッタが必要でした。

注:このアプローチのクレジットは、私が彼の許可なく公然と名を挙げない別の開発者になります。私がしたことは完全なパーサからラインスプリッタにそれを適用することでした。

更新:前lineSplitter実装において、いくつかの壊れたエッジケースを発見し

。提供されるものは、完全にRFC 4180に準拠する必要があります。

+0

あなたは、単にこの使用して正規表現を行うことはできません。あなたは、いくつかの、あるいはほとんどの条件を扱う正規表現を作成することができます。しかし、正規表現では動作しない有効なCSVが常に存在します。 –

+0

正規表現は解析用であり、解析用ではありません。テキストをスキャンするときに、文字以外のもの(つまり、引用されたリテラルの「内側」)を「記憶」する必要がある場合は、その文字を解析しています。微妙な違いは、誰もがそれを解析するために使用したい理由です。 –

答えて

2

私がコメントで指摘したように、ただ一つの正規表現を使うだけの完全な解決法はありません。個人的に私は、状態マシンは複数を有するhere

記載されているように、単純な有限状態機械を使用する

-

コンマで分離することによって、いくつかの正規表現を使用して埋め込まれたカンマで文字列をバック接合新規な方法は、here:に記載されていますコードはより洗練されており、各コードが何をしているのかが明確です。長期的に言えば、これははるかに信頼性と保守性が向上します。

+0

あなたはそうです。 FSMは明らかに優れたアプローチです(エッジケースを微調整する方がはるかに簡単です)。私は質問の更新で完全な作業の実装を提供しました。ところで、あなたの最初のリンクは死んでいます。 –

0

注意 - 改行はその値の部分です。それはPCBではなく、P\nCBです。

しかし、なぜstring.split(',')を使用できませんか?必要に応じて、リストを実行してintにキャストしたり、パディングされた引用符を削除することができます。

+0

私はそれが問題のポイントであることを理解しています。私はエッジケースを管理しようとしています。 String.split( '、')は簡単なCSVでのみ動作します。これはCSV専用のライブラリですので、堅牢なソリューションが必要です。とにかく応答に感謝します。 –

0

regexを使って解析するのは良い考えではありません。より良い「悪い」分割を検出し、それらをバックマージするためにそれを使用します(この答えはCC0とWTFPLの両方の下でライセンスされている)

var lines = csv.split(/\r?\n/g); 
var bad = []; 

for(var i=lines.length-1; i> 0; i--) { 
    // find all the unescaped quotes on the line: 
    var m = lines[i].match(/[^\\]?\"/g); 

    // if there are an odd number of them, this line, and the line after it is bad: 
    if((m ? m.length : 0) % 2 == 1) { bad.push(i--); } 
} 

// starting at the bottom of the list, merge lines back, using \r\n 
for(var b=0,len=bad.length; b < len; b++) { 
    lines.splice(bad[b]-1, 2, lines[bad[b]-1]+"\r\n"+lines[bad[b]]); 
} 

+0

これはおそらく私が使用する方法に近いです。エスケープされていない引用符を数えるのが最も理にかなっているようですが、私は2回目のパス(つまり分割して解析)をシングルパスで避けたいと思います。基本的には、有効な改行が検出されるまで文字列を歩き、それをエントリとして抽出し、解析して、スティングを続けます。 2パスは機能しますが、大きなデータセットではパフォーマンス/メモリの関係が醜いことがあります。 –

関連する問題