2012-11-19 9 views
5

Ich versuche, mit PARSE eine CSV-Zeile in einen Rebol-Block zu verwandeln. Einfach genug, um in offenen Code zu schreiben, aber wie bei anderen Fragen versuche ich zu lernen, was der Dialekt ohne das tun kann.Wie benutzt man den PARSE-Dialekt, um eine Zeile von einer CSV einzulesen?

Also, wenn eine Zeile sagt:

"Look, that's ""MR. Fork"" to you!",Hostile Fork,,http://hostilefork.com 

Dann möchte ich den Block:

[{Look, that's "MR. Fork" to you!} {Hostile Fork} none {http://hostilefork.com}] 

Probleme bemerken:

  • Embedded Anführungszeichen in CSV-Strings werden mit "" angegeben
  • Kommas können in Anführungszeichen stehen und h ENCE Teil des wörtlichen, nicht eine Spaltenseparator
  • benachbarten Spalte Trenn Kommas zeigen ein leeres Feld
  • Strings, die keine Anführungszeichen oder Kommas ohne Anführungszeichen
  • Im Moment erscheinen können wir die Dinge halten können wie http://rebol.com als STRING! statt LOAD sie in Typen wie URL!

ing Um es einheitlich zu machen, das erste, was ich tue, ist ein Komma an die Eingangsleitung anhängen. Dann habe ich eine column-rule, die eine einzelne Spalte mit einem Komma endet beendet ... die entweder in Anführungszeichen oder nicht sein kann.

Ich weiß, wie viele Spalten es sollte in die Kopfzeile fällig, so dass der Code dann sagt:

unless parse line compose [(column-count) column-rule] [ 
    print rejoin [{Expected } column-count { columns.}] 
] 

Aber ich bin ein wenig auf das Schreiben column-rule stecken. Ich brauche einen Weg im Dialekt, um auszudrücken "Sobald Sie ein Zitat finden, überspringen Sie Zitat-Paare, bis Sie ein Zitat finden, das ganz allein steht." Was ist ein guter Weg, das zu tun?

Antwort

3

Wie bei den meisten Parse-Problemen versuche ich eine Grammatik zu erstellen, die die Elemente des Eingabeformats am besten beschreibt.

In diesem Fall haben wir Substantive:

[comma ending value-chars qmark quoted-chars value header row] 

Einige Verben:

[row-feed emit-value] 

Und die operativen Substantive:

[current chunk current-row width] 

Ich glaube, ich es möglicherweise nach unten brechen könnte eine etwas mehr, aber es ist genug, um damit zu arbeiten. Zuerst die Grundlage:

comma: "," 
ending: "^/" 
qmark: {"} 
value-chars: complement charset reduce [qmark comma ending] 
quoted-chars: complement charset reduce [qmark] 

Jetzt die Wertstruktur. Angegebenen Werte sind aus Stücken von gültigen Zeichen oder Anführungszeichen aufgebaut, wie wir sie finden:

current: chunk: none 
quoted-value: [ 
    qmark (current: copy "") 
    any [ 
     copy chunk some quoted-chars (append current chunk) 
     | 
     qmark qmark (append current qmark) 
    ] 
    qmark 
] 

value: [ 
    copy current some value-chars 
    | quoted-value 
] 

emit-value: [ 
    (
     delimiter: comma 
     append current-row current 
    ) 
] 

emit-none: [ 
    (
     delimiter: comma 
     append current-row none 
    ) 
] 

Beachten Sie, dass delimiter wird auf ending am Anfang jeder Zeile, dann comma geändert, sobald wir einen Wert übergeben.Daher wird eine Eingabezeile als [ending value any [comma value]] definiert.

Alles, was bleibt, ist die Dokumentstruktur zu definieren:

current-row: none 
row-feed: [ 
    (
     delimiter: ending 
     append/only out current-row: copy [] 
    ) 
] 

width: none 
header: [ 
    (out: copy []) 
    row-feed any [ 
     value comma 
     emit-value 
    ] 
    value body: ending :body 
    emit-value 
    (width: length? current-row) 
] 

row: [ 
    row-feed width [ 
     delimiter [ 
      value emit-value 
      | emit-none 
     ] 
    ] 
] 

if parse/all stream [header some row opt ending][out] 

es all diese Worte zu schützen einhüllen und Sie haben:

REBOL [ 
    Title: "CSV Parser" 
    Date: 19-Nov-2012 
    Author: "Christopher Ross-Gill" 
] 

parse-csv: use [ 
    comma ending delimiter value-chars qmark quoted-chars 
    value quoted-value header row 
    row-feed emit-value emit-none 
    out current current-row width 
][ 
    comma: "," 
    ending: "^/" 
    qmark: {"} 
    value-chars: complement charset reduce [qmark comma ending] 
    quoted-chars: complement charset reduce [qmark] 

    current: none 
    quoted-value: use [chunk][ 
     [ 
      qmark (current: copy "") 
      any [ 
       copy chunk some quoted-chars (append current chunk) 
       | 
       qmark qmark (append current qmark) 
      ] 
      qmark 
     ] 
    ] 

    value: [ 
     copy current some value-chars 
     | quoted-value 
    ] 

    current-row: none 
    row-feed: [ 
     (
      delimiter: ending 
      append/only out current-row: copy [] 
     ) 
    ] 
    emit-value: [ 
     (
      delimiter: comma 
      append current-row current 
     ) 
    ] 
    emit-none: [ 
     (
      delimiter: comma 
      append current-row none 
     ) 
    ] 

    width: none 
    header: [ 
     (out: copy []) 
     row-feed any [ 
      value comma 
      emit-value 
     ] 
     value body: ending :body 
     emit-value 
     (width: length? current-row) 
    ] 

    row: [ 
     opt ending end break 
     | 
     row-feed width [ 
      delimiter [ 
       value emit-value 
       | emit-none 
      ] 
     ] 
    ] 

    func [stream [string!]][ 
     if parse/all stream [header some row][out] 
    ] 
] 
+0

Fantastische Reaktionszeit auf eine Antwort, die (bis jetzt) ​​scheint, an den verrückten Daten zu arbeiten, die ich es gegeben habe! – HostileFork

2

Ich hatte vor Jahren zu tun. Ich habe meine Funktionen aktualisiert, um alle Fälle zu behandeln, die ich seitdem gefunden habe. Ich hoffe, es ist jetzt fester.

Beachten Sie, dass es Strings mit Zeilenumbrüche innerhalb handhaben kann, aber:

  1. Zeilenumbrüche in Strings müssen nur und ...
  2. Newline zwischen Datensätzen sein muss CRLF und LF werden ..
  3. Sie müssen die Datei mit read/binary laden, damit Rebol die Zeilenumbrüche nicht automatisch konvertiert.

(1. und 2. ist es, was Excel geben, zum Beispiel)

; Conversion function from CSV format 
csv-to-block: func [ 
    "Convert a string of CSV formated data to a Rebol block. First line is header." 
    csv-data [string!] "CSV data." 
    /separator separ [char!] "Separator to use if different of comma (,)." 
    /without-header "Do not include header in the result." 
    /local out line start end this-string header record value data chars spaces chars-but-space 
    ; CSV format information http://www.creativyst.com/Doc/Articles/CSV/CSV01.htm 
] [ 
    out: copy [] 
    separ: any [separ #","] 

    ; This function handle replacement of dual double-quote by quote while copying substring 
    this-string: func [s e] [replace/all copy/part s e {""} {"}] 
    ; CSV parsing rules 
    header: [(line: copy []) value any [separ value | separ (append line none)] (if not without-header [append/only out line])] 
    record: [(line: copy []) value any [separ value | separ (append line none)] (append/only out line)] 
    value: [any spaces data any spaces (append line this-string start end)] 
    data: [start: some chars-but-space any [some spaces some chars-but-space] end: | #"^"" start: any [some chars | {""} | separ | newline] end: #"^""] 
    chars: complement charset rejoin [ {"} separ newline] 
    spaces: charset exclude { ^-} form separ 
    chars-but-space: exclude chars spaces 

    parse/all csv-data [header any [newline record] any newline end] 
    out 
] 

Wenn nötig, ich habe das Gegenstück block-to-csv.

[Bearbeiten] OK, das Gegenstück (Anmerkung: alle String mit doppelten Anführungszeichen und Header eingeschlossen werden in der ersten Zeile des Blocks sein müssen, wenn Sie es im Ergebnis wollen):

block-to-csv: func [ 
    "Convert a block of blocks to a CSV formated string." 
    blk-data [block!] "block of data to convert" 
    /separator separ "Separator to use if different of comma (,)." 
    /local out csv-string record value v 
] [ 
    out: copy "" 
    separ: any [separ #","] 
    ; This function convert a string to a CSV formated one 
    csv-string: func [val] [head insert next copy {""} replace/all replace/all copy val {"} {""} newline #{0A} ] 
    record: [into [some [value (append out separ)]]] 
    value: [set v string! (append out csv-string v) | set v any-type! (append out form v)] 

    parse/all blk-data [any [record (remove back tail out append out crlf)]] 
    out 
] 
+0

Hey, danke! Ich brauche tatsächlich eine 'Block-zu-Csv'-Funktion für diese Aufgabe. Wenn Sie also die Antwort bearbeiten wollen, um sie einzufügen, würde ich sie nicht schreiben müssen (obwohl es die leichtere der beiden ist). – HostileFork