2012-11-19 21 views
5

私はPARSEを使ってCSVラインをRebolブロックに変換しようとしています。簡単にオープンコードで書くことができますが、他の質問と同様に、私は方言がそれなしで何ができるのかを学ぼうとしています。だから、PARSE方言を使ってCSVから行を読み込む方法は?

ラインが言う場合:

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

その後、私はブロックをしたい:注意すべき

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

問題:

    は、CSV文字列で
  • 埋め込まれた引用符が""
  • で示されます
  • カンマは引用符で囲み、h
  • 隣接する列分離カンマは、我々は物事を保つことができる引用符やカンマを含まない空のフィールド
  • 文字列が一瞬株価
  • せずに表示されることが示しているリテラルではなく、列セパレータのレンスの一部STRINGとしてhttp://rebol.comのように!代わりに、それはより均一にするために、このようなURL!

ようなタイプにそれらをINGのLOADの、私が最初にすることは、入力ラインにカンマを追加しています。次に、私はcolumn-ruleを持っています。これはカンマで区切られた単一の列をキャプチャします。引用符で囲まれていてもいなくてもかまいません。

私はヘッダ行が原因があるはずですどのように多くの列を知っているので、コードは言う:

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

しかし、私は少しcolumn-ruleを書くことにこだわっています。 を表現する方言が必要です。「見積もりを見つけたら、見積もりが一揃いになるまで見積もりの​​ペアをスキップしてください。」これを行うには良い方法はありますか?

答えて

3

ほとんどの解析問題と同様に、私は入力フォーマットの要素を最もよく説明する文法を構築しようとします。

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

いくつかの動詞:

[row-feed emit-value] 

そして手術名詞:私は、私はおそらくそれを打破することができたとし

[current chunk current-row width] 

は、このケースでは、名詞を持っていますもう少しですが、十分に機能します。まず、基礎:

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

今、値の構造。我々はそれらを見つけると引用符で囲まれた値は、有効な文字や引用符の塊から構築されていますdelimiterは、各行の先頭にendingに設定されていることを

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

注意し、私たちとすぐに値を渡すようcommaに変更。したがって、入力行は[ending value any [comma value]]と定義されます。

すべてのことが残っているが、文書の構造を定義することです:

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] 

は、すべてのそれらの単語を遮蔽するためにそれをラップし、あなたが持っている:

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

私はそれを与えたばかげたデータを扱うように(これまでのところ)回答に驚くほどの応答時間があります! – HostileFork

2

私は数年前ということをしなければなりませんでした。 その後、私が見つけたすべてのケースを処理するために自分のfuncを更新しました。私は今それがよりしっかりしていることを望む。

それはしかし内部に改行して文字列を扱うことができることに注意してください:文字列内の

  1. 改行はLFする必要がありますレコード間のみ ...
  2. 改行はCRLF でなければなりません。..
  3. Rebolは改行を自動的に変換しないように、ファイルを読み込み/バイナリでロードする必要があります。

(1.と2.は、Excelは、たとえば、与えるものです)

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

必要であれば、私は、相手block-to-csvを持っています。

[編集] OK、カウンターパート(注:!あなたは結果でそれをしたい場合は、すべての文字列は二重引用符やヘッダで囲まれますが、ブロックの最初の行でなければなりません):

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

ねえ、ありがとう!私は実際にこの作業のために 'block-to-csv'を必要とします。もしあなたがそれを投げ入れる答えを編集したいのであれば、それを書く必要がなくなります。 – HostileFork

関連する問題