ページ 1 / 1
XPathを使うべきか それともDOM?
Posted: 2012年10月20日(土) 17:11
by らん
C言語でXMLの中のデータをランダムに参照しようとしていますが、
なにせ、これまで全然XMLと無縁だったので、理解できないところがあります。
まず、XML Path Language (XPath)を使うべきかそれともDOMでしょうか。
要はXPathとDOMはどういう関係でしょうか。
同時に使うべきかそれとも排他的関係でしょうか。
開発環境:MS WINDOWS XPで ビジュアルstidio VC2010
宜しくお願いします
Re: XPathを使うべきか それともDOM?
Posted: 2012年10月20日(土) 17:47
by softya(ソフト屋)
C言語からDOMとXPathはどうやって使う予定でしょうか? 利用するライブラリ/APIを教えて下さい。
Re: XPathを使うべきか それともDOM?
Posted: 2012年10月20日(土) 21:52
by らん
ごめんなさい!
恥ずかしいですけれども、何APIですら分かりません。
基本はフリーAPIでいいと思います。
開発環境はMS VISUAL STUDIO VC2010 で、C#を利用する予定はありません。
適当なAPIをご紹介いただければ助かります。
_(._.)_
Re: XPathを使うべきか それともDOM?
Posted: 2012年10月20日(土) 22:16
by softya(ソフト屋)
C++ならクラスを使って幾つかライブラリがあるみたいですが、C言語だと私は「Parsifal XML Parser」ぐらしか使ったことがありません。
http://www.saunalahti.fi/~samiuus/toni/xmlproc/
使い方はDOMでもXPathでもありません。
C++であればXPathやDOMは利用可能なライブラリがありますね。申し訳ないですが使ったことがないので使い勝手はわかりません。
「デベロッパーズコーナー:DOMプログラミング講座 III(1)-1 - XML Square」
http://www.utj.co.jp/xml/dev/dom/dxdom1_1.html
「Xerces-C++ XML Parser」
http://xerces.apache.org/xerces-c/
「TinyXPath : tiny C++ XPath プロジェクト日本語トップページ - SourceForge.JP」
http://sourceforge.jp/projects/sfnet_tinyxpath/
Re: XPathを使うべきか それともDOM?
Posted: 2012年10月20日(土) 22:43
by YuO
Windows環境限定なら
MSXMLがまず出てくる選択肢かなぁ,と。
読み込むことでDOMツリーが形成されて,そのDOMツリーに対して
selectNodesメソッドにXPathを使って対象を選ぶことが出来ます。
とりあえず,
昔書いたサンプルがあるので,参考になればと。
Re: XPathを使うべきか それともDOM?
Posted: 2012年10月21日(日) 06:23
by YuO
MSXML6のサンプル書いてみました。
# CoCreateInstanceでE_NOINTERFACEが出ている原因が,IID_IXMLDOMDocumentをIID_IXMLDocumentと書き間違えていることに気づかず時間を無駄にした……。
まず,XML。とりあえず,名前はtest.xmlとします。
コード:
<?xml version="1.0" encoding="utf-8"?>
<books>
<book isbn="9784320026926">
<title>プログラミング言語C 第2版 ANSI規格準拠</title>
<authors>
<author>B.W.カーニハン</author>
<author>D.M.リッチー</author>
</authors>
<translators>
<translator>石田晴久</translator>
</translators>
</book>
<book isbn="9784756118950">
<title>プログラミング言語C++</title>
<authors>
<author>Bjarne Stroustrup</author>
</authors>
<translators>
<translator>長尾高弘</translator>
</translators>
</book>
</books>
こいつは実行ディレクトリにコピーしておきます。ビルド後イベントに,
コード:
XCOPY /D "$(ProjectDir)test.xml" "$(OutDir)"
とでも書いておけばよいでしょう。
次に,Cの場合のコード。
► スポイラーを表示
コード:
#define COBJMACROS
#include <Windows.h>
#include <Shlwapi.h>
#include <ObjBase.h>
#include <msxml6.h>
#pragma comment(lib, "shlwapi")
#pragma comment(lib, "msxml6")
/* XMLのファイル名取得 */
void getXmlFileName (TCHAR xmlPath[MAX_PATH])
{
TCHAR exePath[MAX_PATH];
GetModuleFileName(NULL, exePath, sizeof(exePath) / sizeof(exePath[0]));
lstrcpy(xmlPath, exePath);
PathRemoveFileSpec(xmlPath);
PathAppend(xmlPath, TEXT("test.xml"));
}
/* XMLファイルを読み込んでDOMを構築する */
IXMLDOMDocument * loadXml (LPCTSTR lpszFileName)
{
IXMLDOMDocument * pDomDocument;
BSTR bstrFileName;
VARIANT varFileName;
VARIANT_BOOL varStatus;
HRESULT hr;
/* 文字コード変換 */
#ifndef UNICODE
WCHAR lpszFileNameW[MAX_PATH];
MultiByteToWideChar(CP_ACP, 0, lpszFileName, -1, lpszFileNameW, sizeof(lpszFileNameW) / sizeof(lpszFileNameW[0]);
#else
LPCWSTR lpszFileNameW = lpszFileName;
#endif
/* DOMオブジェクトの作成 */
hr = CoCreateInstance(
&CLSID_DOMDocument60, NULL, CLSCTX_INPROC_SERVER,
&IID_IXMLDOMDocument, (void **)&pDomDocument);
/* 各種設定 */
hr = IXMLDOMDocument_put_async(pDomDocument, VARIANT_FALSE);
hr = IXMLDOMDocument_put_validateOnParse(pDomDocument, VARIANT_FALSE);
hr = IXMLDOMDocument_put_resolveExternals(pDomDocument, VARIANT_FALSE);
/* 読み込む */
bstrFileName = SysAllocString(lpszFileNameW);
V_VT(&varFileName) = VT_BSTR;
V_BSTR(&varFileName) = bstrFileName;
hr = IXMLDOMDocument_load(pDomDocument, varFileName, &varStatus);
VariantClear(&varFileName);
return pDomDocument;
}
int main (void)
{
TCHAR xmlPath[MAX_PATH];
IXMLDOMDocument * pDomDocument;
IXMLDOMNodeList * pDomNodeList;
long i, count;
HRESULT hr;
/* COMの初期化 */
hr = CoInitialize(NULL);
/* XMLの読み込み */
getXmlFileName(xmlPath);
pDomDocument = loadXml(xmlPath);
/* XPathによる絞り込み */
hr = IXMLDOMDocument_selectNodes(pDomDocument, L"/books/book/title/text()", &pDomNodeList);
/* 絞り込んだ要素の列挙 */
hr = IXMLDOMNodeList_get_length(pDomNodeList, &count);
for (i = 0; i < count; ++i)
{
IXMLDOMNode * pDomNode;
BSTR text;
DWORD dwWritten;
hr = IXMLDOMNodeList_get_item(pDomNodeList, i, &pDomNode);
hr = IXMLDOMNode_get_text(pDomNode, &text);
#ifdef UNICODE
WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), text, SysStringLen(text), &dwWritten, NULL);
#else
{
char * buffer;
int cstrLength;
cstrLength = WideCharToMultiByte(CP_ACP, 0, text, SysStringLen(text), NULL, 0, NULL, NULL) + 1;
buffer = (char *)HeapAlloc(GetProcessHeap(), 0, cstrLength);
buffer[cstrLength - 1] = 0;
WideCharToMultiByte(CP_ACP, 0, text, SysStringLen(text), buffer, cstrLength, NULL, NULL);
WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), buffer, lstrlen(buffer), &dwWritten, NULL);
HeapFree(GetProcessHeap(), 0, buffer);
}
#endif
SysFreeString(text);
WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), TEXT("\r\n"), 2, &dwWritten, NULL);
IXMLDOMNode_Release(pDomNode);
}
/* 後始末 */
IXMLDOMNodeList_Release(pDomNodeList);
IXMLDOMDocument_Release(pDomDocument);
CoUninitialize();
return 0;
}
COMまわりの初期化と後処理が面倒なくらいで,それほど難しくないかと。
# QueryInterfaceしてキャストするようなことをしていないから,というのもありますが。
COBJMACROSで隠蔽していますが,
コード:
IXMLDOMNode_get_text(pDomNode, &text);
は
コード:
pDomNode->lpVtbl->get_text(pDomNode, &text);
と等価です。
次に,C++の場合のコード。
► スポイラーを表示
コード:
#include <Windows.h>
#include <Shlwapi.h>
#include <ObjBase.h>
#include <msxml6.h>
#pragma comment(lib, "shlwapi")
#pragma comment(lib, "msxml6")
// 簡易スマートポインタ
template <typename T> class ComPtr
{
private:
T * ptr_;
public:
ComPtr (T * ptr = nullptr) : ptr_(ptr) { }
ComPtr (const ComPtr & other) : ptr_(other.ptr_) { ptr_->AddRef(); }
ComPtr & operator= (const ComPtr & other)
{
if (other.ptr_) other.ptr_->AddRef();
if (ptr_) ptr_->Release();
ptr_ = other.ptr_;
return *this;
}
~ComPtr () { if (ptr_) ptr_->Release(); }
T * operator -> () { return ptr_; }
const T * operator -> () const { return ptr_; }
operator T** () { return &ptr_; }
};
// XMLのファイル名取得
void getXmlFileName (TCHAR xmlPath[MAX_PATH])
{
TCHAR exePath[MAX_PATH];
GetModuleFileName(NULL, exePath, sizeof(exePath) / sizeof(exePath[0]));
lstrcpy(xmlPath, exePath);
PathRemoveFileSpec(xmlPath);
PathAppend(xmlPath, TEXT("test.xml"));
}
// XMLファイルを読み込んでDOMを構築する
auto loadXml (LPCTSTR lpszFileName) -> ComPtr<IXMLDOMDocument>
{
HRESULT hr;
// 文字コード変換
#ifndef UNICODE
WCHAR lpszFileNameW[MAX_PATH];
MultiByteToWideChar(CP_ACP, 0, lpszFileName, -1, lpszFileNameW, sizeof(lpszFileNameW) / sizeof(lpszFileNameW[0]);
#else
LPCWSTR lpszFileNameW = lpszFileName;
#endif
ComPtr<IXMLDOMDocument> domDocument;
/* DOMオブジェクトの作成 */
hr = CoCreateInstance(
CLSID_DOMDocument60, NULL, CLSCTX_INPROC_SERVER,
IID_IXMLDOMDocument, reinterpret_cast<void **>(static_cast<IXMLDOMDocument**>(domDocument)));
/* 各種設定 */
hr = domDocument->put_async(VARIANT_FALSE);
hr = domDocument->put_validateOnParse(VARIANT_FALSE);
hr = domDocument->put_resolveExternals(VARIANT_FALSE);
/* 読み込む */
auto bstrFileName = SysAllocString(lpszFileNameW);
VARIANT varFileName;
V_VT(&varFileName) = VT_BSTR;
V_BSTR(&varFileName) = bstrFileName;
VARIANT_BOOL varStatus;
hr = domDocument->load(varFileName, &varStatus);
VariantClear(&varFileName);
return domDocument;
}
auto main (void) -> int
{
// COMの初期化と後始末
struct ComInitializer
{
ComInitializer () { CoInitialize(nullptr); }
~ComInitializer () { CoUninitialize(); }
} ComInitializerObj;
// XMLの読み込み
TCHAR xmlPath[MAX_PATH];
getXmlFileName(xmlPath);
ComPtr<IXMLDOMDocument> domDocument = loadXml(xmlPath);
// XPathによる絞り込み
ComPtr<IXMLDOMNodeList> domNodeList;
HRESULT hr;
hr = domDocument->selectNodes(L"/books/book/title/text()", domNodeList);
// 絞り込んだ要素の列挙
long count;
hr = domNodeList->get_length(&count);
for (auto i = 0L; i < count; ++i)
{
ComPtr<IXMLDOMNode> domNode;
hr = domNodeList->get_item(i, domNode);
BSTR text;
hr = domNode->get_text(&text);
DWORD dwWritten;
// 出力
#ifdef UNICODE
WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), text, SysStringLen(text), &dwWritten, NULL);
#else
// 文字コード変換
{
char * buffer;
int cstrLength;
cstrLength = WideCharToMultiByte(CP_ACP, 0, text, SysStringLen(text), NULL, 0, NULL, NULL) + 1;
buffer = (char *)HeapAlloc(GetProcessHeap(), 0, cstrLength);
buffer[cstrLength - 1] = 0;
WideCharToMultiByte(CP_ACP, 0, text, SysStringLen(text), buffer, cstrLength, NULL, NULL);
WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), buffer, lstrlen(buffer), &dwWritten, NULL);
HeapFree(GetProcessHeap(), 0, buffer);
}
#endif
SysFreeString(text);
WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), TEXT("\r\n"), 2, &dwWritten, NULL);
}
return 0;
}
スマートポインタにT**への変換は邪道な気もしますが,コードが楽になるので書いてしまっています。
# .NETのマーシャリングのように,HRESULTと出力引数から失敗時は例外,戻り値が出力引数,みたいなことができるとコードは簡易になるのだけれど……。
なお,C/C++どちらのコードもエラー処理を一切していません。
実コードでは,エラー処理をする必要があります。
Re: XPathを使うべきか それともDOM?
Posted: 2012年10月22日(月) 10:08
by らん
皆さん親切なご指導ありがとうございます。
コードまで示されて、非常に勉強になります。
ただ、自分も調べてみましたが、
「C++/CLI で作っているのであれば、System.Xml.XmlDocument を使えばよい、
そうではない場合は、MSXMLを検討するのがよい」という表現もありまして、
ご解説お願いできますか。
またどうぞ宜しくお願いします。
Re: XPathを使うべきか それともDOM?
Posted: 2012年10月22日(月) 10:32
by softya(ソフト屋)
らん さんが書きました:皆さん親切なご指導ありがとうございます。
コードまで示されて、非常に勉強になります。
ただ、自分も調べてみましたが、
「C++/CLI で作っているのであれば、System.Xml.XmlDocument を使えばよい、
そうではない場合は、MSXMLを検討するのがよい」という表現もありまして、
ご解説お願いできますか。
またどうぞ宜しくお願いします。
C++/CLIはC++の.NET版でC++を更に拡張した言語です。
らんさんは、使う言語をC言語かC++かC++/CLIを決めなければいけません。後ろに行くほど難しい言語です。
ちなみに、C言語であればMSXMLで構わないと思います。
※ 私がMSXMLを使っていないのは古いOSのバージョンでMSXMLを使うのは問題があったから。
Re: XPathを使うべきか それともDOM?
Posted: 2012年10月22日(月) 12:52
by YuO
らん さんが書きました:「C++/CLI で作っているのであれば、System.Xml.XmlDocument を使えばよい、
そうではない場合は、MSXMLを検討するのがよい」という表現もありまして、
C++/CLI なら確かに XmlDocument の方が楽なんですが (COM まわりのあれやこれやが全部不要になる),根本的に C++/CLI を使う必要性がほとんど無いです。
C++/CLI 単体 .NET Framework のアプリケーションを書くならば,圧倒的に C# や Visual Basic のほうが楽ですし,Windows API などは Platform 呼び出し (P/Invoke) があるので C++/CLI の出番は普通ありません。
既存資産があっても DLL 化すれば P/Invoke が使えるのでそれで大抵は片が付きます。
速度を理由に一部を C or C++ で書きたい場合でも,結局は P/Invoke が使えます。
現実的には,C++/CLI が役に立つのは C# or VB と「非常に大きな既存資産」(P/Invoke の宣言が非常に膨大になる) 場合程度に限られるのではないかと。
# VS 2012 Express for Desktop からは C++ の Windows フォームアプリケーションのプロジェクトが消えましたし,Windows 8~の WinRT では C++/CX という別の拡張を使うことになります。
Re: XPathを使うべきか それともDOM?
Posted: 2012年10月22日(月) 16:47
by らん
みなさん素晴らしいご回答本当にありがとうございました!