next up previous contents index
Search Next: Arrays und Hashtabellen Up: Reguläre Ausdrücke Previous: Reguläre Ausdrücke - ein   Contents   Index


CSV-Parser

Für den Datenaustausch zwischen Tabellenkalkulationen und Datenbanken gibt es ein einfaches Datenformat, das von praktisch jeder Applikation unterstützt wird: kommagetrennte Werte, in Englisch: Comma Separated Values. Der im Folgenden beschriebene CSV-Parser entstand aus der Notwendigkeit, die exportierten Daten einer Kontaktliste etwas zu bearbeiten, bevor sie in die Kundentabelle einer Fakturierungssoftware importiert werden konnten.

Das einfachere UNQUOTED-Muster passt auf den Text bis zum nächsten Komma oder dem (möglicherweise DOS-)Zeilenende und ist für einen "`normalen"' Eintrag zuständig. Komplexe Einträge, die einen Zeilenwechsel oder ein Komma im Text enthalten, werden mit dem QUOTED-Muster erfasst: Genau einem öffnenden Anführungszeichen "{1,1} folgen beliebig viele andere Zeichen oder doppelte Anführungszeichen ([^"]|"")*?, bis das nächste einzelne Anführungszeichen "{1,1}(,|\r?\n) vor einem Komma oder dem (DOS-)Zeilenende den Eintrag beendet. Die doppelten Anführungszeichen werden im CSV-Format verwendet, wenn in der Applikation ein einzelnes " steht.

Die Option m am Ende des regulären Ausdrucks weist Ruby an, die Mustererkennung mehrzeilig durchzuführen, das heißt, Zeilenwechsel werden wie gewöhnliche Zeichen behandelt. Ohne diese Option würden Zeilenwechsel in einem Feld den Parser aus dem Tritt bringen.

Beide Muster liefern in md[1] den zu übernehmenden Inhalt des Datenfelds und in md[0] den durch sie bearbeiteten Teilstring.

class CSVParser
  include Enumerable
  QUOTED = /"{1,1}(([^"]|"")*?)"{1,1}(,|\r?\n)/m
  UNQUOTED = /(.*?)(,|\r?\n)/m
  def initialize(string)
    @string = string
  end

  ## ich liefere die Datenfelder einer Zeile
  ## in einem Array
  def each
    while @string != ""
      tokens = []
      while @string != ""
        case @string[0..0]
          ## das aktuelle Feld ist leer
          when ','
            tokens << nil
            @string.slice!(0..0)
            next
          ## das letzte Feld der Zeile ist leer
          when /\r?\n/
            tokens << nil
            @string.slice!(0...$&.size)
            break
          ## das Feld ist komplex
          when '"'
            pattern = QUOTED
            dequote = true
          ## das Feld ist einfach
          else
            pattern = UNQUOTED
            dequote = false
        end
        ## Datenfeld mit Inhalt
        md = pattern.match(@string)
        token = md[1]
        token.gsub!('""', '"') if dequote
        tokens << token
        @string.slice!(0...md[0].size)
        ## letztes Feld der Zeile
        break if md[0][-1..-1] == "\n"
      end
      yield tokens
    end
  end
end


\epsfig{height=36pt,file=images/chan.eps}Durch die include-Anweisung und die Definition einer each-Methode erhält ein CSVParser alle nicht sortierabhängigen Methoden des Enumerables-Moduls, siehe Abschnitt 6.3. \epsfig{height=10pt,file=images/ruby.eps}

Die Anwendung des Parsers sieht so aus:

csv = CSVParser.new($stdin.read)
csv.each_with_index { |row, i|
  puts "#{i}: #{row.join('_')}"
}

Konsole:
1,2,3
ab,"c,d","e""f","g"",""","h
ij",kl

Ausgabe:
0: 1_2_3
1: ab_c,d_e"f_g","_h
ij_kl


next up previous contents index
Search Next: Arrays und Hashtabellen Up: Reguläre Ausdrücke Previous: Reguläre Ausdrücke - ein   Contents   Index
(C) 2002 by dpunkt.de, Armin Roehrl, Stefan Schmiedl, Clemens Wyss 2002-01-20