A small, fast, and (I think) complete csv parser. Now handles newlines within fields. A comma is now the default field-separator. | class Array | def to_csv | ",".is_fs if $csv_fs.nil? | s = '' | self.map { |item| | str = item.to_s | # Quote the string if it contains the field-separator or | # a " or a newline, or if it has leading or trailing whitespace. | if str.index($csv_fs) or /^\s|"|\n|\s$/.match(str) | str = '"' + str.gsub( /"/, '""' ) + '"' | end | str | }.join($csv_fs) | end | def unescape | self.map{|x| x.gsub( /""/, '"' ) } | end | end | | class String | # Set regexp for parse_csv. | # self is the field-separator, which must be | # a single character. | def is_fs | $csv_fs = self | if "^" == $csv_fs | fs = "\\^" | else | fs = $csv_fs | end | $csv_re = \ | ## Assumes embedded quotes are escaped as "". | %r{ \s* | (?: | "( [^"]* (?: "" [^"]* )* )" | | ( .*? ) | ) | \s* | [#{fs}] | }mx | end | | def parse_string | (self + $csv_fs).scan( $csv_re ).flatten.compact.unescape | end | end | | def get_rec( file ) | ",".is_fs if $csv_re.nil? | $csv_s = "" | begin | if file.eof? | raise "The csv file is malformed." if $csv_s.size>0 | return nil | end | $csv_s += file.gets | end until $csv_s.count( '"' ) % 2 == 0 | $csv_s.chomp! | $csv_s.parse_string | end | | | while rec = get_rec( ARGF ) | puts "----------------" | puts $csv_s | p rec | puts rec.to_csv | | end