challenge RFC 4180対応版 CSVレコードの分解

ある関数(splitCSV)に渡された文字列を配列に分解して列ごとに表示してください。
渡される文字列は、CSVデータの1レコードが設定されているとします。

使用するデータはK3形式が元になっている仕様で
エクセルが出力しているような形式です。

書式には次のような特徴があります。
1. 各レコードは「改行」によって区切られている。
2. 各列は「,」によって区切られている。
3. 列のデータは「"」によって囲んでも良い。
4. 列に「,」「改行」「"」いずれかを含む場合「"」で
   囲わなければならない。
5. 列データに「"」を含める場合「""」とする。

本来、改行コードはCRLFですが今回は特に指定しません。

次の入力があった場合
"aaa","b
bb","ccc",zzz,"y""Y""y",xxx

出力は
1 => aaa
2 => b
bb
3 => ccc
4 => zzz
5 => y"Y"y
6 => xxx

となります。
このお題はraynstardさんの投稿によるものです。ご投稿ありがとうございます。助かります。

Posted feedbacks - Common Lisp

arnesi:parse-csv-stringはyYyとなるバグがあって使えない
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
(require :fare-csv)
(defun splitCSV (line)
  (loop for elt in (with-input-from-string (inn line)
                     (fare-csv:read-csv-line inn))
     for i from 1 do
     (format t "~a => ~a~%" i elt)))

(splitCSV "\"aaa\",\"b
bb\",\"ccc\",zzz,\"y\"\"Y\"\"y\",xxx
")
;; 1 => aaa
;; 2 => b
;; bb
;; 3 => ccc
;; 4 => zzz
;; 5 => y"Y"y
;; 6 => xxx

【これはひどい】
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
(defun splitCSV (csv)
  (let ((len (length csv))
        (lst '(())))
    (labels
        ((field (h n i &optional (esc nil))
           (when (> len i)
             (if h
               (princ (format nil "~D => " n)))
             (let ((it (char csv i)))
               (case it
                 (#\" (if esc
                          (case (char csv (incf i))
                            (#\" (princ #\" ) (field nil n (1+ i) t))
                            (#\, (princ #\newline) (field t (1+ n) (1+ i))))
                        (field nil n (1+ i) t)))
                 (#\newline (if esc
                            (progn (princ it) (field nil n (1+ i) t))
                          (progn (princ it) (field t 1 (1+ i)))))
                 (#\, (princ #\newline) (field t (1+ n) (1+ i)))
                 (t (princ it) (field nil n (1+ i) esc)))))))
      (field t 1 0 nil))))

色々修正版

(splitCSV "\"aaa\",\"b
bb\",\"ccc\",zzz,\"y\"\"Y\"\"y\",xxx,\"eee,EEE\",,,")
1 => aaa
2 => b
bb
3 => ccc
4 => zzz
5 => y"Y"y
6 => xxx
7 => eee,EEE
8 => 
9 => 
nil
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
(defun splitCSV (csv)
  (let ((len (length csv)))
    (labels
        ((field (h n i &optional (esc nil))
           (when (> len i)
             (and h (princ (format nil "~D => " n)))
             (let ((it (char csv i)))
               (case it
                 (#\" (if esc
                          (case (char csv (incf i))
                            (#\" (princ #\" ) (field nil n (1+ i) t))
                            (#\, (princ #\newline) (field t (1+ n) (1+ i))))
                        (field nil n (1+ i) t)))
                 (#\newline (if esc
                                (progn (princ it) (field nil n (1+ i) t))
                              (progn (princ it) (field t 1 (1+ i)))))
                 (#\, (if esc
                          (progn (princ it) (field nil n (1+ i) t))
                        (progn (princ #\newline) (field t (1+ n) (1+ i)))))
                 (t (princ it) (field nil n (1+ i) esc)))))))
      (field t 1 0 nil))))

やっちゃった><

相互呼び出しの再帰で実装してみた。
これって、10 => まで出るのが正しいんだよね?

[sample.csv]
"aaa","b
bb","ccc",zzz,"y""Y""y",xxx,"eee,EEE",,,

(splitCSV "sample.csv")
1 => aaa
2 => b
bb
3 => ccc
4 => zzz
5 => y"Y"y
6 => xxx
7 => eee,EEE
8 => 
9 => 
10 => 
t
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
(defun splitCSV (csv-file)
  (labels
      ((in-esc (csv header col)
         (let ((it (read-char csv nil)))
           (case it
             ((#\") (let ((it (read-char csv nil)))
                      (case it
                        (#\" (princ it) (in-esc csv nil col))
                        (#\, (princ #\newline) (out-esc csv t (1+ col))))))
             ((nil) nil)
             (t (princ it) (in-esc csv nil col)))))
       (out-esc (csv header col)
         (let ((it (read-char csv nil)))
           (and header it (format t "~D => " col))
           (case it
             ((#\") (in-esc csv nil col))
             ((#\newline) (princ it) (out-esc csv t 1))
             ((#\,) (princ #\newline) (out-esc csv t (1+ col)))
             ((nil) t)
             (t (princ it) (out-esc csv nil col))))))
    (with-open-file (csv csv-file)
      (out-esc csv t 1))))

題意に沿うバージョンと、ファイルから読み込むバージョンとに分けてみた。
#終端処理が微妙なことに気づいたので微妙に修正…
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
(defun splitCSV (csv-stream)
  (labels
      ((in-esc (csv header col)
         (let ((it (read-char csv nil)))
           (case it
             ((#\") (let ((it (read-char csv nil)))
                      (case it
                        (#\" (princ it) (in-esc csv nil col))
                        (#\, (princ #\newline) (out-esc csv t (1+ col))))))
             ((nil) nil)
             (t (princ it) (in-esc csv nil col)))))
       (out-esc (csv header col)
         (let ((it (read-char csv nil)))
           (and header (or (> col 1) it) (format t "~D => " col))
           (case it
             ((#\") (in-esc csv nil col))
             ((#\newline) (princ it) (out-esc csv t 1))
             ((#\,) (princ #\newline) (out-esc csv t (1+ col)))
             ((nil) t)
             (t (princ it) (out-esc csv nil col))))))
    (and (streamp csv-stream)
         (out-esc csv-stream t 1))))

(defun splitCSV-from-file (csv-file)
  (with-open-file (stream csv-file)
    (splitCSV stream)))

(defun splitCSV-from-string (csv-string)
  (with-input-from-string (stream csv-string)
    (splitCSV stream)))

Index

Feed

Other

Link

Pathtraq

loading...