使用 XML DOM Document 3.0 的方法比較直觀但效率較差,簡易的範例如下
#include <msxml2.h>
int main()
{
CComPtr<IXMLDOMDocument> spXMLDOM;
HRESULT hr;
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if(FAILED(hr))
{
CoUninitialize();
}
hr = spXMLDOM.CoCreateInstance(CLSID_DOMDocument30, NULL, CLSCTX_INPROC_SERVER);
if(FAILED(hr))
{
CoUninitialize();
}
// 讀xml檔案路徑
hr = spXMLDOM->load(CComVariant(strFileSource), &bSuccess);
// 或使用以下方法讀xml字串
// hr = spXMLDOM->loadXML(CComBSTR(p_strXMLString), &vbSuccess);
// 以下為取得元素值
CString strValue;
CComBSTR bstrSS(_T("tagA"));
CComPtr<IXMLDOMNode> spXMLNode = NULL;
BSTR bstrXmlText;
if(SUCCEEDED(spXMLDOM->selectSingleNode(bstrSS, &spXMLNode)) && spXMLNode != NULL)
{
spXMLNode->get_text(&bstrXmlText);
strValue = bstrXmlText;
}
// 以下為取得元素屬性
CString strAttribute;
CComBSTR bstrSS(_T("tagA"));
CComQIPtr<IXMLDOMElement> spXMLChildElement;
VARIANT variantValue;
spXMLNode = NULL;
if(SUCCEEDED(m_spXMLDOM->selectSingleNode(bstrSS, &spXMLNode)) && spXMLNode != NULL)
{
spXMLChildElement = spXMLNode;
if(FAILED(spXMLChildElement->getAttribute(CComBSTR(_T("attributeA")), &variantValue)))
{
strAttribute = variantValue.bstrVal;
}
}
return TRUE;
}
另外一個方法是繼承 ISAXContentHandler 寫一個專用的 Parser,ISAXContentHandler 是一個純虛擬類別,我們必須 override 所有的 function,例如我們要 Parse 一個以下結構的 XML 檔案
<stocklist>
<stock>
<id>2412</id>
<name>中華電</name>
<close>60.8</close>
<price>61.4</price>
<volume>28697</volume>
</stock>
<stock>
<id>2454</id>
<name>聯發科</name>
<close>475</close>
<price>483</price>
<volume>18643</volume>
</stock>
</stocklist>
通常最重要的就是這三個function
HRESULT STDMETHODCALLTYPE CGetStockListParser::startElement(
/* [in] */ const wchar_t __RPC_FAR *pwchNamespaceUri,
/* [in] */ int cchNamespaceUri,
/* [in] */ const wchar_t __RPC_FAR *pwchLocalName,
/* [in] */ int cchLocalName,
/* [in] */ const wchar_t __RPC_FAR *pwchQName,
/* [in] */ int cchQName,
/* [in] */ ISAXAttributes __RPC_FAR *pAttributes)
HRESULT STDMETHODCALLTYPE CGetStockListParser::characters(
/* [in] */ const wchar_t __RPC_FAR *pwchChars,
/* [in] */ int cchChars)
HRESULT STDMETHODCALLTYPE CGetStockListParser::endElement(
/* [in] */ const wchar_t __RPC_FAR *pwchNamespaceUri,
/* [in] */ int cchNamespaceUri,
/* [in] */ const wchar_t __RPC_FAR *pwchLocalName,
/* [in] */ int cchLocalName,
/* [in] */ const wchar_t __RPC_FAR *pwchQName,
/* [in] */ int cchQName)
startElement 是用來指定收到一個起始tag時要做啥米事情,以上面的股市清單為例,通常這麼做
#define TAG_STOCK _T("stock")
m_strCurrentTag = CString( pwchLocalName, cchLocalName );
if ( m_strCurrentTag == TAG_STOCK )
{
m_pCurrentStockInfo = new CStockListItem();
}
而 characters 是用來指定收到 tag 的內容時要做的事,
#define TAG_STOCK_ID _T("id")
#define TAG_STOCK_NAME _T("name")
#define TAG_STOCK_CLOSE _T("close")
#define TAG_STOCK_PRICE _T("price")
#define TAG_STOCK_VOLUME _T("volume")
wchar_t* wchValue = (wchar_t*)calloc(cchChars + 1, sizeof(wchar_t));
wcsncpy( wchValue, pwchChars, cchChars );
CString strValue( wchValue );
free(wchValue);
if ( m_pCurrentStockInfo )
{
if ( m_strCurrentTag == TAG_STOCK_ID )
{
// 個股編號
m_strCurrentValue += strValue;
}
else if ( m_strCurrentTag == TAG_STOCK_NAME )
{
// 個股名稱
m_strCurrentValue += strValue;
}
else if ( m_strCurrentTag == TAG_STOCK_CLOSED )
{
// 昨日收盤價
m_strCurrentValue += strValue;
}
else if ( m_strCurrentTag == TAG_STOCK_PRICE )
{
// 交易價格
m_strCurrentValue += strValue;
}
else if ( m_strCurrentTag == TAG_STOCK_VOLUME )
{
// 累計交易量
m_strCurrentValue += strValue;
}
}
endElement 是用來指定收到結束 tag 時要做的事情
m_strCurrentTag = CString(); // 清空目前 tag 的紀錄
CString strTag =CString( pwchLocalName, cchLocalName );
if ( m_strCurrentTag == TAG_STOCK_ID )
{
// 個股編號
m_pCurrentStockInfo->SetID(m_strCurrentValue);
m_strCurrentValue.Empty();
}
else if ( m_strCurrentTag == TAG_STOCK_NAME )
{
// 個股名稱
m_pCurrentStockInfo->SetName(m_strCurrentValue);
m_strCurrentValue.Empty();
}
else if ( m_strCurrentTag == TAG_STOCK_CLOSED )
{
// 昨日收盤價
m_pCurrentStockInfo->SetClose(m_strCurrentValue);
m_strCurrentValue.Empty();
}
else if ( m_strCurrentTag == TAG_STOCK_PRICE )
{
// 交易價格
m_pCurrentStockInfo->SetPrice(m_strCurrentValue);
m_strCurrentValue.Empty();
}
else if ( m_strCurrentTag == TAG_STOCK_VOLUME )
{
// 累計交易量
m_pCurrentStockInfo->SetVolume(m_strCurrentValue);
m_strCurrentValue.Empty();
}
if( m_strCurrentTag == TAG_STOCK)
{
// 結束一筆完整的 StockItem
m_pRoot->AppendChild(m_pCurrentStockInfo);
m_pCurrentStockInfo = NULL;
}
其實整篇的重點在於 characters 這個 function 中,全部都使用 "+=" 來接 tag 中的內容,並且在 endElement 時將 m_strCurrentValue 清空,若使用 "=" 來接內容不就不用清空 m_strCurrentValue 了嗎?反正新的值在 assign 時自然就會把舊的值蓋掉了不是嗎?
不使用 "=" 的原因在於若 tag 的內容比較特殊,例如有 ()[] 等奇怪的字元,會以這種奇怪字元的個數為單位重覆呼叫 characters 數次,例如:"大家好(Hello)我叫小源源",就會呼叫數次 characters 此 function,明確的分法和次數不是重點,大概就是這樣
第一次:大家好(
第二次:Hello)
第三次:我叫小源源
所以才要在同一個 tag 的情況下用 += 來串接收到的值,否則會發生這個 tag 被 parse 出來的值只有最後收到的那次,由於是做用 "+=" 來串接,所以在收到 end tag 時也要記得把暫存字串清空嘍。
沒有留言:
張貼留言