ARNESI

Reading and Writing files in Comma-Seperated-Values format 

Generating CSV files from lisp data

(defun princ-csv (items csv-stream
                  &key (quote #\")
                       (separator #\,)
                       (ignore-nulls t)
                       (newline +CR-LF+)
                       (princ #'princ-to-string))
  "Write the list ITEMS to csv-stream."
  (flet ((write-word (word)
           (write-char quote csv-stream)
           (loop
              for char across (funcall princ word)
              if (char= quote char) do
                (progn
                  (write-char quote csv-stream)
                  (write-char quote csv-stream))
              else do
                (write-char char csv-stream))
           (write-char quote csv-stream)))
    (when items
      (write-word (car items))
      (dolist (i (cdr items))
        (write-char separator csv-stream)
        (if ignore-nulls
            (when (not (null i))
              (write-word i))
            (write-word i)))
      (write-sequence newline csv-stream))))
(defun princ-csv-to-string (items)
  (with-output-to-string (csv)
    (princ-csv items csv)))

Reading in CSV files

(defun parse-csv-string (line &key (separator #\,) (quote #\"))
  "Parse a csv line into a list of strings using @var{seperator}
  as the column seperator and @var{quote} as the string quoting
  character."
  (let ((items '())
        (offset 0)
        (current-word (make-array 20 :element-type 'character
                                     :adjustable t
                                     :fill-pointer 0))
        (state :read-word))
    (labels ((current-char ()
               (aref line offset))
             (current-char= (char)
               (char= (current-char) char))
             (chew-current-word ()
               (push current-word items)
               (setf current-word (make-array 20 :element-type 'character
                                                 :adjustable t
                                                 :fill-pointer 0))))
      (loop
         (when (= (length line) offset)
           (ecase state
             (:in-quotes
              (error "Premature end of line."))
             (:read-word
              (chew-current-word)
              (return-from parse-csv-string (nreverse items)))))
         (ecase state
           (:in-quotes
            (if (current-char= quote)
                (progn
                  (when (= (length line) (1+ offset))
                    (error "Premature end of line."))
                  (if (char= (aref line (1+ offset)) quote)
                      (progn
                        (vector-push-extend quote current-word)
                        (incf offset))
                      (setf state :read-word)))
                (vector-push-extend (current-char) current-word)))
           (:read-word
            (if (current-char= quote)
                (setf state :in-quotes)
                (if (current-char= separator)
                    (chew-current-word)
                    (vector-push-extend (current-char) current-word)))))
         (incf offset)))))