csv.scm 3.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. ;; guile-charting
  2. ;; Copyright (C) 2008, 2012, 2014, 2015, 2019 Andy Wingo <wingo at pobox dot com>
  3. ;; This library is free software; you can redistribute it and/or
  4. ;; modify it under the terms of the GNU Lesser General Public
  5. ;; License as published by the Free Software Foundation; either
  6. ;; version 3 of the License, or (at your option) any later version.
  7. ;;
  8. ;; This library is distributed in the hope that it will be useful,
  9. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. ;; Lesser General Public License for more details.
  12. ;;
  13. ;; You should have received a copy of the GNU Lesser General Public
  14. ;; License along with this library; if not, see
  15. ;; <http://www.gnu.org/licenses/>.
  16. (define-module (charting csv)
  17. #:use-module (ice-9 optargs)
  18. #:export (write-csv-row
  19. write-csv-rows
  20. csv-read
  21. csv-port->row-list))
  22. (define (write-csv-row port row delimiter)
  23. (define special-chars (char-set delimiter #\" #\newline))
  24. (define (escape-quotes elt)
  25. (string-join (string-split elt #\") "\"\""))
  26. (define (escape-element elt)
  27. (let ((elt (if (string? elt) elt (object->string elt))))
  28. (if (string-any special-chars elt)
  29. (string-append "\"" (escape-quotes elt) "\"")
  30. elt)))
  31. (display (string-join (map escape-element (vector->list row))
  32. (string delimiter))
  33. port)
  34. (newline port))
  35. (define (write-csv-rows port rows delimiter)
  36. (for-each (lambda (row) (write-csv-row port row delimiter)) rows))
  37. ;;; FIXME: rewrite with some kind of parser generator? functional, of
  38. ;;; course :-) Based on code from Ken Anderson <kanderson bbn com>, from
  39. ;;; http://article.gmane.org/gmane.lisp.guile.user/2269.
  40. (define (csv-read-row port delimiter have-cell init-seed)
  41. (define (!)
  42. (let ((c (read-char port)))
  43. c))
  44. (define (finish-cell b seed)
  45. (have-cell (list->string (reverse b)) seed))
  46. (define (next-cell b seed)
  47. (state-init (!) (finish-cell b seed)))
  48. (define (state-init c seed)
  49. (cond ((eqv? c delimiter) (state-init (!) (have-cell "" seed)))
  50. ((eqv? c #\") (state-string (!) '() seed))
  51. ((eqv? c #\newline) seed)
  52. ((eof-object? c) seed)
  53. (else (state-any c '() seed))))
  54. (define (state-string c b seed)
  55. (cond ((eqv? c #\") (state-string-quote (!) b seed))
  56. ((eof-object? c) (error "Open double-quoted string" (list->string (reverse b))))
  57. (else (state-string (!) (cons c b) seed))))
  58. (define (state-string-quote c b seed)
  59. (cond ((eqv? c #\") (state-string (!) (cons c b) seed)) ; Escaped double quote.
  60. ((eqv? c delimiter) (next-cell b seed))
  61. ((eqv? c #\newline) (finish-cell b seed))
  62. ((eof-object? c) (finish-cell b seed))
  63. (else (error "Single double quote at unexpected place." c b))))
  64. (define (state-any c b seed)
  65. (cond ((eqv? c delimiter) (next-cell b seed))
  66. ((eqv? c #\newline) (finish-cell b seed))
  67. ((eof-object? c) (finish-cell b seed))
  68. (else (state-any (!) (cons c b) seed))))
  69. (state-init (!) init-seed))
  70. (define (csv-read port delimiter new-row have-cell have-row init-seed)
  71. (let lp ((seed init-seed))
  72. (cond
  73. ((eof-object? (peek-char port)) seed)
  74. (else (lp (have-row (csv-read-row port delimiter have-cell (new-row seed))
  75. seed))))))
  76. (define* (csv-port->row-list #:optional (port (current-input-port)) (delimiter #\,))
  77. (reverse
  78. (csv-read port
  79. delimiter
  80. (lambda (rows) '())
  81. (lambda (cell row) (cons cell row))
  82. (lambda (row rows) (cons (list->vector (reverse row)) rows))
  83. '())))