正規表現 (Regex) を利用してCSVファイルやTSVファイルを読み込む - C#

正規表現 (Regex) を利用してCSVファイルやTSVファイルを読み込むコードを紹介します。

概要

Regex.Split()メソッドを利用すると、CSVをカンマの区切り文字でパージングすることができます。

正規表現の作成

基本は区切り文字となる「,」(カンマ)を探す正規表現を利用します。
下記の正規表現式では「,」を検索できますが、「"」(ダブルクォーテーション)内のカンマも検索されてしまします。
カンマのみの正規表現式
,

「"」で囲まれた内部のカンマを除外するため以下の条件を追加します。
  1. カンマ以降の文字列に「"」が無い
  2. カンマ以降の文字列に「"」が偶数個ある

具体例として文字列の途中で「,」が見つかった状態で「,」以降の文字列が分かっている場合、下記の例では先頭の「,」は区切り文字のカンマと判定できます。
カンマを検出した場所以降の文字列
,penguin,"1,000",jump
元のデータ
Duck,penguin,"1,000",jump

一方、下記の場合は「,」以降に「"」が奇数個あることから、先頭の「,」は区切り文字のカンマではなく、「"」に囲まれたカンマと判定できます。
カンマを検出した場所以降の文字列
,000",penguin,"22,000",jump
元のデータ
Camel,"25,000",penguin,"22,000",jump

以下のカンマ以降の「"」が偶数個あるものにマッチする条件式を用意すれば、「"」に囲まれた「,」を除外することができます。
条件式のロジック
,+{(「"」以外の任意の文字が任意の個数)+「"」+(「"」以外の任意の文字が任意の個数)+「"」}(中括弧部分が0回以上繰り返す)+(「"」以外の任意の文字が任意の個数)+(行の終了)

上記を反映した正規表現式が下記になります。「,」以降のマッチ条件は条件には含めますが区切り文字としては扱わないためマッチ範囲から除外するため(?=)でグループ化します。
「,」以降に「"」が無い、または偶数個ある場合にマッチする正規表現式
,(?=([^"]*"[^"]*")*[^"]*$)

上記の正規表現式の下記部分でグループ化「()」を利用していますが、()を利用すると括弧内のパターンもキャプチャされ、パターンが行として抽出されてしまいます。
([^"]*"[^"]*")*
キャプチャ対象に含めないようにするため、(?:)でグループ化します。(?:pattern)の詳細に関してはこちらの記事を参照してください。
(?:[^"]*"[^"]*")*

上記を反映した正規表現が下記になります。
「,」以降に「"」が無い、または偶数個ある場合にマッチする正規表現式
,(?=(?:[^"]*"[^"]*")*[^"]*$)
上記の式を用いて、CSVの行をRegex.split()メソッドに与えて処理をすると「,」で区切られた行を分割して文字列の配列に格納できます。

プログラム例

UI

下図のUIを作成します。

コード

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;

namespace CSVParserRegEx
{
  public partial class FormMain : Form
  {
    public FormMain()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      if (openFileDialog1.ShowDialog() == DialogResult.OK) {
        label1.Text = openFileDialog1.FileName;

        StreamReader sr = new StreamReader(openFileDialog1.FileName, Encoding.GetEncoding("Shift_JIS"));
        try {
          while (sr.EndOfStream == false) {
            string line = sr.ReadLine();

            Regex reg = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");

            string[] elem = reg.Split(line);

            for (int i = 0; i < elem.Length; i++) {
              textBox1.Text += elem[i] + "\r\n";
            }
            textBox1.Text += "------\r\n";
          }
        }
        finally {
          sr.Close();
        }
      }
    }
  }
}

解説

下記のコードでは、OpenFileDialogを表示しダイアログでファイルを選択させます。開いたファイルのパスをlabel1に表示します。StreamReader を用いOpenFileDialogで選択したファイルを開きます。while ループ内で開いたファイルを1ずつ読み取ります。
  if (openFileDialog1.ShowDialog() == DialogResult.OK) {
    label1.Text = openFileDialog1.FileName;

    StreamReader sr = new StreamReader(openFileDialog1.FileName, Encoding.GetEncoding("Shift_JIS"));
    try {
      while (sr.EndOfStream == false) {
        string line = sr.ReadLine();
        ......
      }
    }
    finally {
      sr.Close();
    }
  }

下記のコードでは、StreamReader でファイルを開いた後、whileループ内で1行ずつファイルを読み取ります。読み取った行の文字列を先ほどの正規表現を用いたRegex.Split() メソッドでカンマ区切りで分割します。分割された結果をtextBox1に表示します。
    while (sr.EndOfStream == false) {
      string line = sr.ReadLine();

      Regex reg = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");

      string[] elem = reg.Split(line);

      for (int i = 0; i < elem.Length; i++) {
        textBox1.Text += elem[i] + "\r\n";
      }
      textBox1.Text += "------\r\n";
    }

入力ファイル

今回は下記の3つの入力ファイルを準備します。「"」に囲まれた項目の内部に「,」が記載されている形式のCSVも準備しています。
sample-01.csv
1,ぺんぎんクッキー,350,15
2,らくだキャンディー,240,20
3,あひるケーキ,420,8
sample-02.csv
1,"さくさく,ぺんぎんクッキー",350,15
2,らくだキャンディー,240,20
3,"ふんわり,あひるケーキ",420,8
sample-03.csv
1, ぺんぎんクッキー", "1,680",350, 15
2, らくだキャンディー, "950", 240, 20
3, "ふんわり, あひるケーキ", "2,450", 420, 8

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。


[button1]をクリックします。下図のファイルを開くダイアログが表示されます。読み込ませるCSVのファイルを選択して開きます。


"sample-01.csv"ファイルを読み込んだ結果です。CSVファイルが読み込まれ、それぞれの項目ごとに表示されていることが確認できます。


"sample-02.csv"ファイルを読み込んだ結果です。" "で囲まれた中に「,」があるCSVも正しく読み込まれていることが確認できます。


"sample-03.csv"ファイルを読み込んだ結果です。

ダブルクォーテーション (") の除去

先のコードでCSVを項目ごとに取得できるプログラムができましたが、「"」で囲まれている項目は、「"」を含めた文字列で取り出されてます。取得した項目の文字列が「"」で囲まれていた場合には「"」で囲まれた内部のみを取得するロジックを追加したものが、下記のコードになります。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;

namespace CSVParserRegEx
{
  public partial class FormMain : Form
  {
    public FormMain()
    {
      InitializeComponent();
    }

    private void button2_Click(object sender, EventArgs e)
    {
      if (openFileDialog1.ShowDialog() == DialogResult.OK) {
        label1.Text = openFileDialog1.FileName;

        StreamReader sr = new StreamReader(openFileDialog1.FileName, Encoding.GetEncoding("Shift_JIS"));
        try {
          while (sr.EndOfStream == false) {
            string line = sr.ReadLine();
            Regex reg = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");
            string[] elem = reg.Split(line);

            Regex regdq = new Regex("\\s*\"(?<text>[^\"]*)\"\\s*$");
            for (int i = 0; i < elem.Length; i++) {
              Match match = regdq.Match(elem[i]);
              if (match.Success == true) {
                elem[i] = match.Groups["text"].Value;
              }

               textBox1.Text += elem[i] + "\r\n";
            }
            textBox1.Text += "------\r\n";

          }
        }
        finally {
          sr.Close();
        }
      }
    }
  }
}

解説

カンマで分割した後、分割した要素を確認するforループ内にて、下記のコードにより「"」を除去します。正規表現を利用して、「"」で囲まれている場合は「"」で囲まれている内部の文字列を抜き出します。
Regex regdq = new Regex("\\s*\"(?<text>[^\"]*)\"\\s*$");
for (int i = 0; i < elem.Length; i++) {
  Match match = regdq.Match(elem[i]);
  if (match.Success == true) {
    elem[i] = match.Groups["text"].Value;
  }
}

実行結果

"sample-03.csv"ファイルを読み込んだ結果です。先のプログラムの実行結果から「"」で囲まれていた項目の「"」が除去されていることが確認できます。

タブ区切りの場合

タブ区切りの場合は正規表現の「,」の部分をタブに変更します。
「タブ」以降に「"」が無い、または偶数個ある場合にマッチする正規表現式
\t(?=(?:[^"]*"[^"]*")*[^"]*$)

コード

下記のコードを利用します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;

namespace CSVParserRegEx
{
  public partial class FormMain : Form
  {
    public FormMain()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      if (openFileDialog1.ShowDialog() == DialogResult.OK) {
        label1.Text = openFileDialog1.FileName;

        StreamReader sr = new StreamReader(openFileDialog1.FileName, Encoding.GetEncoding("Shift_JIS"));
        try {
          while (sr.EndOfStream == false) {
            string line = sr.ReadLine();
            Regex reg = new Regex("\t(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");
            string[] elem = reg.Split(line);

            for (int i = 0; i < elem.Length; i++) {
              textBox1.Text += elem[i] + "\r\n";
            }
            textBox1.Text += "------\r\n";

          }
        }
        finally {
          sr.Close();
        }
      }
    }
  }
}

著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
最終更新日: 2024-01-07
作成日: 2017-11-27
iPentec all rights reserverd.