2012-11-19 9 views
5

Sto provando a usare PARSE per trasformare una linea CSV in un blocco Rebol. Abbastanza facile da scrivere in codice aperto, ma come per altre domande sto cercando di imparare che cosa può fare il dialetto senza quello.Come utilizzare il dialetto PARSE per leggere una riga da un CSV?

Quindi, se una linea dice:

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

poi voglio il blocco:

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

Questioni da notare:

  • citazioni incorporate nelle stringhe CSV vengono indicati con ""
  • Le virgole possono essere all'interno delle virgolette eh ENCE parte, non un separatore di colonna letterale
  • adiacenti virgole colonna di separazione indicano un campo vuoto
  • stringhe che non contengono virgolette o virgole possono apparire senza virgolette
  • Per il momento possiamo mantenere le cose come http://rebol.com come STRING! invece di LOAD loro ing in tipi come URL!

Per rendere più uniforme, la prima cosa che faccio è accodare una virgola alla linea di ingresso. Poi ho un column-rule che cattura una singola colonna terminata da una virgola ... che può essere tra virgolette o meno.

so quante colonne ci dovrebbe essere dovuto alla riga di intestazione, in modo che il codice dice poi:

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

ma io sono un po 'bloccato sulla scrittura column-rule. Ho bisogno di un modo nel dialetto per esprimere "Una volta trovato un preventivo, continua a saltare le coppie di quotazioni finché non trovi una citazione che si trova da sola." Qual è un buon modo per farlo?

risposta

3

Come per la maggior parte dei problemi di analisi, provo a creare una grammatica che descriva al meglio gli elementi del formato di input.

In questo caso, abbiamo sostantivi:

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

alcuni verbi:

[row-feed emit-value] 

E i sostantivi operative:

[current chunk current-row width] 

Suppongo che avrei potuto abbattere un un po 'di più, ma è abbastanza per lavorare. Innanzitutto, la fondazione:

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

Ora la struttura del valore. I valori indicati sono costruite da pezzi di caratteri o citazioni validi come li troviamo:

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 
    ) 
] 

noti che delimiter è impostato su ending all'inizio di ogni riga, poi cambiato in comma non appena si passa un valore.Pertanto, una riga di input è definita come [ending value any [comma value]].

Tutto ciò che rimane è quello di definire la struttura del documento:

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] 

avvolgetelo per proteggere tutte quelle parole, e si dispone di:

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

Tempo di risposta fantastico su una risposta che sembra (finora) per lavorare sui dati stravaganti che ho dato! – HostileFork

2

ho dovuto farlo anni fa. Ho aggiornato le mie funzioni per gestire tutti i casi che ho trovato da allora. Spero che ora sia più solido.

Avviso che può gestire stringhe con a capo all'interno MA:

  1. newlines nelle stringhe devono essere solo LF e ...
  2. nuova linea tra i record deve essere CRLF e ..
  3. è necessario caricare il file con read/binary in modo che Rebol non converta automaticamente le newline.

(1. e 2. è quello che Excel dare, per esempio)

; 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 
] 

Se necessario, ho la controparte block-to-csv.

[Edit] OK, la controparte (nota: tutti stringa sarà chiusa con doppio apice e l'intestazione deve essere in prima linea del blocco, se si desidera che nel risultato):

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

Ehi, grazie! In realtà ho bisogno di un 'block-to-csv' per questo compito, quindi se vuoi modificare la risposta per lanciarlo, mi impedirà di doverlo scrivere (anche se è il più facile dei due). – HostileFork

Problemi correlati