2015年11月21日: Scheme、Import-Csv
2015年11月21日 10時28分
PowerShell に Import-Csv
とゆう便利なコマンドがあると聞いて、Scheme 用に作ってみた。Gauche とかだと標準ライブラリにあるぽいけど、自分、Gauche 環境ぢゃないし、お仕事で使ってる SKILL にもそんな便利機能はないから、どっちにしても自作しよぉとおもた。
まず Scheme で作ったんで、あとはこれを SKILL に翻訳すれば仕事でも使える。あと、もちろん、Scheme でシェル芸するのにもお役立ち。ご本家の PowerShell だとデリミタを指定するオプションがあるぽい。それもできるようにすればもっといいかも。
ざっくりな方針
- ダブルクオートの処理とか空白文字の処理とかは Microsoft Excel の処理を基準にする。(PowerShell の
Import-Csv
をまねると言っておきながら何だけど…) - 出力結果は〈〈キーと値のタプル〉のリストのリスト〉とする。これは PowerShell の
Import-Csv
に倣った。実際その方がその後の処理がし易いしねっ。 - ヘッダ行のフィールド数と本体のフィールド数が違う場合の処理も当然 PowerShell の
Import-Csv
を基準にする。 -
CR
、LF
、CR-LF
のどの改行方式でも対応できるようにする。 - 基本機能だし、大量のデータが来てもダイジョブなよおに、効率優先の実装にする。
- 関数としては Scheme 組み込みの以外のものは使わない。
コード
(define (Import-Csv inputPort)
(if (eof-object? (peek-char inputPort))
(list)
; else
(let*
(
(head/body
(let*
(
(chars (list #f)) (chars_last chars )
(fields (list #f)) (fields_last fields )
(records (list #f)) (records_last records)
)
(let parseLine ((c (read-char inputPort)))
(cond
((eof-object? c)
(if (or (pair? (cdr chars)) (pair? (cdr fields)))
(begin
(set-cdr! fields_last (list (list->string (cdr chars))))
(set-cdr! records_last (list (cdr fields)))))
(cdr records))
((char=? c #\return)
(let ((nextChar (peek-char inputPort)))
(cond
((eof-object? nextChar) #f)
((char=? #\newline nextChar) (read-char inputPort)))
(if (or (pair? (cdr chars)) (pair? (cdr fields)))
(begin
(set-cdr! fields_last (list (list->string (cdr chars))))
(set-cdr! records_last (list (cdr fields)))
(set! records_last (cdr records_last))))
(set-cdr! fields '()) (set! fields_last fields)
(set-cdr! chars '()) (set! chars_last chars )
(parseLine (read-char inputPort))))
((char=? c #\newline)
(if (or (pair? (cdr chars)) (pair? (cdr fields)))
(begin
(set-cdr! fields_last (list (list->string (cdr chars))))
(set-cdr! records_last (list (cdr fields)))
(set! records_last (cdr records_last))))
(set-cdr! fields '()) (set! fields_last fields)
(set-cdr! chars '()) (set! chars_last chars )
(parseLine (read-char inputPort)))
((char=? c #\")
(if (null? (cdr chars))
(let parseQuoted ((c (read-char inputPort)))
(cond
((eof-object? c)
(if (or (pair? (cdr chars)) (pair? (cdr fields)))
(begin
(set-cdr! fields_last (list (list->string (cdr chars))))
(set-cdr! records_last (list (cdr fields)))))
(cdr records))
((char=? c #\")
(case (peek-char inputPort)
((#\")
(read-char inputPort)
(set-cdr! chars_last (list #\"))
(set! chars_last (cdr chars_last))
(parseQuoted (read-char inputPort)))
((#\,) (parseLine (read-char inputPort)))
(else (parseQuoted (read-char inputPort)))))
(else
(set-cdr! chars_last (list c))
(set! chars_last (cdr chars_last))
(parseQuoted (read-char inputPort)))))
; else
(begin
(set-cdr! chars_last (list c))
(set! chars_last (cdr chars_last))
(parseLine (read-char inputPort)))))
((char=? c #\,)
(set-cdr! fields_last (list (list->string (cdr chars))))
(set! fields_last (cdr fields_last))
(set-cdr! chars '()) (set! chars_last chars)
(parseLine (read-char inputPort)))
(else
(set-cdr! chars_last (list c))
(set! chars_last (cdr chars_last))
(parseLine (read-char inputPort)))))))
(head (car head/body))
(body (cdr head/body))
)
(map
(lambda (values)
(let* ((buffer (list #f)) (buffer_last buffer))
(let keyValue ((ks head) (vs values))
(cond
((null? ks) (cdr buffer))
((null? vs)
(set-cdr! buffer_last (list (list (car ks) "")))
(set! buffer_last (cdr buffer_last))
(keyValue (cdr ks) '()))
(else
(set-cdr! buffer_last (list (list (car ks) (car vs))))
(set! buffer_last (cdr buffer_last))
(keyValue (cdr ks) (cdr vs)))))))
body))))
使用例
(let*
(
(inputPort
(open-input-string ; SRFI-6
(string-append
"name,company,address,city\n"
"\"LINCOLN, Abraham\",\"Benton, John B Jr\",6649 N Blue Gum St,New Orleans\n"
"\"CARTER, Jimmy\",\"Chanay, Jeffrey A Esq\",4 B Blue Ridge Blvd,Brighton\n"
"\"ROOSEVELT, Franklin\",\"Chemel, James L Cpa\",8 W Cerritos Ave #54,Bridgeport\n"
"\"BUSH, George\",Feltz Printing Service,639 Main St,Anchorage\n"
"\"CLINTON, Bill\",Printing Dimensions,34 Center St,Hamilton\n"
"\"OBARAM, Barack\",\"Chapman, Ross E Esq\",3 Mcauley Dr,Ashland\n")))
(csv (Import-Csv inputPort))
)
(close-port inputPort)
csv)
→ (
(("name" "LINCOLN, Abraham") ("company" "Benton, John B Jr") ("address" "6649 N Blue Gum St") ("city" "New Orleans"))
(("name" "CARTER, Jimmy") ("company" "Chanay, Jeffrey A Esq") ("address" "4 B Blue Ridge Blvd") ("city" "Brighton"))
(("name" "ROOSEVELT, Franklin") ("company" "Chemel, James L Cpa") ("address" "8 W Cerritos Ave #54") ("city" "Bridgeport"))
(("name" "BUSH, George") ("company" "Feltz Printing Service") ("address" "639 Main St") ("city" "Anchorage"))
(("name" "CLINTON, Bill") ("company" "Printing Dimensions") ("address" "34 Center St") ("city" "Hamilton"))
(("name" "OBARAM, Barack") ("company" "Chapman, Ross E Esq") ("address" "3 Mcauley Dr") ("city" "Ashland"))
)