CSVをちゃんと読む Flash/JavaScript

FlashでCSVファイルを読む必要があったので、使えるものがないかFlashとCSVでググってみたところ…

var lines = text.split("\n");
var sheet = [];
for(var i = 0; i < lines.length; i++) {
    sheet.push(line.split(",")));
}

なんじゃこりゃ。こんな感じのばっかり。これ
http://ja.wikipedia.org/wiki/Comma-Separated_Values
とかこれ
http://www.kasai.fm/wiki/rfc4180jp
とか読め、と。

というわけで、ECMAなスクリプトでまともにCVS解釈ルーチンを書きました。FlashのAS1(ECMAサブセット)でも動きます。

RFCの仕様は満たし、かつ、仕様漏れのあるところはOpenOfficeCSV解釈に準拠しています。

/*
 * CSV parser.
 * 
 * result of this function can be used like "csv[row][col]".
 * all rows has the same number of columns.
 * 
 * @param src raw string of CSV format
 * @return an Array(2D)
 */
function parseCSV(src) {

    // constants
    var QUOT = '"', SEP = ',', CR = "\r", LF = "\n";
    
    // data to be generated
    var sheet = [], row = [], cell = "";
    
    // status flags
    var inquot = false;      // true when the current cell starts with quot.
    var cn = src.charAt(0);  // the next character, to be the 1st one before scanning.
    
    for (var i = 0; i < src.length; i++) {
        var cc = cn;  // current character is the next character of previous loop.
        cn = (i == src.length - 1) ? null : src.charAt(i + 1); // the next character
        
        if (cc == CR) {
            if (cn != LF) cc = LF;    // CR without LF : considerd as LF
            if (cn == LF) continue;   // CR with LF : skip this CR
        }
        
        if (inquot) {
            // put a character to current cell with quot character escape if ...
            // current cell starts with quot and
            // now quoted string is still not terminated.
            // - double quot: puts a quot, then progress cursor twice.
            // - single quot: ends this mode.
            // - else       : put a character to current cell.
            switch(cc) {
                case QUOT:
                    if (cn == QUOT) { cell += QUOT; i++; }
                    else inquot = false; break;
                default:
                    cell += cc;
            }
        }
        else {
            // put a character to current cell or move to the next cell/row if ...
            // current cell does still not start,
            // starts without quot or 
            // quot terminated in cell.
            // - newline  : ends current row.
            // - comma    : ends current cell.
            // - 1st quot : starts quoted cell. (it would be happen with the 1st character of cell.)
            // - else     : put a character to current cell.
            switch(cc) {
                case LF:
                    row.push(cell); cell = "";
                    sheet.push(row); row = []; break;
                case SEP:
                    row.push(cell); cell = ""; break;
                case QUOT:
                    if (cell == "") { inquot = true; break; }
                default:
                    cell += cc;
            }
        }
    }
    
    // join characters after the last spearator to this sheet.
    if(cell != "") row.push(cell);
    if(row.length > 0) sheet.push(row);
    
    // adjust number of columns of each rows.
    var cols = 0; // the max length of each row
    for (i = 0; i < sheet.length; i++) {
        cols = Math.max(sheet[i].length, cols);
    }
    for (i = 0; i < sheet.length; i++) {
        var pad = cols - sheet[i].length; // count to be padded.
        for (j = 0; j < pad; j++) {
            sheet[i].push("");
        }
    }
    
    return sheet;
}

CR+LFのファイルで、末尾行の末尾列がクォート閉じなかった場合、CRが混入するかどうかが違うぐらい。英語コメントが変な文章だけど、そこはご愛嬌で(^^;

保存してあるCSVファイルの文字エンコーディングには注意しましょう。言語機能の特性上、PHPみたいに、外部テキストの文字コード変換ができないので。(それにしても、FlashのSystem.useCodePageはひどい。非同期APIでシングルトンにフラグを持たせるとは…)