葛藤プログラマの一日

2008-05-30

Excelファイル内の検索

システムの見積するのに、特定の文字に囲まれた文字列の数を、シート毎、文字列毎に個数を計算する必要がでてきた。

そこで、以下のようなプログラムを作成。
はじめは、Worksheet.UsedRange の各セルを全部調べてましたが、あまりにも遅くて・・・(^^;
そこで、WorkSheet.UsedRangeで取得したRangeのFindとFindNextを使うことで数十倍速く・・・
はじめからこうすればよかったのね。


Sample
------


// 参照の追加で、COMコンポーネント「Micorosoft Excel 11.0 Object Library」の追加が必要
// COMコンポーネントの追加については、最後の記述を参照

using System.Runtime.InteropServices;
using System.Collections;
using System.IO;
using ExcelApp = Microsoft.Office.Interop.Excel;

public void main(){
SortedList> stringListInSheet = ExcelInfoData(Excelファイル名);
//おまけ
OutputCsvFile(stringListInSheet, ファイル名);
}


public SortedList> ExcelInfoData(string filePath, string leftSideChar, string rightSideChar)
{
SortedList> stringListInSheet = new SortedList>();
try
{
_excelApp = new ExcelApp.Application();
_excelApp.Visible = false;
_books = _excelApp.Workbooks;
_book = _books.Add(filePath);
_sheets = _book.Sheets;

for (int sheetCounter = 1; sheetCounter <= _sheets.Count; sheetCounter++)
{
try
{
_sheet = (ExcelApp.Worksheet)_sheets.get_Item(sheetCounter);
_range = _sheet.UsedRange;

List list = new List();
Object missing = System.Reflection.Missing.Value;

ExcelApp.Range findRange = _range.Find(leftSideChar, missing,
ExcelApp.XlFindLookIn.xlValues, ExcelApp.XlLookAt.xlPart,
ExcelApp.XlSearchOrder.xlByRows, ExcelApp.XlSearchDirection.xlNext, true, true, missing);
ExcelApp.Range prevRange;
if (findRange == null)
{
ReleaseAndNullComObj(findRange);
continue;
}
int columnIndex = findRange.Column;
int rowIndex = findRange.Row;
list.AddRange(GetStringBetweenChars((string)findRange.Text, new char[] { leftSideChar[0], rightSideChar[0] }));
prevRange = findRange;

while (findRange != null)
{
// 2度目以降はFindNext
findRange = _range.FindNext(prevRange);
ReleaseAndNullComObj(prevRange);

if (findRange.Column == columnIndex && findRange.Row == rowIndex)
{
break;
}

list.AddRange(GetStringBetweenChars((string)findRange.Text, new char[] { leftSideChar[0], rightSideChar[0] }))
prevRange = findRange;
}
ReleaseAndNullComObj(findRange);

// シート名の出力
stringListInSheet.Add(_sheet.Name, list);
}
finally
{
ReleaseAndNullComObj(_range);
ReleaseAndNullComObj(_sheet);
}
}
}
finally
{
ReleaseAndNullComObj(_sheets);
ReleaseAndNullComObj(_book);
ReleaseAndNullComObj(_books);
_excelApp.Quit();
ReleaseAndNullComObj(_excelApp);
}
return stringListInSheet;
}


private void ReleaseAndNullComObj(object comObj)
{
if (comObj == null)
return;
Marshal.ReleaseComObject(comObj);
comObj = null;
}


private List GetStringBetweenChars(string source, char[] sideChars)
{
int leftSideIndex = -1;

List result = new List();

for (int i = 0; i < source.Length; i++)
{
if (source[i] == sideChars[0])
{
if (leftSideIndex == -1)
leftSideIndex = i;
continue;
}
else if (source[i] == sideChars[1])
{
if (leftSideIndex != -1)
{
result.Add(source.Substring(leftSideIndex + 1, i - 1 - leftSideIndex));
leftSideIndex = -1;
}
}
}
return result;
}


// おまけ
private void OutputCsvFile(SortedList> data, string filePath)
{
using (StreamWriter writer = new StreamWriter(filePath, false, Encoding.Unicode))
{
writer.WriteLine("シート名\t文字列\t数");
Dictionary statisticsData;
foreach (string key in data.Keys)
{
statisticsData = new Dictionary();
foreach (string value in data[key])
{
if (!statisticsData.ContainsKey(value))
statisticsData[value] = 1;
else
statisticsData[value]++;
}
foreach (string value in statisticsData.Keys)
{
writer.WriteLine("{0}\t{1}\t{2}", key, value, statisticsData[value]);
}
}
}
}









COMコンポーネントは、Excelのセットアップでインストール項目として、 [.NET プログラミング サポート]を選んでインストールする必要があります。
http://www.microsoft.com/japan/msdn/office/office2003/OfficePrimaryInteropAssembliesFAQ.aspx
http://msdn.microsoft.com/ja-jp/library/kh3965hw(VS.80).aspx

なお、以下のURLからダウンロードしてインストールすることもできます。
http://www.microsoft.com/downloads/details.aspx?familyid=3c9a983a-ac14-4125-8ba0-d36d67e0f4ad&displaylang=en