けいじゅ@日本ラショナルソフトウェアです.

In [ruby-list :18477 ] the message: "[ruby-list:18477] [book]
csv_split ", on Nov/08 23:11(JST) Kazuhiro Nishiyama writes:

>こんばんは、ZnZです。

どもども.

>csv_split1は、列が「"」で終わる場合に
>例外(cannot decode CSV (RuntimeError))が発生します。

うーん.... 

こんな感じでしょうか... 正規表現がだんだん複雑になるのが気が重いけど...

# (アルゴリズムの本質を変えない)エレガントな解答求む.
--
###  csv_split

def csv_split(source, delimiter = ',')
  csv = []			 # splitした結果が入る.
  data = ""                      # 解析しているフィールドの文字列が入る.

  #(1)  文字列をデリミタで区切り, その区切られた文字列毎にeachで処理する.
  source.split(delimiter).each do |d|

    # (2) 継続フィールドの処理(フィールドが`"'で始まっている時のため)
    if data.empty?
      data = d
    else
      data += delimiter + d
    end

    if /^"/ =~ data
      # (3.1) 文字列が`"'で始まっている時の処理

      if /(^"|[^"])("")*"$/ =~ data 
	# (3.1.1) `"'で終了している時の処理

	# (A) "で始まるフィールドの処理
	csv << data.sub(/^"(.*)"$/, '\1').gsub(/""/, '"') 
	data = ''
      end
    else
      # (3.2) `"'で始まっていない時の処理
      csv << d
      data = ''
    end
  end
  raise "cannot decode CSV\n" unless data.empty?
  csv
end
--

>csv_split3は行が「"」で終わる場合に例外が発生します。
>./csv_split3.rb:41:in `concat': failed to convert nil into String (TypeError)
>        from ./csv_split3.rb:41:in `csv_split_for_quoted_field'
>        from ./csv_split3.rb:7:in `csv_split'
>
>また、のcsv_split3は最後の列が空の時に無視されるようです。
>
>
>p csv_split(',""""')   # =>どちらも例外
>p csv_split(',"""",')  # =>csv_split3だと["", "\""]になる。

うーん. うーん.

前者の問題は解決が楽だけど, 後者は.... csv_split2と同じく番兵(?)を置くこ
とにしました. 

# こちらもエレガントな解答求む
--
###  csv_split3

# (1) csv_splitの本体
def csv_split(source, delimiter = ',')
  chars = (source+",").split(//)              # (A) 文字列を1文字ずつに分解する
  csv = []
  while c = chars.shift			# 配列の先頭から1要素取り出し, その
                                        # 要素を配列から削除する
    case c
    when '"'
      # 配列csvにフィールドを追加.
      csv.push csv_split_for_quoted_field(chars, delimiter)
    else
      # 先ほど取り出した文字を戻す.
      chars.unshift c
      csv.push csv_split_for_normal_field(chars, delimiter)
    end
  end
  csv
end

# (2) 通常のフィールドの処理
def csv_split_for_normal_field(chars, delimiter)
  field = ""
  while c = chars.shift
    case c
    when delimiter
      return field
    else
      field.concat c			    # 文字列fieldの最後に文字列cを追加
    end
  end
  field
end
      
# (3) `"'で始まるフィールドの処理
def csv_split_for_quoted_field(chars, delimiter)
  field = ""
  while c = chars.shift
    case c
    when '"'
      c1 = chars.shift
      if c1 == delimiter or c1.nil?
	return field
      elsif c1 == '"'
	field.concat '"'
      else
	# (B-1) 不正フォーマット
	field.concat c
	field.concat c1
      end
    else
      field.concat c
    end
  end
  # (B-2) 不正フォーマット
  field
end



__
..............................石塚 圭樹@日本ラショナルソフトウェア...
----------------------------------->> e-mail: keiju / rational.com <<---