by WebSurfer
7. November 2011 22:08
クエリ文字列に日本語を使用している場合、それをブラウザのアドレスバーから直接入力して要求をかけるのは問題がありそうです。
以下のような、日本語を含む URL をブラウザのアドレスバーに直接打ち込んで要求をかけると、ブラウザはどのような文字列をサーバーに送るでしょうか?
http://www.abc.com/日本語.aspx?data=日本語
下の画像は IE9 の場合で、Fiddler を使って HTTP-GET 要求をキャプチャし、HexView で表示したものです。サーバーに送信したバイト列を 16 進数で表したものと、その 16 進数に相当する ASCII コードの文字が表示されています。
ファイル名の "日本語" の方は UTF-8 が URL エンコード(ASCII 文字で %E6%97%A5 ... %AA%9E)されますが、クエリ文字列の方の "日本語" は Shift_JIS そのまま(反転表示したバイト列 93 FA 96 7B 8C EA)になっているのが見えるでしょうか。
IE9, Firefox 7, Chrome 15 で試してみましたが、ファイル名の方はいずれも UTF-8 の URL エンコードで同じ結果になりましたが、クエリ文字列の方はそれぞれ以下のように結果が異なりました。
| ブラウザ |
クエリ文字列 |
| IE9 |
Shift_JIS そのまま |
| Firefox 7 |
Shift_JIS を URL エンコード |
| Chrome 15 |
UTF-8 を URL エンコード |
ということは、サーバーが受信した文字列を UTF-8 として解釈する場合(ASP.NET のデフォルト)、上記のブラウザのうち Chrome しかクエリ文字列を正しく送信できないということになるようです。
では、アドレスバー直打ちではなく、以下のようなハイパーリンクをクリックするとブラウザはどのような要求をサーバーに送るでしょうか?
<a href="default.aspx?data=日本語">リンク</a>
IE は文字コードのバイト列を生のまま送ります。例えば、"日本語" というクエリ文字列の場合、ハイパーリンクのあるページのエンコーディングが Shift_JIS なら 93 fa 96 7b 8c ea、UTF-8 なら e6 97 a5 e6 9c ac e8 aa 9e となります。
Firfox と Chrome は URL エンコードしてから送ります。ただし、Shift_JIS の場合、2 バイト目が ASCII の非予約文字のときは、その文字をそのまま使用します。"日本語" の場合、"本" の 2 バイト目 7b が ASCII 文字で '{' に該当するので、結果は "%93%FA%96{%8C%EA" になります。
特殊な例かもしれませんが、BlogEngine.NET の場合 '{' という文字が問題で、内部で URL の書き換えを行う際に例外がスローされてサーバーエラーになってしまうという問題がありました。
このような予期しない問題を避けるために、サーバー側できちんと UrlEncode メソッドを使って URL エンコーディングした文字列("本" は "%96%7b" になります)を使うのがよさそうです。(ただし、アドレスバー直打ちされた場合はどうしようもありませんが)
ちなみに、URL 本体('?' の左側の文字列)は、アドレスバーへの直打ち、ハイパーリンクへの設定、ページに使用されているエンコーディングの違い、ブラウザの違い(IE9, Firefox 7, Chrome 15 しか試してませんが)などに関係なく UTF-8 の URL エンコーディングになります。
なので、BlogEngine.NET 2.0 の Tag cloud の問題 と 日本語の著者名の問題 で書きましたように、URL 本体の文字に URL エンコードしない日本語の文字列を使っていますが、今までのところ期待通りに動いています。運よく問題に遭遇していないだけという可能性は排除しきれませんが。(笑)
by WebSurfer
28. October 2010 12:18
CSV ファイルをパースして DataTable を作るような場合、ファイルを一行ずつ読んで文字列を作り、String.Split メソッドでその文字列を区切るといった方法を考えると思います。
ところが、改行コードやデリミタ(コンマのような区切り文字)がフィールド値の中にあったり、改行コードが異なったり(例: Windows は CR + LF、Unix は LF)するのに対応する場合、上記ような単純な方法は使えません。
改行コードやデリミタがフィールド値の中にある場合、フィールド値をダブルクォート (") で囲むと言った約束を設けて対応しますが、そのような CSV ファイルの処置が可能なパーサーを自力で作るのは簡単ではありません。
という訳で、いつものように他力本願で(笑)、既存のパーサーを使うのがよさそうです。既存のパーサーにはいろいろなものがあります。詳しくは、CSV 形式のファイルを DataTable や配列等として取得する が参考になると思います。
なので、わざわざここに書く必要もないかもしれませんが、自分でも 2, 3 試してみましたので、使ってみた感想と、CSV ファイルを読んで DataTable を作るサンプルを書いておきます。
使ってみたのは Jet Provider, A Fast CSV Reader, TextFieldParser の 3 つです。感想は以下のとおりです。
-
Jet Provider を使うのが最も簡単な方法です。ただし、文字コードが Shift_JIS 以外はダメです。UTF-8 なら日本語が文字化けする程度ですが、Unicode は全く処置できません。
2014/5/26 追記:下にコメントいただいたとおり、schema.ini というファイルを作り、それに文字コードを指定して CSV ファイルと同じディレクトリに配置することにより UTF-8 他の文字コードに対応できます。
また、JET プロバイダはデフォルトで最初の 8 行のデータをスキャンして各列のデータ型を推測しますが、その際、予期しない型に推測されてしまうことも schema.ini で型を指定することとにより防ぐことができます。
-
A Fast CSV Reader は The Code Project のサイトで MIT License にて提供されているものです。解析のスピードの速さがウリのようです。使い方も簡単ですし、Shift_JIS, UTF-8, Unicode いずれも対応できるので、自分的にはこれが気に入っています。ただし、dll をダウンロードしてこなければならない点と、日本語の説明がない点が問題かも。
-
TextFieldParser は Microsoft が提供している Visual Basic .NET 用のクラスライブラリです。これも使い方は簡単で、Shift_JIS, UTF-8, Unicode いずれも対応できます。C# でも Microsoft.VisualBasic.dll を参照に追加してやれば使えます。何といっても Microsoft のライブラリなので、これを使うのが一番無難そうな気がします。
CSV ファイルをパースして DataTable を作り、それを GridView にバインドして表示するサンプルをアップしておきます。検証用なので、ユーザーインターフェイスは��なり省略して書いてます。すみません。(汗)
A Fast CSV Reader を使用するには LumenWorks.Framework.IO.dll を The Code Project のサイト A Fast CSV Reader からダウンロードしてきて、それを Bin フォルダに置く必要があります。test.csv は検証用のテキストファイルです。
<%@ Page Language="C#" %>
<%@ Import Namespace="LumenWorks.Framework.IO.Csv" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Data" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
// CSV パーサーの選択
int select = 0;
// CSVファイルのあるフォルダ、ファイル名
string csvDir = @"C:\WebSites\MsdnTestNew\App_Data\";
string csvFileName = "test.csv";
DataTable dt;
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
if (select == 0)
{
// A Fast CSV Reader を使う場合
// ストリームの最初の 3 バイトを参照して、エン
// コーディングの検出を試みます。
// ファイルが該当する BOM で開始される場合は、UTF-8、
// リトルエンディアン Unicode、ビッグエンディアン
// Unicode の各テキストが自動的に認識されます。
// それ以外の場合は、ユーザー指定のエンコーディング
// (以下の例では Shift_JIS)が使用されます。
using (CsvReader csv =
new CsvReader(
new StreamReader(
csvDir + csvFileName,
Encoding.GetEncoding("Shift_JIS")
),
true
)
)
{
int fieldCount = csv.FieldCount;
string[] headers = csv.GetFieldHeaders();
dt = new DataTable();
DataRow dr;
DataColumn dc;
for (int i = 0; i < fieldCount; i++)
{
dc = new DataColumn(headers[i], typeof(String));
dt.Columns.Add(dc);
}
while (csv.ReadNextRecord())
{
dr = dt.NewRow();
for (int i = 0; i < fieldCount; i++)
{
dr[headers[i]] = csv[i];
}
dt.Rows.Add(dr);
}
}
}
else if (select == 1)
{
// Jet Provider を使う場合
// 接続文字列。HDR=Yes で一行目をヘッダーとして扱う
string conString =
"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +
csvDir +
";Extended Properties=\"text;HDR=Yes;FMT=Delimited\"";
System.Data.OleDb.OleDbConnection con =
new System.Data.OleDb.OleDbConnection(conString);
string commText = "SELECT * FROM [" + csvFileName + "]";
System.Data.OleDb.OleDbDataAdapter da =
new System.Data.OleDb.OleDbDataAdapter(commText, con);
dt = new DataTable();
da.Fill(dt);
}
else
{
// TextFieldParser を使う場合
using (Microsoft.VisualBasic.FileIO.TextFieldParser tfp =
new Microsoft.VisualBasic.FileIO.TextFieldParser(
csvDir + csvFileName,
Encoding.GetEncoding("Shift_JIS")
)
)
{
//フィールドがデリミタで区切られている
tfp.TextFieldType =
Microsoft.VisualBasic.FileIO.FieldType.Delimited;
// デリミタを , とする
tfp.Delimiters = new string[] { "," };
// フィールドを " で囲み、改行文字、デリミタを
// 含めることができるか
tfp.HasFieldsEnclosedInQuotes = true;
// フィールドの前後からスペースを削除
tfp.TrimWhiteSpace = true;
string[] headers = tfp.ReadFields();
int fieldCount = headers.Length;
dt = new DataTable();
DataRow dr;
DataColumn dc;
for (int i = 0; i < fieldCount; i++)
{
dc = new DataColumn(headers[i], typeof(String));
dt.Columns.Add(dc);
}
while (!tfp.EndOfData)
{
string[] fields = tfp.ReadFields();
dr = dt.NewRow();
for (int i = 0; i < fieldCount; i++)
{
dr[headers[i]] = fields[i];
}
dt.Rows.Add(dr);
}
}
}
GridView1.DataSource = dt;
GridView1.DataBind();
}
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:GridView ID="GridView1" runat="server">
</asp:GridView>
</div>
</form>
</body>
</html>