見出し画像

テンプレートを使ったexcelファイル出力方法(C#)-2.実装篇

前回「テンプレートを使ったexcelファイル出力方法(C#)-1.設計」では、本機能の概要設計、詳細設計、プログラム設計を行いました。

これからはC#でプログラムを実装する方法について解説します。

この記事はC#のプログラミング基礎がある読者を想定していますので、プログラミング基礎知識の説明は割愛します。

C#プロジェクトの作成

Visual Studioを起動し、C#プロジェクトを新規作成します。
注:今回はDLLを作成したいので、テンプレートから「Class Library(.NET Framework)」を選択します。

画像1

本記事では.Net Framwork 4.5で開発していますが、別に特別なことはやってないので、バージョンが違っても本記事でのソースは動くはずです。

ここで「クラスライブラリ(dll)型」のプロジェクトを作成する理由は、DLL(ダイナミックリンクライブラリ)を作成すると、後であらゆるアプリに組み込ませることができるからです。

本記事で作成したDLLを組み込むことにより、アプリはすぐ「テンプレートを使ったexcelファイル出力機能」を備えることになります。

画像3

↑図のようにDLLを組み込むことで、「アプリA」、「アプリB」はどれも本記事で作成したexcel出力機能を自分で開発したかのように利用することができるようになります。

プロジェクトを作成したら、ダウンロードしたNPOIライブラリを参照に追加しておきましょう。

画像3

これで、C#言語を使ってexcelファイルの読み込み、書き込み処理ができるようになりました。

クラスの実装

次は設計通りクラス図に書いてあるクラスを1個ずつ実装していきます。

実装完了したら、↓図のようになります。(筆者はjdr_core.templatedExcelという名前のネーミングスペース内に実装しています)

画像4

列挙型CItemType、列挙型CCellDataType、クラスCMergeBlock 

■  CItemType.cs

namespace jdr_core.templatedExcel
{
   /// <summary>
   /// excel項目の種類
   /// </summary>
   public enum CItemType
   {
       /// <summary>
       /// 普通項目
       /// </summary>
       Normal,
       /// <summary>
       /// リスト項目
       /// </summary>
       List,
       /// <summary>
       /// 普通項目とリスト項目全て
       /// </summary>
       Any
   }//end enum

   /// <summary>
   /// excelセール値の型
   /// </summary>
   public enum CCellDataType
   {
       /// <summary>
       /// 文字列
       /// </summary>
       String,
       /// <summary>
       /// 数値
       /// </summary>
       Double,
       /// <summary>
       /// 時間
       /// </summary>
       DateTime
   }

   /// <summary>
   /// セルのマージ管理
   /// </summary>
   public class CMergeBlock
   {
       /// <summary>
       /// マージする範囲開始列番(0 based)
       /// </summary>
       public int StColIdx { get; set; }
       /// <summary>
       /// マージ範囲終了列番(0 based)
       /// </summary>
       public int EdColIdx { get; set; }
       /// <summary>
       /// マージ範囲開始行番(0 based)
       /// </summary>
       public int StRowIdx { get; set; }
       /// <summary>
       /// マージ範囲終了行番(0 based)
       /// </summary>
       public int EdRowIdx { get; set; }
   }
}//end namespace
プログラミングでよく使う方法は、1クラス(or 1列挙型)毎に1つのソースファイルを作成するのですが、

これはプログラミング言語の制限ではなく、単なるプログラマーが読みやすいためだけであって、

場合によっては(筆者は)、全ての列挙型をまとめて1つのソースファイル内で定義定義しても問題ありません。

クラスCTemplatedCell

■  CTemplatedCell.cs

using System;
namespace jdr_core.templatedExcel
{
   /// <summary>
   /// テンプレートから解析したセール情報格納
   /// </summary>
   public class CTemplatedCell
   {
       string groupName = "";
       string valueName = "";
       int rowIndex = 0;  //0 based
       int colIndex = 0;   //0 based
       /// <summary>
       /// コンストラクター
       /// </summary>
       /// <param name="groupName"></param>
       /// <param name="valueName"></param>
       /// <param name="rowIndex"></param>
       /// <param name="colIndex"></param>
       public CTemplatedCell(string groupName,
                             string valueName,
                             int rowIndex,
                             int colIndex)
       {
           this.groupName = groupName;
           this.valueName = valueName;
           this.rowIndex = rowIndex;
           this.colIndex = colIndex;
       }
       public String GroupName
       {
           get
           {
               return this.groupName;
           }
       }
       public String ValueName
       {
           get
           {
               return this.valueName;
           }
       }
       public int RowIndex
       {
           get
           {
               return this.rowIndex;
           }
       }
       public int ColIndex
       {
           get
           {
               return this.colIndex;
           }
       }
   }//end class
}//end namespace
メンバー変数とプロパーティの定義だけで、簡単なクラスなので説明は割愛します。

クラスCTemplatedItem

■  CTemplatedItem.cs

□ソース抜粋1  ==========

       /// <summary>
       /// normal項目[行番, [ CTemplatedCell ] ]
       /// </summary>
       private SortedDictionary<int, List<CTemplatedCell>> normalItem = 
           new SortedDictionary<int, List<CTemplatedCell>>();

       /// <summary>
       /// リスト項目[行番, [ CTemplatedCell ] ]
       /// </summary>
       private SortedDictionary<int, List<CTemplatedCell>> listItem = 
           new SortedDictionary<int, List<CTemplatedCell>>();

テンプレートを解析して取得した、「normal項目」と「list項目」情報を格納する「辞書型(SortedDictionary)」変数を定義しています(↓図参照)。

画像5

□全体ソース  ==========

using System.Collections.Generic;

namespace jdr_core.templatedExcel
{
   /// <summary>
   /// テンプレートファイルから解析した項目情報格納
   /// </summary>
   public class CTemplatedItem
   {

       /// <summary>
       /// normal項目[行番, [ CTemplatedCell ] ]
       /// </summary>
       private SortedDictionary<int, List<CTemplatedCell>> normalItem = 
           new SortedDictionary<int, List<CTemplatedCell>>();

       /// <summary>
       /// リスト項目[行番, [ CTemplatedCell ] ]
       /// </summary>
       private SortedDictionary<int, List<CTemplatedCell>> listItem = 
           new SortedDictionary<int, List<CTemplatedCell>>();

       /// <summary>
       /// 普通項目の行番を取得
       /// </summary>
       public List<int> NormalItemRow
       {
           get
           {
               return getRows(CItemType.Normal);
           }
       }

       /// <summary>
       /// リスト項目の行番を取得
       /// </summary>
       public List<int> ListItemRow
       {
           get
           {
               return getRows(CItemType.List);
           }
       }

       /// <summary>
       /// 普通項目を取得する
       /// <param name="rowIdx">行番(0 based)</param>
       /// </summary>
       public List<CTemplatedCell> GetNormalItem(int rowIdx)
       {
           if (this.normalItem.ContainsKey(rowIdx))
           {
               return this.normalItem[rowIdx];
           }
           else
           {
               return null;
           }
       }

       /// <summary>
       /// リスト項目を取得する
       /// </summary>
       /// <param name="rowIdx">行番(0 based)</param>
       /// <returns></returns>
       public List<CTemplatedCell> GetListItem(int rowIdx)
       {
           if (this.listItem.ContainsKey(rowIdx))
           {
               return this.listItem[rowIdx];
           }
           else
           {
               return null;
           }
       }

       /// <summary>
       /// 普通項目を追加する
       /// </summary>
       /// <param name="rowNum">行番(0 based)</param>
       /// <param name="tc">セル定義情報</param>
       public void AddNormalItem(int rowNum, CTemplatedCell tc)
       {
           List<CTemplatedCell> tmp = null;

           if (this.normalItem.ContainsKey(rowNum))
           {
               tmp = this.normalItem[rowNum];
           }
           else
           {
               tmp = new List<CTemplatedCell>();
               this.normalItem.Add(rowNum, tmp);
           }

           tmp.Add(tc);
       }

       /// <summary>
       /// リスト項目を追加する
       /// </summary>
       /// <param name="rowNum">行番(0 based)</param>
       /// <param name="tc"></param>
       public void AddListItem(int rowNum, CTemplatedCell tc)
       {
           List<CTemplatedCell> tcLst = null;

           if (this.listItem.ContainsKey(rowNum))
           {
               tcLst = this.listItem[rowNum];
           }
           else
           {
               tcLst = new List<CTemplatedCell>();
               this.listItem.Add(rowNum, tcLst);
           }//end if

           tcLst.Add(tc);
       }

       /// <summary>
       /// セル定義データがあるかどうかを判定し、結果を返す
       /// </summary>
       /// <param name="type">データ種類</param>
       /// <returns>true:指定typeのデータ有; false:データ無し</returns>
       public bool HaveData(CItemType type)
       {
           bool ret = false;

           if (normalItem.Count > 0)
               ret = true;

           if (type.Equals(CItemType.Normal))
               return ret;

           if (listItem.Count > 0)
               ret = true;

           if (type.Equals(CItemType.List))
               return ret;

           //CItemType.Any
           return ret;
       }

       /// <summary>
       /// 普通項目/リスト項目の行番をすべて取得する
       /// </summary>
       /// <param name="itemType">項目種類</param>
       /// <returns>行番リスト</returns>
       private List<int> getRows(CItemType itemType)
       {
           List<int> ret = new List<int>();

           if (itemType.Equals(CItemType.Normal))
           {
               foreach (int rowNum in this.normalItem.Keys)
               {
                   ret.Add(rowNum);
               }
           }
           else if(itemType.Equals(CItemType.List))
           {
               foreach(int rowNum in this.listItem.Keys)
               {
                   ret.Add(rowNum);
               }
           }
           
           return ret;
       }
   }//end class
}//end namespace

クラスCTemplatedExcel

■  CTemplatedExcel.cs

□ソース抜粋1  ==========

        /// <summary>
       /// シート毎の定義情報 [シート番号, 定義情報]
       /// </summary>
       private SortedDictionary<int, CTemplatedItem> data = new SortedDictionary<int, CTemplatedItem>();

今回は、複数のシートのテンプレート(excelファイル)をサポートしています。
上記ソースでは、「シート番号」とそのシート内の解析情報を格納する「辞書型」変数を定義しています。

□ソース抜粋2  ==========

       /// <summary>
       /// ノーマル項目を追加する
       /// </summary>
       /// <param name="sheetNum">シートindex(0 based)</param>
       /// <param name="tmpCell">項目定義情報</param>
       public void AddNormalData(int sheetNum, CTemplatedCell tmpCell)
       {
           CTemplatedItem tmpItm;

           if (this.data.ContainsKey(sheetNum))
           {
               tmpItm = this.data[sheetNum];
           }
           else
           {
               tmpItm = new CTemplatedItem();
               this.data.Add(sheetNum, tmpItm);
           }//end if

           tmpItm.AddNormalItem(tmpCell.RowIndex, tmpCell);
       }

       /// <summary>
       /// リスト項目を追加する
       /// </summary>
       /// <param name="sheetNum">シートindex(0 based)</param>
       /// <param name="tmpCell">項目定義情報</param>
       public void AddListData(int sheetNum, CTemplatedCell tmpCell)
       {
           CTemplatedItem tmpItm;
           if (this.data.ContainsKey(sheetNum))
           {
               tmpItm = this.data[sheetNum];
           }
           else
           {
               tmpItm = new CTemplatedItem();
               this.data.Add(sheetNum, tmpItm);
           }//end if

           tmpItm.AddListItem(tmpCell.RowIndex, tmpCell);
       }

・AddNormalData(): normal項目情報の追加処理
・AddListData():list項目情報の追加処理

□全体ソース  ==========

using System.Collections.Generic;

namespace jdr_core.templatedExcel
{
   /// <summary>
   /// テンプレートexcelファイル解析結果格納
   /// </summary>
   public class CTemplatedExcel
   {
       /// <summary>
       /// シート毎の定義情報 [シート番号, 定義情報]
       /// </summary>
       private SortedDictionary<int, CTemplatedItem> data = new SortedDictionary<int, CTemplatedItem>();

       /// <summary>
       /// シート番号を取得
       /// </summary>
       public List<int> SheetNumber
       {
           get
           {
               List<int> ret = new List<int>();

               foreach(int key in this.data.Keys){
                   ret.Add(key);
               }

               return ret;
           }
       }

       /// <summary>
       /// 添え字(インデクサー)プロパーティ
       /// 指定シート番号の定義情報を取得する
       /// </summary>
       /// <param name="sheetIdx">シート番号(0 based)</param>
       /// <returns>指定sheetの定義情報;null:定義情報無し</returns>
       public CTemplatedItem this[int sheetIdx]
       {
           get
           {
               if (this.data.ContainsKey(sheetIdx))
               {
                   return this.data[sheetIdx];
               }
               else
               {
                   return null;
               }
           }//end get
       }

       /// <summary>
       /// 指定したシートの項目定義情報があるかどうかを判定
       /// </summary>
       /// <param name="sheetNum">シート番号(0 based)</param>
       /// <returns>true:項目定義情報有;false:無し</returns>
       public bool HaveData(int sheetNum)
       {
           return data.ContainsKey(sheetNum);
       }

       /// <summary>
       /// 項目定義情報があるかどうかを判定する
       /// </summary>
       /// <returns>true:項目定義情報有;false:無し</returns>
       public bool HaveData()
       {
           return data.Count > 0 ? true : false;
       }

       /// <summary>
       /// シート毎の項目定義情報を追加する(重複時、上書き)
       /// </summary>
       /// <param name="sheetNum">シート番(0 based)</param>
       /// <param name="itm">項目定義情報</param>
       public void AddSheetData(int sheetNum, CTemplatedItem itm)
       {
           if (this.data.ContainsKey(sheetNum))
               this.data.Remove(sheetNum);

           this.data.Add(sheetNum, itm);
       }

       /// <summary>
       /// ノーマル項目を追加する
       /// </summary>
       /// <param name="sheetNum">シートindex(0 based)</param>
       /// <param name="tmpCell">項目定義情報</param>
       public void AddNormalData(int sheetNum, CTemplatedCell tmpCell)
       {
           CTemplatedItem tmpItm;

           if (this.data.ContainsKey(sheetNum))
           {
               tmpItm = this.data[sheetNum];
           }
           else
           {
               tmpItm = new CTemplatedItem();
               this.data.Add(sheetNum, tmpItm);
           }//end if

           tmpItm.AddNormalItem(tmpCell.RowIndex, tmpCell);
       }

       /// <summary>
       /// リスト項目を追加する
       /// </summary>
       /// <param name="sheetNum">シートindex(0 based)</param>
       /// <param name="tmpCell">項目定義情報</param>
       public void AddListData(int sheetNum, CTemplatedCell tmpCell)
       {
           CTemplatedItem tmpItm;
           if (this.data.ContainsKey(sheetNum))
           {
               tmpItm = this.data[sheetNum];
           }
           else
           {
               tmpItm = new CTemplatedItem();
               this.data.Add(sheetNum, tmpItm);
           }//end if

           tmpItm.AddListItem(tmpCell.RowIndex, tmpCell);
       }

       /// <summary>
       /// 指定したシートの定義情報を削除する
       /// </summary>
       /// <param name="sheetNum">削除するシート番(0 based)</param>
       /// <returns></returns>
       public bool DeleteSheetData(int sheetNum)
       {
           if (data.ContainsKey(sheetNum))
           {
               data.Remove(sheetNum);
               return true;
           }

           return false;
       }

   }//end class
}//end namespace

列挙型CTemplatedExcelDataValueType / クラスCTemplatedExcelDataValue

■ CTemplatedExcelDataValue.cs

□列挙型CTemplatedExcelDataValueType  ==========

   /// <summary>
   /// 値の型定義
   /// </summary>
   public enum CTemplatedExcelDataValueType
   {
       /// <summary>
       /// bool型
       /// </summary>
       B,
       /// <summary>
       /// DateTime型
       /// </summary>
       DT,
       /// <summary>
       /// Double型(数値)
       /// </summary>
       DB,
       /// <summary>
       /// string型
       /// </summary>
       S
   }

実際にexcelに出力するデータ型は、「bool型(2値型)」、「日付型」、「数値型」、「文字列型」の4種類をサポートするので、それぞれ定義しています。

□ソース抜粋1  ==========

       //型
       private CTemplatedExcelDataValueType type = CTemplatedExcelDataValueType.S;  //def:文字列

       /// <summary>
       /// それぞれの型に合わせて値保存
       /// </summary>
       private bool bValue;
       private DateTime dtValue;
       private double dbValue;
       private string sValue;

typeでデータ型を指定し、その値によって「bValue/dtValue/dbValue/sValue」のうち1つの変数のみ有効になる仕組みです。

□全体ソース  ==========

using System;
using System.Text;

namespace jdr_core.templatedExcel
{
   /// <summary>
   /// 値の型定義
   /// </summary>
   public enum CTemplatedExcelDataValueType
   {
       /// <summary>
       /// bool型
       /// </summary>
       B,
       /// <summary>
       /// DateTime型
       /// </summary>
       DT,
       /// <summary>
       /// Double型(数値)
       /// </summary>
       DB,
       /// <summary>
       /// string型
       /// </summary>
       S
   }

   /// <summary>
   /// テンプレートexcelのセルに設定する値の定義クラス
   /// </summary>
   public class CTemplatedExcelDataValue
   {
       //型
       private CTemplatedExcelDataValueType type = CTemplatedExcelDataValueType.S;  //def:文字列

       /// <summary>
       /// それぞれの型に合わせて値保存
       /// </summary>
       private bool bValue;
       private DateTime dtValue;
       private double dbValue;
       private string sValue;

       /// <summary>
       /// 値の型を取得
       /// </summary>
       public CTemplatedExcelDataValueType ValueType
       {
           get
           {
               return type;
           }
       }

       /// <summary>
       /// 型チェック
       /// </summary>
       /// <param name="typ"></param>
       private void checkType(CTemplatedExcelDataValueType typ)
       {
           if (!this.type.Equals(typ))  //型不一致
           {
               StringBuilder msg = new StringBuilder();
               msg.Append("値の型と指定した型が一致しません。[値型:");
               msg.Append(this.type.ToString());
               msg.Append(" , 指定した型:");
               msg.Append(typ.ToString());
               msg.Append("]");

               throw new ApplicationException(msg.ToString());
           }
       }

       /// <summary>
       /// bool型の値を取得する
       /// </summary>
       public bool BoolValue
       {
           get
           {
               checkType(CTemplatedExcelDataValueType.B);
               return bValue;
           }
       }

       /// <summary>
       /// DateTime型の値を取得
       /// </summary>
       public DateTime DateTimeValue
       {
           get
           {
               checkType(CTemplatedExcelDataValueType.DT);
               return dtValue;
           }
       }

       /// <summary>
       /// double型の値を取得
       /// </summary>
       public double DoubleValue
       {
           get
           {
               checkType(CTemplatedExcelDataValueType.DB);
               return dbValue;
           }
       }

       /// <summary>
       /// string型の値を取得
       /// </summary>
       public string StringValue
       {
           get
           {
               checkType(CTemplatedExcelDataValueType.S);
               return sValue;
           }
       }

       public CTemplatedExcelDataValue(bool value)
       {
           type = CTemplatedExcelDataValueType.B;
           bValue = value;
       }

       public CTemplatedExcelDataValue(DateTime value)
       {
           type = CTemplatedExcelDataValueType.DT;
           dtValue = value;
       }

       public CTemplatedExcelDataValue(double value)
       {
           type = CTemplatedExcelDataValueType.DB;
           dbValue = value;
       }

       public CTemplatedExcelDataValue(string value)
       {
           type = CTemplatedExcelDataValueType.S;
           sValue = value;
       }

       /// <summary>
       /// "値,列挙型"文字列を解析する
       /// </summary>
       /// <param name="str"></param>
       /// <returns></returns>
       public static CTemplatedExcelDataValue Parse(string str)
       {
           string valuePart = "";
           string typePart = "";

           int pos = str.LastIndexOf(",");

           //フォーマット違反文字列チェック
           if (pos < 0                            // カンマ無し
               || pos == str.Length - 1)   //列挙型無し
               return null;

           valuePart = pos==0? "" : str.Substring(0, pos);
           typePart = str.Substring(pos + 1, str.Length - pos - 1);

           CTemplatedExcelDataValueType type;
           try
           {
               type = (CTemplatedExcelDataValueType)Enum.Parse(typeof(CTemplatedExcelDataValueType), typePart);
           }
           catch(Exception /*e*/)
           {
               string msg = "型の文字列が不適切です [型文字列:" + typePart + "]";
               throw new ApplicationException(msg);
           }

           CTemplatedExcelDataValue retVal = null;
           switch (type)
           {
               case CTemplatedExcelDataValueType.B:
                   bool bval = Boolean.Parse(valuePart);
                   retVal = new CTemplatedExcelDataValue(bval);
                   break;
               case CTemplatedExcelDataValueType.DT:
                   DateTime dtVal = Convert.ToDateTime(valuePart);
                   retVal = new CTemplatedExcelDataValue(dtVal);
                   break;
               case CTemplatedExcelDataValueType.DB:
                   double dbVal = Double.Parse(valuePart);
                   retVal = new CTemplatedExcelDataValue(dbVal);
                   break;
               default:  //string型
                   retVal = new CTemplatedExcelDataValue(valuePart);
                   break;
           }//end switch

           return retVal;
       }
       /// <summary>
       /// "値,列挙型"の文字列へ変換する
       /// </summary>
       /// <returns></returns>
       public override string ToString()
       {
           string ret = "";

           switch (this.type)
           {
               case CTemplatedExcelDataValueType.B:  //bool型
                   ret = this.bValue.ToString();
                   break;
               case CTemplatedExcelDataValueType.DT:  //DateTime型
                   ret = this.dtValue.ToString();
                   break;
               case CTemplatedExcelDataValueType.DB:  //double型
                   ret = this.dbValue.ToString();
                   break;
               default:  //string型
                   ret = this.sValue;
                   break;
           }

           ret += "," + type.ToString();
           return ret;
       }

   }//end class
}//end namespace

クラスCTemplatedExcelData

■  CTemplatedExcelData.cs

□ソース抜粋1  ==========

       /// <summary>
       /// normal項目のデータ格納 [groupName, [valueName, CTemplatedExcelDataValue]]
       /// </summary>
       IDictionary<string, IDictionary<string, CTemplatedExcelDataValue>> normalData = 
           new Dictionary<string,IDictionary<string, CTemplatedExcelDataValue>>();

       /// <summary>
       /// リスト項目のデータ格納 [groupName, list[ [valueName, CTemplatedExcelDataValue] ]]
       /// </summary>
       IDictionary<string, IList<IDictionary<string, CTemplatedExcelDataValue>>> listData = 
           new Dictionary<string, IList<IDictionary<string, CTemplatedExcelDataValue>>>();

normal項目とlist項目の値を格納する「辞書型」変数「normalData」、「listData」を定義しています。

□全体ソース  ==========

using System.Collections.Generic;

namespace jdr_core.templatedExcel
{
   /// <summary>
   /// excelに設定するデータ格納用
   /// </summary>
   public class CTemplatedExcelData
   {
       /// <summary>
       /// normal項目のデータ格納 [groupName, [valueName, CTemplatedExcelDataValue]]
       /// </summary>
       IDictionary<string, IDictionary<string, CTemplatedExcelDataValue>> normalData = 
           new Dictionary<string,IDictionary<string, CTemplatedExcelDataValue>>();

       /// <summary>
       /// リスト項目のデータ格納 [groupName, list[ [valueName, CTemplatedExcelDataValue] ]]
       /// </summary>
       IDictionary<string, IList<IDictionary<string, CTemplatedExcelDataValue>>> listData = 
           new Dictionary<string, IList<IDictionary<string, CTemplatedExcelDataValue>>>();

       /// <summary>
       /// 非リスト項目データを追加する
       /// </summary>
       /// <param name="groupName"></param>
       /// <param name="valueName"></param>
       /// <param name="value"></param>
       public void AddNormalData(string groupName, string valueName, CTemplatedExcelDataValue value)
       {
           IDictionary<string, CTemplatedExcelDataValue> tmpDic = null;
           if (this.normalData.ContainsKey(groupName))
           {
               tmpDic = this.normalData[groupName];  //key不存在時:例外
           }
           else
           {
               tmpDic = new Dictionary<string, CTemplatedExcelDataValue>();
               this.normalData.Add(groupName, tmpDic);
           }//end if

           tmpDic.Add(valueName, value);

       }//end AddData()

       /// <summary>
       /// 非リスト項目データを取得する
       /// </summary>
       /// <param name="groupName"></param>
       /// <param name="valueName"></param>
       /// <returns>null:該当データ無し; 以外:該当データ</returns>
       public CTemplatedExcelDataValue GetNormalData(string groupName, string valueName)
       {
           CTemplatedExcelDataValue ret = null;

           IDictionary<string, CTemplatedExcelDataValue> tmpDic = null;
           if (this.normalData.ContainsKey(groupName))
           {
               tmpDic = this.normalData[groupName];
               tmpDic.TryGetValue(valueName, out ret);  //key不存在時:null返却(例外にはならない)
           }//end if

           return ret;
       }//end GetData()

       /// <summary>
       /// リスト項目データを追加する(行単位 = 1 Dictionary)
       /// </summary>
       /// <param name="groupName"></param>
       /// <param name="lstRowData"></param>
       public void AddListData(string groupName, IDictionary<string, CTemplatedExcelDataValue> lstRowData)
       {
           IList<IDictionary<string, CTemplatedExcelDataValue>> tmpLst = null;
           if (this.listData.ContainsKey(groupName))
           {
               tmpLst = this.listData[groupName];
           }
           else
           {
               tmpLst = new List<IDictionary<string, CTemplatedExcelDataValue>>();
               this.listData.Add(groupName, tmpLst);
           }//end if

           tmpLst.Add(lstRowData);
       }//end AddListData()

       /// <summary>
       /// リスト項目データを取得する(リストデータ全て)
       /// </summary>
       /// <param name="groupName"></param>
       /// <returns></returns>
       public IList<IDictionary<string, CTemplatedExcelDataValue>> GetListData(string groupName)
       {
           IList<IDictionary<string, CTemplatedExcelDataValue>> ret = null;
           if (this.listData.ContainsKey(groupName))
           {
               ret = this.listData[groupName];
           }//end if

           return ret;
       }//end GetListData()

       /// <summary>
       /// リスト項目データの、指定された行データを取得する
       /// </summary>
       /// <param name="groupName"></param>
       /// <param name="rowIdx">取得したい、リスト項目の行のindex(0 based)</param>
       /// <returns>null:データ無し; 以外:データ</returns>
       public IDictionary<string, CTemplatedExcelDataValue> GetListData(string groupName, int rowIdx)
       {
           IDictionary<string, CTemplatedExcelDataValue> ret = null;

           if (!this.listData.ContainsKey(groupName))
               return null;

           IList<IDictionary<string, CTemplatedExcelDataValue>> tmpLst = this.listData[groupName];
           if (tmpLst.Count == 0)
               return null;

           if (tmpLst.Count > rowIdx)
               ret = tmpLst[rowIdx];

           return ret;
       }//end GetListData()

       /// <summary>
       /// データがあるかどうかを判定し、結果を返す
       /// </summary>
       /// <param name="type">データ種類</param>
       /// <returns>true:指定タイプのデータあり; false:データ無し</returns>
       public bool HaveData(CItemType type)
       {
           bool ret = false;

           if (this.normalData.Count > 0)
               ret = true;

           if (type.Equals(CItemType.Normal))
               return ret;

           if (this.listData.Count > 0)
               ret = true;

           if (type.Equals(CItemType.List))
               return ret;

           //CItemType.Any
           return ret;
       }//end HaveData()

   }//end class
}//end namespace

外に使う共通クラス

該当アプリは下記のような共通クラスを使用しています。

画像6

1つのアプリに限らずどのアプリでも使えるような機能は「共通機能」として抽出し、独自のクラスに実装しておけば将来別のアプリでも使えるようになるので、同じソースコードを何度でも書く手間が省けます。

クラスCCommon

■CCommon.cs

アプリ全体(ほかのアプリも含)で使える共通関数を格納するクラスです。

このような共通関数は一気に実装するのではなく、普段プログラミングしながら共通関数を抽出し少しずつ実装していく感じです。

□ソース全体 ==========

using System.Collections.Generic;

namespace jdr_core
{
   /// <summary>
   /// プロジェクト全体の共通クラス
   /// ■ LOGGER : log4netのロガー
   /// ■ MAP    : 共通用マップ
   /// </summary>
   public static class CCommon
   {
       /// <summary>
       /// 共通マップ
       ///   Add(key, value) ※すでに存在する場合エラー;上書き: map["key"] = value;
       ///   foreach(string str in map.Keys)
       ///   Remove(key) / Clear()
       ///   ContainsKey(key) : bool
       /// </summary>
       public static readonly IDictionary<string, object> MAP = new Dictionary<string, object>();
       
       /// <summary>
       /// 静的コンストラクター
       /// Static constructor is calles at most one time, before any instance constructor is invoked 
       /// or member is accessed.
       /// </summary>
       static CCommon()
       {

       }//end CCommon()

       /// <summary>
       /// プロパティファイルから指定したキーのメッセージを取得
       /// </summary>
       /// <param name="resKey">Resourceファイルで定義しているキー</param>
       /// <param name="param">メッセージに埋め込む引数値配列; 引数無し時はnull/空の文字列配列</param>
       /// <returns></returns>
       public static string GetMessage(string resKey, params string[] param)
       {
           string ret = getMessage(resKey);

           if(ret!=null)
           {
               if (param == null)
                   param = new string[] { "" };

               ret = string.Format(ret, param);
           }//end if

           return ret;
       }//end GetMessage()

       /// <summary>
       /// プロパティファイルから指定したキーのメッセージを取得
       /// </summary>
       /// <param name="resKey">Resourceファイルで定義しているキー</param>
       /// <returns></returns>
       private static string getMessage(string resKey)
       {
           return Properties.Resource1.ResourceManager.GetString(resKey);
       }

   }//end class
}//end namespace

クラスCConst

■CConst.cs

定数値と変数の初期値等を定義するクラスです。

□ソース抜粋 ==========

// リソースキー >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
public const string RK_E20001 = "E20001";  //excelファイルエラー[{0}]
public const string RK_E20002 = "E20002";  //テンプレートexcelファイルが存在しません [{0}]
public const string RK_E20003 = "E20003";  //データが存在しません
public const string RK_E20004 = "E20004";  //テンプレートexcelに項目定義情報が存在しません
public const string RK_E20005 = "E20005";  //ファイルコピー中エラー発生[src={0}, dst={1}]
public const string RK_E20006 = "E20006";  //ファイル読み込みエラー[file={0}]
public const string RK_E20007 = "E20007";  //ファイル書き込みエラー[file={0}]
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

エラーメッセージのキーもCConstで定義しています。C#ではエラーメッセージの内容はリソースファイル(Resource1.resx)にで管理し、ソースからはキーでアクセスする方法が一般的です。

□ソース全体 ==========

using System;

namespace jdr_core
{
   /// <summary>
   /// 定数値定義s
   /// </summary>
   static class CConst
   {
       // excelファイルの拡張子定義
       public const string EXCEL_EXT_2003 = ".XLS";    //excel2003
       public const string EXCEL_EXT_2007 = ".XLSX";  //excel2007以降

       //テンプレートexcelの定義関連
       public const string TEMPLATE_CELL_ST = "${";
       public const string TEMPLATE_CELL_ED = "}";
       public const string TEMPLATE_CELL_SEPARATOR = ".";  //group nameとvalue nameの分離記号
       public const string TEMPLATE_CELL_LISTGROUP = "[]";

       // リソースキー >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
       public const string RK_E20001 = "E20001";  //excelファイルエラー[{0}]
       public const string RK_E20002 = "E20002";  //テンプレートexcelファイルが存在しません [{0}]
       public const string RK_E20003 = "E20003";  //データが存在しません
       public const string RK_E20004 = "E20004";  //テンプレートexcelに項目定義情報が存在しません
       public const string RK_E20005 = "E20005";  //ファイルコピー中エラー発生[src={0}, dst={1}]
       public const string RK_E20006 = "E20006";  //ファイル読み込みエラー[file={0}]
       public const string RK_E20007 = "E20007";  //ファイル書き込みエラー[file={0}]
       //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

   }//end clsss
}//end namespace

尚、メッセージの内容は下記のように定義されています。

画像8

画像7

クラスCLogger

■CLogger.cs

ログを出力する機能をまとめたクラスです。

□ソース抜粋1 ==========

private const string LOG4NET_CONFIG_FILE = @"log4net.xml";  //log4net定義ファイル(EXEと同じ場所)

javaのlog4jに似たように実装しています。該当ログ出力機能は「log4net.xml」という設定ファイルが必要になるので注意してください。(log4net.xmlの内容はソースコードの次に載せます)

□ソース抜粋2 ==========

private const string LOGGER_NAME_ACCESS = "LOGGER.ACCESS";  //ロガー名:ACCESS アクセスロガー
private const string LOGGER_NAME_SQL = "LOGGER.SQL";        //ロガー名:SQL    SQL出力用
private const string LOGGER_NAME_ERROR = "LOGGER.ERROR";    //ロガー名:ERROR  エラー出力

ログファイルの種類を定義するコードです。それぞれの種類毎にログファイルが別れる(詳しくはlog4net.xml参照)仕組みになります。

LOGGER.SQL:実行するSQL文をログ出力する機能です。今回のアプリとは関係ない部分です。将来、DBアクセスするアプリでログ出力する場合を想定して実装した機能です。

□ソース全体 ==========

using log4net;
using System;
using System.IO;
using System.Text;

namespace jdr_core
{
   public static class CLogger
   {
       private const string LOG4NET_CONFIG_FILE = @"log4net.xml";  //log4net定義ファイル(EXEと同じ場所)
       private const string LOGGER_NAME_ACCESS = "LOGGER.ACCESS";  //ロガー名:ACCESS アクセスロガー
       private const string LOGGER_NAME_SQL = "LOGGER.SQL";        //ロガー名:SQL    SQL出力用
       private const string LOGGER_NAME_ERROR = "LOGGER.ERROR";    //ロガー名:ERROR  エラー出力

       /// <summary>
       /// ロガー
       /// </summary>
       internal static readonly ILog LOGGER_STD;
       internal static readonly ILog LOGGER_ACCESS;
       internal static readonly ILog LOGGER_SQL;
       internal static readonly ILog LOGGER_ERROR;

       static CLogger()
       {
           //log4net初期化
           log4net.Config.XmlConfigurator.Configure(new FileInfo(LOG4NET_CONFIG_FILE));

           LOGGER_STD = LogManager.GetLogger("");
           LOGGER_ACCESS = LogManager.GetLogger(LOGGER_NAME_ACCESS);
           LOGGER_SQL = LogManager.GetLogger(LOGGER_NAME_SQL);
           LOGGER_ERROR = LogManager.GetLogger(LOGGER_NAME_ERROR);
       }

       /// <summary>
       /// ログに出力するメッセージを編集する。
       /// </summary>
       /// <param name="classFullName"></param>
       /// <param name="methodName"></param>
       /// <param name="msg"></param>
       /// <returns></returns>
       private static string EditLogMsg(string classFullName, string methodName, string msg)
       {
           StringBuilder sbMsg = new StringBuilder();
           sbMsg.Append(classFullName);
           sbMsg.Append("#" + methodName);
           sbMsg.Append("  " + msg);

           return sbMsg.ToString();
       }

       /// <summary>
       /// ログ出力
       /// </summary>
       /// <param name="logTyp">ログタイプ</param>
       /// <param name="loglvl">ログレベル</param>
       /// <param name="msg">出力メッセージ</param>
       private static void OutputLog(CLogType logTyp, CLogLevel loglvl, string msg, Exception cause)
       {
           ILog logger = LOGGER_STD;

           switch (logTyp)
           {
               case CLogType.ACCESS:
                   logger = LOGGER_ACCESS;
                   break;

               case CLogType.SQL:
                   logger = LOGGER_SQL;
                   break;

               case CLogType.ERROR:
                   logger = LOGGER_ERROR;
                   break;
           }//end switch

           switch (loglvl)
           {
               case CLogLevel.DEBUG:
                   logger.Debug(msg, cause);
                   break;

               case CLogLevel.FATAL:
                   logger.Fatal(msg, cause);
                   break;

               case CLogLevel.ERROR:
                   logger.Error(msg, cause);
                   break;

               case CLogLevel.WARN:
                   logger.Warn(msg, cause);
                   break;

               case CLogLevel.INFO:
                   logger.Info(msg, cause);
                   break;
           }//end switch
       }//end outputLog()

       /// <summary>
       /// ログ出力
       /// </summary>
       /// <param name="logLvl"></param>
       /// <param name="classFullName"></param>
       /// <param name="methodName"></param>
       /// <param name="msg"></param>
       /// <param name="cause"></param>
       public static void Log(CLogLevel logLvl,
                              string classFullName,
                              string methodName,
                              string msg,
                              Exception cause)
       {
           string logMsg = EditLogMsg(classFullName, methodName, msg);
           OutputLog(CLogType.STD, logLvl, logMsg, cause);
       }//end Log()

       /// <summary>
       /// アクセスログ出力
       /// </summary>
       /// <param name="logLvl"></param>
       /// <param name="classFullName"></param>
       /// <param name="methodName"></param>
       /// <param name="msg"></param>
       /// <param name="cause"></param>
       public static void AccessLog(CLogLevel logLvl,
                                    string classFullName,
                                    string methodName,
                                    string msg,
                                    Exception cause)
       {
           string logMsg = EditLogMsg(classFullName, methodName, msg);
           OutputLog(CLogType.ACCESS, logLvl, logMsg, cause);
       }//end AccessLog()

       /// <summary>
       /// SQLログ出力
       /// </summary>
       /// <param name="logLvl"></param>
       /// <param name="classFullName"></param>
       /// <param name="methodName"></param>
       /// <param name="msg"></param>
       /// <param name="cause"></param>
       public static void SQLLog(CLogLevel logLvl,
                                    string classFullName,
                                    string methodName,
                                    string msg,
                                    Exception cause)
       {
           string logMsg = EditLogMsg(classFullName, methodName, msg);
           OutputLog(CLogType.SQL, logLvl, logMsg, cause);
       }//end SQLLog()

       /// <summary>
       /// ERRORログ出力
       /// </summary>
       /// <param name="logLvl"></param>
       /// <param name="classFullName"></param>
       /// <param name="methodName"></param>
       /// <param name="msg"></param>
       /// <param name="cause"></param>
       public static void ErrorLog(CLogLevel logLvl,
                                    string classFullName,
                                    string methodName,
                                    string msg,
                                    Exception cause)
       {
           string logMsg = EditLogMsg(classFullName, methodName, msg);
           OutputLog(CLogType.ERROR, logLvl, logMsg, cause);
       }//end ErrorLog()
   }//end CLogger
}//end namespace

□設定ファイル log4net.xml内容 ==========

当該設定ファイルを実行ファイル(exe)と同じディレクトリに配置してください。

<?xml version="1.0" encoding="utf-8" ?>
 <configuration>
   <log4net>
     <appender name="appender.STD" type="log4net.Appender.RollingFileAppender">
       <Encoding value="UTF-8"/>
       
       <!-- 追加書き込み指定(追加書込みOK) -->
       <AppendToFile value="true" />
       
       <!-- ログファイル名 -->
       <File value="log/ALL-Debug.log" />
       
       <rollingStyle value="Date" />
       <datePattern value='"_"yyyyMMdd".log"' />

       <!-- ログの出力フォーマット -->
       <layout type="log4net.Layout.PatternLayout">
         <ConversionPattern value="%d [%t] %-5p %m%n" />
       </layout>
     </appender>
     
     <appender name="appender.ACCESS" type="log4net.Appender.RollingFileAppender">
       <Encoding value="UTF-8"/>
       
       <!-- 追加書き込み指定(追加書込みOK) -->
       <AppendToFile value="true" />
       
       <!-- ログファイル名 -->
       <File value="log/ACCESS.log" />
       
       <rollingStyle value="Date" />
       <datePattern value='"_"yyyyMMdd".log"' />

       <!-- ログの出力フォーマット -->
       <layout type="log4net.Layout.PatternLayout">
         <ConversionPattern value="%d [%t] %-5p %m%n" />
       </layout>
     </appender>
     
     <appender name="appender.SQL" type="log4net.Appender.RollingFileAppender">
       <Encoding value="UTF-8"/>
       
       <!-- 追加書き込み指定(追加書込みOK) -->
       <AppendToFile value="true" />
       
       <!-- ログファイル名 -->
       <File value="log/SQL.log" />
       
       <rollingStyle value="Date" />
       <datePattern value='"_"yyyyMMdd".log"' />

       <!-- ログの出力フォーマット -->
       <layout type="log4net.Layout.PatternLayout">
         <ConversionPattern value="%d [%t] %-5p %m%n" />
       </layout>
     </appender>
     
     <appender name="appender.ERROR" type="log4net.Appender.RollingFileAppender">
       <Encoding value="UTF-8"/>
       
       <!-- 追加書き込み指定(追加書込みOK) -->
       <AppendToFile value="true" />
       
       <!-- ログファイル名 -->
       <File value="log/ERROR.log" />
       
       <rollingStyle value="Date" />
       <datePattern value='"_"yyyyMMdd".log"' />

       <!-- ログの出力フォーマット -->
       <layout type="log4net.Layout.PatternLayout">
         <ConversionPattern value="%d [%t] %-5p %m%n" />
       </layout>
     </appender>
     
     <!-- アクセスログ >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
     <logger name="LOGGER.ACCESS">
       <level value="DEBUG" />
       <appender-ref ref="appender.ACCESS" />
     </logger>
     <!-- アクセスログ <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< -->
     
     <!-- SQLログ >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
     <logger name="LOGGER.SQL">
       <level value="DEBUG" />
       <appender-ref ref="appender.SQL" />
     </logger>
     <!-- SQLログ <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< -->
     
     <!-- エラーログ >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
     <logger name="LOGGER.ERROR">
       <level value="DEBUG" />
       <appender-ref ref="appender.ERROR" />
     </logger>
     <!-- エラーログ <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< -->
     
     <!-- 標準出力 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
     <root>
       <!--
       <level value="DEBUG" />
       <level value="OFF" />
       <level value="FATAL" />
       <level value="ERROR" />
       <level value="WARN" />
       <level value="INFO" />
       -->
       <!-- 出力ログレベルの指定(全て) -->
       <level value="ALL" />

       <!-- 出力タイプの指定(ファイルへ出力) -->
       <appender-ref ref="appender.STD" />
     </root>
     <!-- 標準出力 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< -->
     
   </log4net>
 </configuration>

log4net.xmlの内容については、グーグル先生に聞けば詳しいい説明があるので、併せて利用してください。

注!当該ログ出力機能は .netライブラリ「log4net」を使っています。説明と詳しい使い方についてはインターネットで検索してください。

画像9

列挙体CLogLevel

■CLogLevel.cs

ログ出力関連の定義内容です。

□ソース全体 ==========

namespace jdr_core
{
   /// <summary>
   /// log4netで出力するログのレベル
   /// </summary>
   public enum CLogLevel
   {
       DEBUG,
       FATAL,
       ERROR,
       WARN,
       INFO
   }//end enum

   /// <summary>
   /// ログタイプ
   /// </summary>
   public enum CLogType
   {
       STD,
       ACCESS,
       SQL,
       ERROR
   }//end enum
}//end namespace

クラスCString

■CString.cs

文字列関連の各操作機能をまとめたクラスです。
今回は関数が1つしかありませんが、将来必要に応じて文字列分割、切り取り等の関数を実装するイメージです。

□ソース全体 ==========

using System;

namespace jdr_core
{
   /// <summary>
   /// 字符串相关的变换功能群
   /// </summary>
   public class CString
   {

       /// <summary>
       /// 文字列がnull or 空("")かを判定
       /// </summary>
       /// <param name="val">判定する文字列</param>
       /// <param name="ignoreSpace">半角スペース無視フラグ(true:半角スペース無視; false:無視しない)</param>
       /// <returns>true:null or 空である; false:以外</returns>
       public static bool IsNullOrEmpty(string val, bool ignoreSpace = false)
       {
           bool ret = false;

           if (val == null)
               return true;

           if (ignoreSpace)
               val = val.Trim();

           if (val.Length == 0)
               return true;

           return ret;
       }
   }//end class
}//end namespace


まとめ

記事の長さがオーバーしているので、今回はここまでにします。

残りクラス「CTemplatedExcelUtil」と、テスト(呼び出し方法)方法については次回にします。

では、バイバイ! Have a nice day!

この記事が気に入ったらサポートをしてみませんか?