CodeIQ:CSVからHTMLに変換しよう

私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「CSVからHTMLに変換しよう」(CodeIQ)を参照してください。

問題の概要

問題を引用します。
あなたはCSVフォーマットのデータを、テーブルタグを用いてHTML化する仕事を任されました。
もちろんこのような処理を行うツールはありますが、今後カスタマイズすることを前提に自動化したいとのこと。
そこで、CSVからHTMLに置換するプログラムを作ることになりました。

求められるプログラムの前提条件は、以下の通りとなります。

標準入力から、CSVフォーマット(wikipedia参照)のデータが送られる
なおCSVはコンマ「,」(U+002C) 区切りで、文字列フィールドはダブルクォート「"」(U+0022)で囲まれる場合がある
CSVの文字列フィールドに、改行を含む制御文字や、全角文字は含まれないものとする
CSVにはヘッダ行が必ず存在し、カラム名を表している
CSVを読み込み、table, tr, th, tdタグを用いてHTMLに変換すること
このときカラム名はthタグで囲み、各レコードのフィールドはtdタグで囲むこと
文字列フィールドは文字実体参照(wikipedia参照)を用いてエスケープ処理をすること
ただし、<(小なり記号)、>(大なり記号)、&(アンパサンド)のみの対応でよいものとする
なお出力するHTMLは部分的なものであり、テーブル関連タグ以外は用いないこと
また、出力するHTMLに整形のための改行は含めないこと
変換したHTMLを標準出力に返すこと

以下、置換例となります。

【入出力サンプル】
標準入力
"x","y"
1,2

標準出力
<table><tr><th>x</th><th>y</th></tr><tr><td>1</td><td>2</td></tr></table>

実際の分析業務において、データをHTMLに変換して可視化するという仕事は、決して珍しくありません。
その背景には、CSSを用いたレイアウトなど、様々な応用が利くため、可視化手段として扱いやすいという理由があります。
是非挑戦してみてください!

【問題】
標準入力から、CSVフォーマットでデータが送られます。
このデータをテーブルタグを用いてHTMLに変換し、その結果を標準出力に返してください。
なお、文字列フィールドのエスケープ処理も忘れずに。
※ご利用のブラウザによって、エスケープ処理された文字が変換されて表示される箇所があります

私のプログラム

Rubyで解答しています。

#!/usr/bin/ruby
require 'csv'


def parseRow(line)
	replaced = line.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;")
	return CSV.parse_line(replaced)
end

def getTableRow(row, tag)
	ret = "<tr>"
	for r in row
		ret += "<" + tag + ">" + r.gsub('"', '') + "</" + tag + ">"
	end
	return ret + "</tr>"
end

def printTable(rows)
	html = "<table>"

	rows.each_with_index{|row, i|
		if i == 0 then
			html += getTableRow(row, "th")
		else
			html += getTableRow(row, "td")
		end
	}

	html += "</table>"
	puts html
end

# main
rows = []
while line = gets
	line.strip!
	if line.empty? then next end


	rows << parseRow(line)
end

printTable(rows)

解説

全部自力でやろうとするとCSVのパースが面倒臭いです。
今回はRubyだったので便利なライブラリを使えたため簡単でした。

考え方

基本的には入力値を1行ずつパースして配列にし、1行目は<ht>で囲い、それ以外は<td>で囲うというだけです。面倒臭いのはCSVのパースです。
以前、別の問題で(確かJavaで)CSVのパース処理を書いたのですが、今回はRubyだったのでCSVのライブラリがあるんじゃないか、と思って調べたらあったのでそれを使いました。なので面倒な部分が全部なくなっています。

main

1行ずつparseRow()に渡して配列を取得します。
全ての入力値を配列に変えたら、printTable()でHTML文字列を印字します。

parseRow(line)

入力値1行のエスケープが必要な部分をエスケープし、CSV#parse_line()で配列に変換して返すだけです。
ちょっとした工夫が&を最初に変換してしまうことです。

printTable(rows)

<table>〜</table>までを印字します。
テーブルの各行を作るのはgetTableRow()で、この関数はgetTableRow()に1行分のデータを渡して返却値を連結することと、前後に<table>と</table>をつけて印字することだけです。
1行目だけは<th>なのでgetTableRow()に"th"を指定し、それ以外は"td"を指定することでタグを変更します。

getTableRow(row, tag)

rowは1行分のデータの配列、tagはデータ1つを囲むタグの文字列("th"か"td")です。
データ1個ずつを<th>か<td>で囲んで連結し、前後に<tr>の開始タグと終了タグをつけるだけです。CSV#parse_line()で作った文字列が"と"で囲まれているのでそれは消しています。

雑感

CSVのパースを自分で書くと面倒臭いのは"hoge,fuga"みたいな項目を考慮しなければいけないからです。もっと面倒なのはダブルクォートのエスケープで"""hoge"""を考慮しなければいけません(バックスラッシュでエスケープの処理系もあるらしい)。
結局、これらを考慮すると1文字ずつ処理しなければいけなくて非常に面倒です。