EternalWindows
MSHTML / テーブルの取得

今回は、テーブルのセルからデータを取得する方法について説明します。 次に、テーブルの例を示します。

name price
apple 100
orange 200

使用するインターフェースを理解するために、上記テーブルのhtmlを示します。

<table id = "sample">
	<tr>
	<th>name</th> <th>price</th>
	</tr>

	<tr>
	<td>apple</td> <td>100</td>
	</tr>

	<tr>
	<td>orange</td> <td>200</td>
	</tr>
</table>

テーブルにおける行の数は、<tr></tr>の組み合わせの数によって決定します。 上記ではこの組み合わせが3個存在するため、テーブルは3行ということになります。 複数存在するテーブルの行は、IHTMLTable::get_rowsで取得できるIHTMLElementCollectionで列挙可能であり、 これのitemメソッドを呼び出せば1つの行を表すIHTMLTableRowを取得できます。 ここからさらに行に含まれるセルを取得するには、 IHTMLTableRow::get_cellsでIHTMLElementCollectionを取得し、 1つのセルを表すIHTMLElementを列挙することになります。 この列挙できる数は、<tr></tr>に含まれる<th>または<td>の数によって決定します。

今回のプログラムは、テーブルのセルに格納されているデータをメッセージボックスで表示します。

#include <windows.h>
#include <exdisp.h>
#include <mshtml.h>
#include <oleacc.h>

#pragma comment (lib, "oleacc.lib")

BOOL ShowTableData(IHTMLDocument3 *pDocument3);
BOOL GetDocumentFromIE(IHTMLDocument3 **pp);
BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	IHTMLDocument3 *pDocument3;

	CoInitialize(NULL);

	if (!GetDocumentFromIE(&pDocument3)) {
		CoUninitialize();
		return 0;
	}
	
	ShowTableData(pDocument3);

	CoUninitialize();
	
	return 0;
}

BOOL ShowTableData(IHTMLDocument3 *pDocument3)
{
	LONG                   i, j;
	LONG                   lRowCount, lCellCount;
	BSTR                   bstrId, bstrCell;
	VARIANT                varName, varIndex;
	IHTMLElement           *pElement, *pCellElement;
	IHTMLTable             *pTable;
	IHTMLTableRow          *pTableRow;
	IHTMLElementCollection *pRowCollection, *pCellCollection;
	IDispatch              *pDispatch;

	bstrId = SysAllocString(L"sample");
	pDocument3->getElementById(bstrId, &pElement);
	if (pElement == NULL) {
		SysFreeString(bstrId);
		return FALSE;
	}
	
	pElement->QueryInterface(IID_PPV_ARGS(&pTable));

	pTable->get_rows(&pRowCollection);
	pRowCollection->get_length(&lRowCount);

	for (i = 0; i < lRowCount; i++) {
		VariantInit(&varName);
		varName.vt = VT_I4;
		varName.lVal = i;

		VariantInit(&varIndex);
		varIndex.vt = VT_I4;
		varIndex.lVal = 0;

		pRowCollection->item(varName, varIndex, &pDispatch);
		pDispatch->QueryInterface(IID_PPV_ARGS(&pTableRow));
		pDispatch->Release();

		pTableRow->get_cells(&pCellCollection);
		pCellCollection->get_length(&lCellCount);
		pTableRow->Release();

		for (j = 0; j < lCellCount; j++) {
			VariantInit(&varName);
			varName.vt = VT_I4;
			varName.lVal = j;

			VariantInit(&varIndex);
			varIndex.vt = VT_I4;
			varIndex.lVal = 0;

			pCellCollection->item(varName, varIndex, &pDispatch);
			pDispatch->QueryInterface(IID_PPV_ARGS(&pCellElement));
			pDispatch->Release();

			pCellElement->get_innerText(&bstrCell);
			MessageBoxW(NULL, bstrCell, L"OK", MB_OK);
			SysFreeString(bstrCell);
			
			pCellElement->Release();
		}
		
		pCellCollection->Release();
	}
	
	pRowCollection->Release();
	pTable->Release();
	pElement->Release();

	return TRUE;
}

BOOL GetDocumentFromIE(IHTMLDocument3 **pp)
{
	HWND    hwnd;
	UINT    uMsg;
	LRESULT lResult;
	HRESULT hr;
	
	EnumChildWindows(FindWindow(TEXT("IEFrame"), NULL), EnumChildProc, (LPARAM)&hwnd);
	if (hwnd == NULL)
		return FALSE;

	uMsg = RegisterWindowMessage(TEXT("WM_HTML_GETOBJECT"));
	if (!SendMessageTimeout(hwnd, uMsg, 0, 0, SMTO_ABORTIFHUNG, 1000, (LPDWORD)&lResult))
		return FALSE;

	hr = ObjectFromLresult(lResult, IID_IHTMLDocument3, 0, (void **)pp);
	if (FAILED(hr))
		return FALSE;

	return TRUE;
}

BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lParam)
{
	TCHAR szClassName[256];

	GetClassName(hwnd, szClassName, sizeof(szClassName) / sizeof(TCHAR));
	if (lstrcmp(szClassName, TEXT("Internet Explorer_Server")) == 0) {
		*((HWND *)lParam) = hwnd;
		return FALSE;
	}
	else
		return TRUE;
}

テーブルからデータを取得するためには、まず何よりもIHTMLTableを取得する必要があります。 <table>タグのidはsampleであるため、IHTMLDocument3::getElementByIdでIHTMLElementを取得することができ、 そこからQueryInterfaceを呼び出すことでIHTMLTableを取得できます。 この時点まで来れば後は、get_rowsを呼び出して列を列挙するためのIHTMLElementCollectionを取得し、 get_lengthで取得した列数だけ列挙を行うことになります。

for (i = 0; i < lRowCount; i++) {
	VariantInit(&varName);
	varName.vt = VT_I4;
	varName.lVal = i;

	VariantInit(&varIndex);
	varIndex.vt = VT_I4;
	varIndex.lVal = 0;

	pRowCollection->item(varName, varIndex, &pDispatch);
	pDispatch->QueryInterface(IID_PPV_ARGS(&pTableRow));
	pDispatch->Release();

	pTableRow->get_cells(&pCellCollection);
	pCellCollection->get_length(&lCellCount);
	pTableRow->Release();

	for (j = 0; j < lCellCount; j++) {
		VariantInit(&varName);
		varName.vt = VT_I4;
		varName.lVal = j;

		VariantInit(&varIndex);
		varIndex.vt = VT_I4;
		varIndex.lVal = 0;

		pCellCollection->item(varName, varIndex, &pDispatch);
		pDispatch->QueryInterface(IID_PPV_ARGS(&pCellElement));
		pDispatch->Release();
		
		pCellElement->get_innerText(&bstrCell);
		MessageBoxW(NULL, bstrCell, L"OK", MB_OK);
		SysFreeString(bstrCell);
		
		pCellElement->Release();
	}
	
	pCellCollection->Release();
}

iをベースにしたループが列を取得する処理であり、jをベースにしたループがセルを取得する処理になります。 まず、pRowCollection->itemによって1つの列を表すIDispatchを取得します。 このIDispatchからは、QueryInterfaceでIHTMLTableRowを取得できます。 itemの第1引数と第2引数はどちらもVARIANT構造体であり、第1引数の方には列のインデックスを指定します。 第2引数については0で問題ないでしょう。 IHTMLTableRowを取得したら、get_cellsを呼び出してセルを列挙するIHTMLElementCollectionを取得し、 セルを取得するためのループに入ることになります。 pCellCollection->itemを呼び出す手順は、pRowCollection->itemの際と同様であり、 セルの場合は取得したIDispatchをIHTMLTableCellで表すことができます。 ただし、IHTMLTableCellにはセルのデータを取得するメソッドがないため、 pCellElementの型はIHTMLElementになっています。 そして、IHTMLElement::get_innerTextを通じてセルのデータを取得しています。

IHTMLTable2の使用

今回はテーブル内の全てのセルを列挙しましたが、 全てのセルを列挙することに限定するならば、 IHTMLTable2::get_cellsを呼び出した方が効率的です。 このメソッドが返すIHTMLElementCollectionを使用すれば、全てのセルを列挙することができます。

BOOL ShowTableData(IHTMLDocument3 *pDocument3)
{
	LONG                   i;
	LONG                   lCellCount;
	BSTR                   bstrId, bstrCell;
	VARIANT                varName, varIndex;
	IHTMLElement           *pElement, *pCellElement;
	IHTMLTable2            *pTable2;
	IHTMLElementCollection *pCellCollection;
	IDispatch              *pDispatch;

	bstrId = SysAllocString(L"sample");
	pDocument3->getElementById(bstrId, &pElement);
	if (pElement == NULL) {
		SysFreeString(bstrId);
		return FALSE;
	}
	
	pElement->QueryInterface(IID_PPV_ARGS(&pTable2));

	pTable2->get_cells(&pCellCollection);
	pCellCollection->get_length(&lCellCount);

	for (i = 0; i < lCellCount; i++) {
		VariantInit(&varName);
		varName.vt = VT_I4;
		varName.lVal = i;

		VariantInit(&varIndex);
		varIndex.vt = VT_I4;
		varIndex.lVal = 0;

		pCellCollection->item(varName, varIndex, &pDispatch);
		pDispatch->QueryInterface(IID_PPV_ARGS(&pCellElement));
		pDispatch->Release();

		pCellElement->get_innerText(&bstrCell);
		MessageBoxW(NULL, bstrCell, L"OK", MB_OK);
		SysFreeString(bstrCell);
		
		pCellElement->Release();
	}
	
	pCellCollection->Release();
	pTable2->Release();
	pElement->Release();

	return TRUE;
}

上記のコードでは列を列挙するためのIHTMLElementCollectionや、 列を表すIHTMLTableRowが使用されていないため、コードは少しだけ簡素になっています。 特定の列のセルを列挙したいような場合は、IHTMLTableRowが必要になるでしょう。



戻る