Archive for 2010年9月29日|Daily archive page

newLISP で CSV を扱う(解説編)。

 先日の “newLISP で CSV を扱う。” は如何だったでしょうか?
(defun は newlisp-utility.lsp に定義してあります。)

(define CRLF "\r\n")
(define regexCRLF "\r*\n")
(defun stringEx (x)
  (if (string? x) (string "\"" x "\"") (string x)))
(defun string-convert (str) 
  (if (catch (eval-string str) 'res) (if res res str) str))
(defun list2csv (lst)
  (let (csv "")
    (dolist (row lst)
      (extend csv (join (map stringEx row) ",") CRLF))))
(defun csv2list (csv)
  (let (lst '())
    (dolist (row (parse (if (ends-with csv regexCRLF 0) (chop csv) csv) regexCRLF 0))
      (push (map string-convert (parse row ",")) lst -1))
    (setf (lst -1 -1) (if (string? $it) (trim $it "\r") $it))
    lst))

 短いコードなので、再掲載。といいつつ、さりげなく変更しています(汗)。
 関数list2csv では組込join を使ってCSV 文字列を作り、関数csv2list では組込parse を使ってリストにしているのは、定石通り?
 もちろん、ポイントは、関数stringEx と string-convert です。
 関数stringEx は、組込string のように文字列に変換しますが、引数が文字列の時は、ダブルクォートで囲まれた文字列に変換します。
 一方、関数string-convert は、引数に組込eval-string を適用して、エラーなら文字列そのまま出力します。組込eval-string がエラーを起こさなければ、それは評価されたわけですから、評価された値を出します。関数list2csv で作成した CSV 文字列だけを扱うなら、

(defun string-convert (str) 
  (if (catch (eval-string str) 'res) res str))

 でも良かったのですが、最初のコードのように評価値が nil の場合も、文字列のまま出すようにしています。
 これは、CSV 文字列中で文字列に変換したい部分が必ずしもダブルクォートで囲まれているとは限らないからです。
 ここが、EXCEL 等の出力する CSV ファイルにも対応する時のポイントです(笑)。
 問題があるとすれば、

> (setq test (list2csv '(("test" nil true))))
"\"test\",nil,true\r\n"
> (csv2list test)
(("test" "nil" true))
> 

 という風に、データが nil の時も文字列になってしまうことくらい?(笑)
 ちなみに、EXCEL のブール値 TRUE と FALSE も、文字列になります。
 さて、先日の関数は、ファイルを対象にしていません。なぜなら、組込read-filewrite-file を使えば良いだけだからです。

> csv
"\"Planet name\",\"Equator diameter (earth)\",\"Mass (earth)\",\"Orbital radius (AU)\",\"Orbital period (years)\",\"Orbital Incline Angle\",\"Orbital Eccentricity\",\"Rotation (days)\",\"Moons\"\r\n\"Mercury\",0.382,0.06,0.387,0.241,7,0.206,58.6,0\r\n\"Venus\",0.949,0.82,0.72,0.615,3.39,0.0068,-243,0\r\n\"Earth\",1,1,1,1,0,0.0167,1,1\r\n\"Mars\",0.53,0.11,1.52,1.88,1.85,0.0934,1.03,2\r\n\"Jupiter\",11.2,318,5.2,11.86,1.31,0.0484,0.414,63\r\n\"Saturn\",9.41,95,9.54,29.46,2.48,0.0542,0.426,49\r\n\"Uranus\",3.98,14.6,19.22,84.01,0.77,0.0472,-0.718,27\r\n\"Neptune\",3.81,17.2,30.06,164.8,1.77,0.0086,0.671,13\r\n\"Pluto\",0.18,0.002,39.5,248.5,17.1,0.249,-6.5,3\r\n\"test\",FALSE,TRUE,nil,true\r\n"
> (write-file "test.csv" csv)
639
> (csv2list (read-file "test.csv"))
(("Planet name" "Equator diameter (earth)" "Mass (earth)" "Orbital radius (AU)" "Orbital period (years)" 
  "Orbital Incline Angle" "Orbital Eccentricity" "Rotation (days)" "Moons") 
 ("Mercury" 0.382 0.06 0.387 0.241 7 0.206 58.6 0) 
 ("Venus" 0.949 0.82 0.72 0.615 3.39 0.0068 -243 0) 
 ("Earth" 1 1 1 1 0 0.0167 1 1) 
 ("Mars" 0.53 0.11 1.52 1.88 1.85 0.0934 1.03 2) 
 ("Jupiter" 11.2 318 5.2 11.86 1.31 0.0484 0.414 63) 
 ("Saturn" 9.41 95 9.54 29.46 2.48 0.0542 0.426 49) 
 ("Uranus" 3.98 14.6 19.22 84.01 0.77 0.0472 -0.718 27) 
 ("Neptune" 3.81 17.2 30.06 164.8 1.77 0.0086 0.671 13) 
 ("Pluto" 0.18 0.002 39.5 248.5 17.1 0.249 -6.5 3) 
 ("test" "FALSE" "TRUE" "nil" true))
> 

 こんな感じです。
 また、Linux で使われている改行だけの文字列にも、

> (replace "\r" csv "")
"\"Planet name\",\"Equator diameter (earth)\",\"Mass (earth)\",\"Orbital radius (AU)\",\"Orbital period (years)\",\"Orbital Incline Angle\",\"Orbital Eccentricity\",\"Rotation (days)\",\"Moons\"\n\"Mercury\",0.382,0.06,0.387,0.241,7,0.206,58.6,0\n\"Venus\",0.949,0.82,0.72,0.615,3.39,0.0068,-243,0\n\"Earth\",1,1,1,1,0,0.0167,1,1\n\"Mars\",0.53,0.11,1.52,1.88,1.85,0.0934,1.03,2\n\"Jupiter\",11.2,318,5.2,11.86,1.31,0.0484,0.414,63\n\"Saturn\",9.41,95,9.54,29.46,2.48,0.0542,0.426,49\n\"Uranus\",3.98,14.6,19.22,84.01,0.77,0.0472,-0.718,27\n\"Neptune\",3.81,17.2,30.06,164.8,1.77,0.0086,0.671,13\n\"Pluto\",0.18,0.002,39.5,248.5,17.1,0.249,-6.5,3\n\"test\",nil,true,FALSE,TRUE\n"
> (csv2list csv)
(("Planet name" "Equator diameter (earth)" "Mass (earth)" "Orbital radius (AU)" "Orbital period (years)" 
  "Orbital Incline Angle" "Orbital Eccentricity" "Rotation (days)" "Moons") 
 ("Mercury" 0.382 0.06 0.387 0.241 7 0.206 58.6 0) 
 ("Venus" 0.949 0.82 0.72 0.615 3.39 0.0068 -243 0) 
 ("Earth" 1 1 1 1 0 0.0167 1 1) 
 ("Mars" 0.53 0.11 1.52 1.88 1.85 0.0934 1.03 2) 
 ("Jupiter" 11.2 318 5.2 11.86 1.31 0.0484 0.414 63) 
 ("Saturn" 9.41 95 9.54 29.46 2.48 0.0542 0.426 49) 
 ("Uranus" 3.98 14.6 19.22 84.01 0.77 0.0472 -0.718 27) 
 ("Neptune" 3.81 17.2 30.06 164.8 1.77 0.0086 0.671 13) 
 ("Pluto" 0.18 0.002 39.5 248.5 17.1 0.249 -6.5 3) 
 ("test" "nil" true "FALSE" "TRUE"))
> 

 対応しています。parse に正規表現を使っていますから。
 ということで、さりげなく直しているこの部分。やはり、正規表現は、鬼門?(汗)

 以上、如何でしょうか?

広告