2012-11-19 6 views
5

Próbuję użyć PARSE, aby przekształcić linię CSV w blok Rebola. Łatwo napisać w otwartym kodzie, ale tak jak w przypadku innych pytań, staram się dowiedzieć, co dialekt może zrobić bez tego.Jak używać dialera PARSE do czytania w wierszu z pliku CSV?

Więc jeśli linia mówi:

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

Następnie chcę bloku:

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

Problemy zauważyć:

  • Osadzone cudzysłowy w ciągi CSV są oznaczone ""
  • Przecinki mogą znajdować się w cudzysłowach i h część ence z dosłownym, a nie separatorem kolumny
  • Sąsiednie przecinki oddzielające kolumny wskazują pustym polu
  • ciągi, które nie zawierają cytaty lub przecinki mogą pojawić się bez cudzysłowów
  • Na razie możemy przechowywać rzeczy jak http://rebol.com jako STRING! zamiast LOAD ing je do typów, takich jak URL!

aby uczynić go bardziej jednolite, pierwszą rzeczą, jaką mogę zrobić, to dołączyć przecinek do linii wejściowej. Następnie mam column-rule, który przechwytuje pojedynczą kolumnę zakończoną przecinkiem ... który może być cytowany lub nie.

wiem ile kolumn nie powinno wynikać z linii nagłówka, więc kod następnie mówi:

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

Ale jestem trochę zatrzymany na piśmie column-rule. Potrzebuję sposobu w dialekcie, aby wyrazić "Gdy znajdziesz cytat, pomijaj cytowane pary, aż znajdziesz wycenę stojącą zupełnie sama." Co to jest dobry sposób na zrobienie tego?

Odpowiedz

3

Jak w przypadku większości problemów Analiza składni próbuję zbudować gramatyki, która najlepiej opisuje elementy formatu wejściowego.

W tym przypadku mamy rzeczowniki:

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

Niektóre czasowniki:

[row-feed emit-value] 

a rzeczowniki operacyjny:

[current chunk current-row width] 

Przypuszczam mógłbym złamać go w dół niewiele więcej, ale wystarcza do pracy. Najpierw podstawa:

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

Teraz struktura wartości. Podane wartości są zbudowane z kawałkami poprawnych znaków lub cytatów jak je znaleźć:

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

Zauważ, że delimiter jest ustawiony na ending na początku każdego wiersza, a następnie zmieniono na comma jak najszybciej przekazać wartość.Zatem wiersz wejściowy jest zdefiniowany jako [ending value any [comma value]].

Pozostaje do definiowania struktury dokumentu:

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] 

owinąć ją osłonić wszystkie te słowa i masz:

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

Fantastyczny czas odpowiedzi na odpowiedź, która wydaje się (do tej pory) pracować nad zwariowanymi danymi, które mu dałem! – HostileFork

2

musiałem zrobić lata temu. Mam zaktualizowane moje funkcje do obsługi wszystkich przypadków, które znalazłem od tego czasu. Mam nadzieję, że teraz jest bardziej solidny.

Zauważ, że może obsługiwać strun z nowymi liniami wewnątrz BUT:

  1. newlines w ciągi należy LF tylko i ...
  2. nowej linii między rekordy muszą być CRLF i ..
  3. musisz załadować plik z read/binary, aby Rebol automatycznie nie konwertował linii.

(1. i 2. jest to, co daje Excel, na przykład)

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

W razie potrzeby, mam odpowiednika block-to-csv.

[Edytuj] OK, odpowiednik (uwaga: cały łańcuch zostanie zamknięty z podwójnym cudzysłowem i cel musi być w pierwszym wierszu bloku, jeśli chcesz go w wyniku):

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

Hej, dzięki! Właściwie do tego zadania potrzebuję 'block-to-csv', więc jeśli chcesz edytować odpowiedź i wrzucić to, to powstrzymałoby mnie to od napisania (chociaż jest to łatwiejsze z dwóch). – HostileFork

Powiązane problemy