参考
競馬ソフト開発体験教室(https://jra-van.jp/dlb/sdv/trial.html)
Lesson.5 コード変換を行う
注:上記の参考サイトを元にVBからC#へ移植する進めていきます。
前回記事
JV-LinkをC#で使ってみる(WinForms版) ~4.JV-Dataのダウンロード/読み込み進捗状況を表示する~
今回のソースコード
source – JV-LinkをC#で使ってみる(WinForms版) ~5.コード変換を行う~
開発環境
- Windows7 64ビット版
- Visual Studio Community 2017
- JV-Link Ver.4.5.1
コード変換クラスの移植
コード変換クラス移植の準備
参考サイトLesson.5で利用されている「コード変換クラス」にはC#のサンプルがありません。仕方がないので移植いたします。(これに限らず JRA-VAN にはC#のサンプルが不足しています。)
まず、ソリューションエクスプローラーから「clsCodeConv.cs」を新規作成します。
以下のように、クラスのテンプレートが Visual Studio によって作成されます。
clsCodeConvクラスは今回のアプリケーションとは無関係ですので、namespaceは削除しておきます。 (本当は別のnamespaceを付けたほうが良いのかもしれませんが、テストプログラムですので。)
また、メッセージボックスを使う予定ですのでusing System.Windows.Forms;
を追加します。と、言うわけで以下のようなテンプレートで作成を開始いたします。
コード変換クラスの仕組み
clsCodeConvクラスの構造は、以下のような仕組みになっています。
※メインプログラム側
- FileNameプロパティにCSVファイル名を設定すると、clsCodeConvオブジェクト内の配列にCSVの内容を読み込みます。
- GetCodeName()関数によってclsCodeConvオブジェクト内の配列にアクセスします。
※clsCodeConv クラス側
- FileNameプロパティが呼び出されると、clsCodeConvオブジェクト内ではファイル名をメンバ変数に設定し、SetData()関数を呼び出します。
- SetData()関数内では、CSVファイルをテキストとして全て読み込み、一行ごとに分割します。その後、1行ごとにSetLine()関数へ渡します。
- SetLine()関数内では、カンマ区切りで「第1要素」「第2要素」「第3要素以降」の3つの要素に分けてmudtCodeLine構造体の配列に保存します。
- メインプログラム側からGetCodeName()関数が呼び出されると、clsCodeConvオブジェクト内ではGetCodeName構造体の配列からデータを検索し、メインプログラムへ返します。
コード変換クラスの移植について解説
< コード解説の前に >
なるべく元のVBソースを生かして移植を行いますので、「なんでこんな構造なんだろう」と疑問な部分もそのままにしてあります。そのうち、もう少し綺麗に見えるように作り直します。
mudtCodeLine構造体
CSVファイルを読み込みための構造体です。データについては『JRA-VAN Data Lab. JVData 仕様書』のシート「コード表」を参照してください。
コード名 | 説明 |
---|---|
strCodeNo: | 2001.競馬場コード、2002.曜日コード、など |
strCode: | 「コード表」内の値(2列目) |
strNames: | 「コード表」内の内容(3~8列目) |
注:仕様書に正式な名前を見つけられなかった為、「第1要素」「第2要素」「第3要素以降」と仮に呼びます。
// コード名称取得モジュール
// コードから名称を取得する
private struct mudtCodeLine
{
public string strCodeNo;
public string strCode;
public string strNames;
}
FileNameプロパティ
メンバ変数としてファイル名を保存した後に、SetData()関数を呼び出します。
public string FileName
{
set
{
try
{
mFileName = value;
SetData();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
GetCodeName()関数
SetData()が一回も成功していない場合は、""が返ります。関数呼び出しに成功するとintNoによって「第3要素」の1つ目、2つ目、3つ目、・・・を返します。
public string GetCodeName(string strCodeNo, string strCode, short intNo = 1)
{
int i; // ループカウンタ
int j; // ループカウンタ
int ct; // 名称取得用カウンタ
string strName = ""; // 名称
// データが読み込めていない場合
if (!blnFlag) return "";
try
{
// 名称文字列から指定番目の名称を返す
for (i = 0; i < mArrData.Length; i++)
{
if (mArrData[i].strCodeNo == strCodeNo && mArrData[i].strCode == strCode)
{
ct = 1;
for (j = 0; j < mArrData[i].strNames.Length; j++)
{
if (mArrData[i].strNames.Substring(j, 1) == ",")
{
ct = ct + 1;
if (ct > intNo) break;
}
else if (ct == intNo)
{
strName = strName + mArrData[i].strNames.Substring(j, 1);
}
}
break;
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return strName;
}
~clsCodeConv()関数(デストラクタ)
一応、配列の初期化を行っています。
~clsCodeConv()
{
try
{
Array.Clear(mArrData, 0, mArrData.Length);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
SetData()関数
本当はStreamReaderクラスでReadLine()関数を使えば、1行切り出しの部分は簡単に書けると思いますが、このサンプルでは改行コードを探して切り出しを行っています。
private void SetData()
{
string strRt; // 改行文字
int lngLnRt; // 改行文字の文字数
string strData; // CSVファイルを受ける文字列
int lnglenData; // strDataの文字数
int lngRt; // strData中のstrRtの位置
int lngCt; // Rtのカウンタ,mArrDataの行数
int lngBeforeRt; // ひとつ前のlngRt
string strLine; // CSVファイル一行分
byte[] bytData; // ファイルのデータ格納先
blnFlag = true;
try
{
// 改行文字の決定
strRt = "\r\n";
lngLnRt = strRt.Length;
// ファイルの中身を文字列として取得
System.IO.FileStream fs = new System.IO.FileStream(mFileName, System.IO.FileMode.Open, System.IO.FileAccess.Read);
bytData = new byte[fs.Length];
fs.Read(bytData, 0, bytData.Length);
fs.Close();
// エンコード
strData = System.Text.Encoding.GetEncoding(932).GetString(bytData);
// 配列クリア
Array.Clear(bytData, 0, bytData.Length);
lnglenData = strData.Length;
// 中身が空,もしくはファイルが存在しない場合
if (lnglenData == 0)
{
blnFlag = false;
return;
}
// 一行ずつ処理
lngBeforeRt = -lngLnRt; // 一行目の前に改行があると仮定
lngRt = -1;
lngCt = 0;
while (lngRt + lngLnRt < lnglenData)
{
lngRt = strData.Substring(lngRt + 1).IndexOf(strRt) + lngRt + 1;
if (lngRt == 0) break;
Array.Resize(ref mArrData, lngCt + 1);
strLine = strData.Substring(lngBeforeRt + lngLnRt, lngRt - lngBeforeRt - lngLnRt);
SetLine(ref strLine, ref lngCt);
lngCt = lngCt + 1;
lngBeforeRt = lngRt;
}
}
catch (Exception ex)
{
blnFlag = false;
MessageBox.Show(ex.Message);
}
}
SetLine()関数
「第1要素 / 第2要素 / 第3要素以降」の3回に分けてデータを読み込んでいます。正直言ってループを使わなくてもいいのではないかと思いますが、そのまま移植します。Splitを使って全て分割した後に、第3要素以降を合成するほうが自然かとも思います。 (合成せずにそのまま配列にしておく仕様変更を考えてもいいかもしれません。)
private void SetLine(ref string strLine, ref int lngCt)
{
byte bytFieldCt; //フィールド(列)数
string strDelimiter; //区切り子
int lngDelimiter; //区切り子の位置
int lngBeforeDel; //前の区切り子の位置
string strWord; //フィールド1つ分の文字列
mudtCodeLine udtWords; //一行分のstrWordを格納
udtWords = new mudtCodeLine();
//区切り子の決定
strDelimiter = ",";
try
{
//ユーザ定義型mudtCodeLineに変換
bytFieldCt = 0;
lngDelimiter = -1;
lngBeforeDel = -1;
while (bytFieldCt <= 2)
{
if (bytFieldCt < 2)
{
lngDelimiter = strLine.Substring(lngDelimiter + 1).IndexOf(strDelimiter) + lngDelimiter + 1;
}
else
{
lngDelimiter = strLine.Length;
}
// フィールドが2以下の場合
if (lngDelimiter == 0)
{
MessageBox.Show("CSVファイルが不正です");
blnFlag = false;
return;
}
strWord = strLine.Substring(lngBeforeDel + 1, lngDelimiter - lngBeforeDel - 1);
switch (bytFieldCt)
{
case 0:
udtWords.strCodeNo = strWord;
break;
case 1:
udtWords.strCode = strWord;
break;
case 2:
udtWords.strNames = strWord;
break;
default:
return;
}
bytFieldCt = (byte)(bytFieldCt + 1);
lngBeforeDel = lngDelimiter;
}
// ユーザ定義型mudtCodeLineを配列に代入
mArrData[lngCt] = udtWords;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
メインコードの修正
CodeTable.csvの読み込み
メインフォームクラス(frmMain)のメンバ変数にclsCodeConvクラスの変数を追加します。
private clsCodeConv objCodeConv; // コード変換インスタンス
また、メインフォームクラスのfrmMain_Load()関数内でclsCodeConvクラスのインスタンスを作成し、ファイルの読み込みを行います。
// コード変換インスタンス生成
objCodeConv = new clsCodeConv();
// パスを指定し、コードファイルを読込む
objCodeConv.FileName = Application.StartupPath + @"\CodeTable.csv";
コード変換処理の使い方
メインフォームクラスのbtnGetJVData_Click()関数内のデータ表示処理部分を以下のように修正します。『JRA-VAN Data Lab. JVData 仕様書』を見るとわかりますが、競馬場コード("2001")に対するサブコード(RaceInfo.id.JyoCD)にそれぞれ「競馬場名」が「フル/1文字/2文字/3文字/アルファベット」で表示されます。サンプルにはありませんが、曜日コード("2002")とサブコードを渡せば、曜日が返ってきます。
// データ表示
rtbData.AppendText(
"年:" + RaceInfo.id.Year +
" 月日:" + RaceInfo.id.MonthDay +
//" 場:" + RaceInfo.id.JyoCD +
" 場:" + objCodeConv.GetCodeName("2001", RaceInfo.id.JyoCD, 1) +
" 回次:" + RaceInfo.id.Kaiji +
" 日次:" + RaceInfo.id.Nichiji +
" R:" + RaceInfo.id.RaceNum +
" レース名:" + RaceInfo.RaceInfo.Ryakusyo10 +
"\n");
コードの修正が完了したらプログラムを実行し、データ取得ボタンを選択して動作を確認します。
以上で、 JV-Data のコード変換が完了となります。
今までコード表示だった部分に、競馬場名が表示されるようになりました。