5

PARSE を使用して CSV 行を Rebol ブロッ​​クに変換しようとしています。オープンコードで書くのは簡単ですが、他の質問と同様に、方言がそれなしで何ができるかを学ぼうとしています.

したがって、次のような行があるとします。

"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 文字列に埋め込まれた引用符は、""
  • カンマは引用符の中に入れることができるため、列区切りではなくリテラルの一部になります
  • 隣接する列区切りのコンマは空のフィールドを示します
  • 引用符またはコンマを含まない文字列は、引用符なしで表示できます
  • 現時点http://rebol.comでは、STRINGのようなものを保持できます。URLなどの型にLOADする代わりに!

より均一にするために、最初に入力行にコンマを追加します。次にcolumn-rule、カンマで終了する単一の列をキャプチャする があります...引用符で囲まれているかどうかに関係なく。

ヘッダー行が原因で列がいくつあるかはわかっているので、コードは次のように述べています。

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

しかし、私は書くことに少し行き詰まっていcolumn-ruleます。方言で「引用を見つけたら、すべて独立した引用が見つかるまで引用のペアをスキップし続ける」を表現する方法が必要です。 それを行う良い方法は何ですか?

4

3 に答える 3

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]

次に、値の構造です。引用符で囲まれた値は、有効な文字または引用符のチャンクを見つけたときに構築されます。

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

は各行の先頭で にdelimiter設定され、値を渡すとすぐに に変更されることに注意してください。したがって、入力行は として定義されます。endingcomma[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]
    ]
]
于 2012-11-19T13:42:52.737 に答える
2

私は何年も前にそれをしなければなりませんでした。それ以降に見つけたすべてのケースを処理するために、関数を更新しました。私はそれが今よりしっかりしていることを願っています。

BUT内に改行を含む文字列を処理できることに注意してください。

  1. 文字列の改行は LF のみである必要があり ...
  2. レコード間の改行は CRLF である必要があり、 ...
  3. Rebol が自動的に改行を変換しないように、ファイルを read/binary でロードする必要があります。

(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
]
于 2012-11-20T08:52:31.050 に答える
2

さらに、BrianH の rebol.org で %csv-tools.r スクリプトを見つけてください。

http://www.rebol.org/view-script.r?script=csv-tools.r

素晴らしいコードです。R2 と R3 で動作します。

于 2012-12-06T16:42:01.143 に答える