2015年11月21日: Scheme、Import-Csv

# 2015年11月21日: Scheme、Import-Csv

2015年11月21日 10時28分

<p>PowerShell に <code>Import-Csv</code> とゆう便利なコマンドがあると聞いて、Scheme 用に作ってみた。Gauche とかだと標準ライブラリにあるぽいけど、自分、Gauche 環境ぢゃないし、お仕事で使ってる SKILL にもそんな便利機能はないから、どっちにしても自作しよぉとおもた。</p>

<p>まず Scheme で作ったんで、あとはこれを SKILL に翻訳すれば仕事でも使える。あと、もちろん、Scheme でシェル芸するのにもお役立ち。ご本家の PowerShell だとデリミタを指定するオプションがあるぽい。それもできるようにすればもっといいかも。</p>

<h2>ざっくりな方針</h2>

<ul>
<li>ダブルクオートの処理とか空白文字の処理とかは Microsoft Excel の処理を基準にする。（PowerShell の <code>Import-Csv</code> をまねると言っておきながら何だけど…）
</li>
<li>出力結果は〈〈キーと値のタプル〉のリストのリスト〉とする。これは PowerShell の <code>Import-Csv</code> に倣った。実際その方がその後の処理がし易いしねっ。
</li>
<li>ヘッダ行のフィールド数と本体のフィールド数が違う場合の処理も当然 PowerShell の <code>Import-Csv</code> を基準にする。
</li>
<li>
<code>CR</code>、<code>LF</code>、<code>CR-LF</code> のどの改行方式でも対応できるようにする。
</li>
<li>基本機能だし、大量のデータが来てもダイジョブなよおに、効率優先の実装にする。
</li>
<li>関数としては Scheme 組み込みの以外のものは使わない。
</li>
</ul>

<h2>コード</h2>

```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))))
```

<h2>使用例</h2>

```scheme:
(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"))
  )
```