とりあえずいい加減なパーサがこんな感じ。
(import (rnrs) (packrat) (srfi :14 char-set)) (define *text-set* (ucs-range->char-set! #x2d #x7e #f (ucs-range->char-set! #x23 #x2b #f (ucs-range->char-set #x20 #x21)))) (define (any results) (let loop ((acc '()) (results results)) (let ((ch (parse-results-token-value results))) ;; it's just testing (if (and ch (char-set-contains? *text-set* ch)) (loop (cons ch acc) (parse-results-next results)) (make-result (list->string (reverse! acc)) results))))) (define (textdata results) (any results)) (define parser (packrat-parser (begin (define (crlf results) (let ((ch (parse-results-token-value results))) (case ch ((#\linefeed) (make-result "" results)) ((#\return) (let ((ch (parse-results-token-value (parse-results-next results)))) (if (char=? #\linefeed ch) (make-result "" results) (field-entry results)))) (else (field-entry results))))) file) (file ((h <- header '#\linefeed r <- records) (cons h r)) ((r <- records) r)) (header ((n <- names) (cons :header n))) (records ((f <- fields '#\linefeed r <- records) (cons (cons :record f) r)) ((f <- fields) (list (cons :record f)))) (fields ((f <- field-entry '#\, fs <- fields) (cons f fs)) ;; (1) ((f <- field-entry) (list f))) (names ((n <- field-entry '#\, ns <- fields) (cons n ns)) ((n <- field-entry) (list n))) (field-entry ((e <- textdata) e)))) (define (generator p) (let ((ateof #f) (pos (top-parse-position ""))) (lambda () (if ateof (values pos #f) (let ((x (read-char p))) (if (eof-object? x) (begin (set! ateof #t) (values pos #f)) (let ((old-pos pos)) (set! pos (update-parse-position pos x)) (values old-pos (cons x x))))))))) (define (parse-csv p) (let ((result (parser (base-generator->results (generator p))))) (if (parse-result-successful? result) (parse-result-semantic-value result) (apply assertion-violation (let ((e (parse-result-error result))) (list 'parse-csv (parse-error-messages e) (parse-position->string (parse-error-position e)) (parse-error-expected e))))))) (call-with-input-file "test.csv" (lambda (p) (parse-csv p)))) #| 入力ファイル aaa,bbb ccc,ddd,eee fff,ggg,hhh a,b,c 出力 ((:header "aaa" "bbb") (:record "ccc" "ddd" "eee") (:record "fff" "ggg" "hhh") (:record "a" "b" "c")) |#generatorはドキュメントにあったものをちょっと改変しただけ。多分これが基本なんだろう。
かなり適当で本来はCRLFなのがLFだけだったり、エスケープされたものを認識しなかったりするがまぁ動く。気になったのは「/」の使い方で、こんな感じには使えないという不便さであった。
(entry (((/ (e <- escaped) (ne <- non-escaped))) (if e e ne)))展開されたコードをみたらまぁ納得なのだが、これがやれないとなると結構面倒だよなぁ。あとこのライブラリは「*」とか「+」とか「?」がないので、それらを書こうと思ったら、(1)のように条件を2つ作る必要がある。
やっぱり正規表現をつかってやるべきだろうか。
No comments:
Post a Comment