456 lines
13 KiB
C++
456 lines
13 KiB
C++
|
/*
|
||
|
Copyright 1999 Microsoft Corporation
|
||
|
|
||
|
Simplified XML "DOM"
|
||
|
|
||
|
Walter Smith (wsmith)
|
||
|
Rajesh Soy (nsoy) - modified - 4/13/2000
|
||
|
*/
|
||
|
|
||
|
#ifdef THIS_FILE
|
||
|
#undef THIS_FILE
|
||
|
#endif
|
||
|
|
||
|
static char __szTraceSourceFile[] = __FILE__;
|
||
|
#define THIS_FILE __szTraceSourceFile
|
||
|
|
||
|
|
||
|
#include "stdafx.h"
|
||
|
|
||
|
#define NOTRACE
|
||
|
|
||
|
#include "simplexml.h"
|
||
|
|
||
|
#ifdef SIMPLEXML_WANTPARSER
|
||
|
|
||
|
class __declspec(uuid("7887F5E7-640C-45A5-B98B-1E8645E2F7BA")) DataParser;
|
||
|
interface __declspec(uuid("9EFEB013-4E9C-42aa-8354-C5508E352346")) IDataParser;
|
||
|
|
||
|
struct IDataParser : public IUnknown {
|
||
|
virtual HRESULT STDMETHODCALLTYPE GetTopNode(/*OUT*/ SimpleXMLNode** ppNode) = 0;
|
||
|
};
|
||
|
|
||
|
class ATL_NO_VTABLE DataParser :
|
||
|
public CComObjectRoot,
|
||
|
public CComCoClass<DataParser, &__uuidof(DataParser)>,
|
||
|
public IXMLNodeFactory,
|
||
|
public IDataParser
|
||
|
{
|
||
|
public:
|
||
|
|
||
|
public:
|
||
|
DataParser()
|
||
|
{
|
||
|
m_pNodeHolder = auto_ptr<SimpleXMLNode>(new SimpleXMLNode);
|
||
|
}
|
||
|
|
||
|
DECLARE_PROTECT_FINAL_CONSTRUCT()
|
||
|
|
||
|
BEGIN_COM_MAP(DataParser)
|
||
|
COM_INTERFACE_ENTRY(IXMLNodeFactory)
|
||
|
COM_INTERFACE_ENTRY(IDataParser)
|
||
|
END_COM_MAP()
|
||
|
|
||
|
//private:
|
||
|
public:
|
||
|
auto_ptr<SimpleXMLNode> m_pNodeHolder;
|
||
|
|
||
|
public:
|
||
|
// IXMLNodeFactory
|
||
|
|
||
|
STDMETHOD(NotifyEvent)(IXMLNodeSource* pSource,
|
||
|
XML_NODEFACTORY_EVENT iEvt);
|
||
|
|
||
|
STDMETHOD(BeginChildren)(IXMLNodeSource* pSource,
|
||
|
XML_NODE_INFO* pNodeInfo);
|
||
|
|
||
|
STDMETHOD(EndChildren)(IXMLNodeSource* pSource,
|
||
|
BOOL fEmpty,
|
||
|
XML_NODE_INFO* pNodeInfo);
|
||
|
|
||
|
STDMETHOD(Error)(IXMLNodeSource* pSource,
|
||
|
HRESULT hrErrorCode,
|
||
|
USHORT cNumRecs,
|
||
|
XML_NODE_INFO** aNodeInfo);
|
||
|
|
||
|
STDMETHOD(CreateNode)(IXMLNodeSource* pSource,
|
||
|
PVOID pNodeParent,
|
||
|
USHORT cNumRecs,
|
||
|
XML_NODE_INFO** aNodeInfo);
|
||
|
|
||
|
// IDataParser
|
||
|
|
||
|
// Get a pointer to the "node holder" (a node whose children
|
||
|
// are the parsed document) -- caller gets ownership of the
|
||
|
// pointer and must delete when finished.
|
||
|
|
||
|
STDMETHOD(GetTopNode)(/*OUT*/ SimpleXMLNode** ppNode)
|
||
|
{
|
||
|
*ppNode = m_pNodeHolder.get();
|
||
|
m_pNodeHolder.release();
|
||
|
return S_OK;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
HRESULT DataParser::NotifyEvent(IXMLNodeSource* pSource,
|
||
|
XML_NODEFACTORY_EVENT iEvt)
|
||
|
{
|
||
|
switch (iEvt) {
|
||
|
case XMLNF_STARTDOCUMENT:
|
||
|
{
|
||
|
CComQIPtr<IXMLParser> pParser(pSource);
|
||
|
ThrowIfTrue(!pParser);
|
||
|
|
||
|
pParser->SetRoot(m_pNodeHolder.get());
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
HRESULT DataParser::BeginChildren(IXMLNodeSource* pSource,
|
||
|
XML_NODE_INFO* pNodeInfo)
|
||
|
{
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
HRESULT DataParser::EndChildren(IXMLNodeSource* pSource,
|
||
|
BOOL fEmpty,
|
||
|
XML_NODE_INFO* pNodeInfo)
|
||
|
{
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
HRESULT DataParser::Error(IXMLNodeSource* pSource,
|
||
|
HRESULT hrErrorCode,
|
||
|
USHORT cNumRecs,
|
||
|
XML_NODE_INFO** aNodeInfo)
|
||
|
{
|
||
|
throw hrErrorCode;
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
HRESULT DataParser::CreateNode(IXMLNodeSource* pSource,
|
||
|
PVOID pNodeParent,
|
||
|
USHORT cNumRecs,
|
||
|
XML_NODE_INFO** aNodeInfo)
|
||
|
{
|
||
|
switch (aNodeInfo[0]->dwType) {
|
||
|
case XML_ELEMENT:
|
||
|
{
|
||
|
// Make a new node and add it to the parent node
|
||
|
|
||
|
XML_NODE_INFO* pTagInfo = aNodeInfo[0];
|
||
|
wstring tag(pTagInfo->pwcText, pTagInfo->ulLen);
|
||
|
SimpleXMLNode* pNode = ((SimpleXMLNode*) pNodeParent)->AppendChild(tag);
|
||
|
pTagInfo->pNode = pNode;
|
||
|
|
||
|
// Collect attributes, if any
|
||
|
|
||
|
if (cNumRecs > 1) {
|
||
|
wstring sName;
|
||
|
wstring sValue;
|
||
|
|
||
|
for (int i = 1; i < cNumRecs; i++) {
|
||
|
XML_NODE_INFO* pInfo = aNodeInfo[i];
|
||
|
|
||
|
switch (pInfo->dwType) {
|
||
|
case XML_ATTRIBUTE:
|
||
|
if (!sName.empty()) {
|
||
|
pNode->SetAttribute(sName, sValue);
|
||
|
sName.erase();
|
||
|
sValue.erase();
|
||
|
}
|
||
|
sName = wstring(pInfo->pwcText, pInfo->ulLen);
|
||
|
break;
|
||
|
|
||
|
case XML_PCDATA:
|
||
|
sValue.append(pInfo->pwcText, pInfo->ulLen);
|
||
|
break;
|
||
|
|
||
|
case XML_ENTITYREF:
|
||
|
// We don't deal with non-predefined entities, so if we see
|
||
|
// an ENTITYREF we're guaranteed to parse incorrectly.
|
||
|
throw E_FAIL;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!sName.empty()) {
|
||
|
pNode->SetAttribute(sName, sValue);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case XML_PCDATA:
|
||
|
{
|
||
|
_ASSERT(pNodeParent != NULL);
|
||
|
((SimpleXMLNode*) pNodeParent)->text.append(aNodeInfo[0]->pwcText, aNodeInfo[0]->ulLen);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
class FileMapping {
|
||
|
public:
|
||
|
FileMapping()
|
||
|
: hFile(NULL), hMapping(NULL), pView(NULL)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
~FileMapping()
|
||
|
{
|
||
|
if (pView != NULL)
|
||
|
UnmapViewOfFile(pView);
|
||
|
if (hMapping != NULL)
|
||
|
CloseHandle(hMapping);
|
||
|
if (hFile != NULL)
|
||
|
CloseHandle(hFile);
|
||
|
}
|
||
|
|
||
|
void Open(LPCTSTR szFile)
|
||
|
{
|
||
|
// Sometimes the file is still in use by the upload library when we find
|
||
|
// it in the directory -- not sure how this can be true, but it seems to
|
||
|
// be short-lived, so we just give it a chance to finish up and retry a
|
||
|
// couple of times.
|
||
|
TraceFunctEnter(_T("FileMapping"));
|
||
|
for (int tries = 0; tries < 3; tries++) {
|
||
|
hFile = CreateFile(szFile,
|
||
|
GENERIC_READ,
|
||
|
FILE_SHARE_READ,
|
||
|
NULL,
|
||
|
OPEN_EXISTING,
|
||
|
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
|
||
|
// BUGBUG: use FILE_FLAG_DELETE_ON_CLOSE when this is working OK
|
||
|
NULL);
|
||
|
if (hFile != INVALID_HANDLE_VALUE)
|
||
|
break;
|
||
|
Sleep(50);
|
||
|
}
|
||
|
ThrowIfTrue(hFile == INVALID_HANDLE_VALUE);
|
||
|
_RPT1(_CRT_WARN, "CreateFile: %d retries\n", tries);
|
||
|
|
||
|
hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
|
||
|
ThrowIfTrue(hMapping == INVALID_HANDLE_VALUE);
|
||
|
|
||
|
pView = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
|
||
|
TraceFunctLeave();
|
||
|
}
|
||
|
|
||
|
PVOID GetDataPtr()
|
||
|
{
|
||
|
_ASSERT(pView != NULL);
|
||
|
return pView;
|
||
|
}
|
||
|
|
||
|
DWORD GetSize()
|
||
|
{
|
||
|
_ASSERT(hFile != NULL);
|
||
|
return GetFileSize(hFile, NULL);
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
HANDLE hFile;
|
||
|
HANDLE hMapping;
|
||
|
PVOID pView;
|
||
|
};
|
||
|
|
||
|
void DumpNodes(SimpleXMLNode* pNode, int indent = 4)
|
||
|
{
|
||
|
for (vector< auto_ptr<SimpleXMLNode> >::const_iterator it = pNode->children.begin();it != pNode->children.end();it++)
|
||
|
{
|
||
|
_RPT3(_CRT_WARN, "%*s<%ls", indent, _T(""), (*it)->tag.c_str());
|
||
|
for (SimpleXMLNode::attrs_t::const_iterator itattr = (*it)->attrs.begin();itattr != (*it)->attrs.end();itattr++)
|
||
|
{
|
||
|
_RPT2(_CRT_WARN, " %ls=\"%ls\"", (*itattr).first.c_str(), (*itattr).second.c_str());
|
||
|
}
|
||
|
_RPT0(_CRT_WARN, ">\n");
|
||
|
|
||
|
DumpNodes((*it).get(), indent + 4);
|
||
|
|
||
|
if (!(*it)->text.empty())
|
||
|
{
|
||
|
_RPT1(_CRT_WARN, "%ls", (*it)->text.c_str());
|
||
|
}
|
||
|
|
||
|
_RPT3(_CRT_WARN, "%*s</%ls>\n", indent, _T(""), (*it)->tag.c_str());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void SimpleXMLDocument::ParseFile(LPCTSTR szFilename)
|
||
|
{
|
||
|
TraceFunctEnter(_T("SimpleXMLDocument::ParseFile"));
|
||
|
DebugTrace(TRACE_ID, _T("Processing: %s"), szFilename);
|
||
|
|
||
|
_RPTF1(_CRT_WARN, "Processing %s\n", szFilename);
|
||
|
|
||
|
CComPtr<IXMLParser> pParser;
|
||
|
ThrowIfFail( pParser.CoCreateInstance(__uuidof(XMLParser)) );
|
||
|
|
||
|
_ASSERT(_CrtCheckMemory());
|
||
|
|
||
|
CComPtr<IXMLNodeFactory> pFactory;
|
||
|
DataParser::CreateInstance(&pFactory);
|
||
|
|
||
|
_ASSERT(_CrtCheckMemory());
|
||
|
|
||
|
ThrowIfFail( pParser->SetFactory(pFactory) );
|
||
|
|
||
|
ThrowIfFail( pParser->SetFlags(XMLFLAG_NOWHITESPACE) ) ;
|
||
|
|
||
|
FileMapping mapping;
|
||
|
mapping.Open(szFilename);
|
||
|
|
||
|
ThrowIfFail( pParser->PushData((const char*) mapping.GetDataPtr(), mapping.GetSize(), TRUE) );
|
||
|
|
||
|
ThrowIfFail( pParser->Run(-1) );
|
||
|
|
||
|
pParser.Release();
|
||
|
|
||
|
CComQIPtr<IDataParser> pFactoryPrivate(pFactory);
|
||
|
|
||
|
// We get ownership of the parser's node -- we carefully pass it
|
||
|
// along to m_pTopNode (not mixing auto_ptrs and real ptrs would
|
||
|
// probably be the idiomatic way to do this).
|
||
|
SimpleXMLNode* pTopNode;
|
||
|
pFactoryPrivate->GetTopNode(&pTopNode);
|
||
|
m_pTopNode = auto_ptr<SimpleXMLNode>(pTopNode);
|
||
|
|
||
|
// DumpNodes(m_pTopNode.get());
|
||
|
|
||
|
pFactory.Release();
|
||
|
TraceFunctLeave();
|
||
|
}
|
||
|
|
||
|
#endif // SIMPLEXML_WANTPARSER
|
||
|
|
||
|
// This is ATL's stuff slightly modified to pass CP_UTF8 to WideCharToMultiByte
|
||
|
|
||
|
inline LPSTR WINAPI W2Helper(LPSTR lpa, LPCWSTR lpw, int nChars, UINT acp)
|
||
|
{
|
||
|
ATLASSERT(lpw != NULL);
|
||
|
ATLASSERT(lpa != NULL);
|
||
|
// verify that no illegal character present
|
||
|
// since lpa was allocated based on the size of lpw
|
||
|
// don't worry about the number of chars
|
||
|
lpa[0] = '\0';
|
||
|
WideCharToMultiByte(acp, 0, lpw, -1, lpa, nChars, NULL, NULL);
|
||
|
return lpa;
|
||
|
}
|
||
|
|
||
|
#define W2UTF8(lpw) (\
|
||
|
((_lpw = lpw) == NULL) ? NULL : (\
|
||
|
_convert = (lstrlenW(_lpw)+1)*2,\
|
||
|
W2Helper((LPSTR) alloca(_convert), _lpw, _convert, CP_UTF8)))
|
||
|
|
||
|
#define USES_UTF8_CONVERSION int _convert = 0; _convert; LPCWSTR _lpw = NULL; _lpw; LPCSTR _lpa = NULL; _lpa
|
||
|
|
||
|
void OutUTF8String(ostream& out, LPCWSTR wstr, int nChars)
|
||
|
{
|
||
|
_ASSERT(wstr != NULL);
|
||
|
|
||
|
int nBuf = (nChars + 1) * 3; // UTF-8 can convert one WCHAR into 3 bytes
|
||
|
char* buf;
|
||
|
if (nBuf >= 1024)
|
||
|
buf = (char*) HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, nBuf);
|
||
|
else
|
||
|
buf = (char*) alloca(nBuf);
|
||
|
int nWritten = WideCharToMultiByte(CP_UTF8, 0, wstr, nChars, buf, nBuf, NULL, NULL);
|
||
|
// REVIEW: WideCharToMultiByte is documented as writing a null terminator to buf,
|
||
|
// but it doesn't seem to be doing it.
|
||
|
buf[nWritten] = 0;
|
||
|
out << buf;
|
||
|
if (nBuf >= 1024)
|
||
|
HeapFree(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, buf);
|
||
|
}
|
||
|
|
||
|
void OutXMLString(ostream& out, const wstring& str)
|
||
|
{
|
||
|
wstring::size_type iRemainder = 0;
|
||
|
wstring::size_type iSpecial = str.find_first_of(L"<>&'\"");
|
||
|
|
||
|
while (iSpecial < str.size()) {
|
||
|
if (iSpecial > iRemainder)
|
||
|
OutUTF8String(out, str.c_str() + iRemainder, iSpecial - iRemainder);
|
||
|
|
||
|
switch (str.at(iSpecial)) {
|
||
|
case '<': out << "<"; break;
|
||
|
case '>': out << ">"; break;
|
||
|
case '&': out << "&"; break;
|
||
|
case '\'': out << "'"; break;
|
||
|
case '"': out << """; break;
|
||
|
default: _ASSERT(0); break;
|
||
|
}
|
||
|
|
||
|
iRemainder = iSpecial + 1;
|
||
|
iSpecial = str.find_first_of(L"<>&'\"", iRemainder);
|
||
|
}
|
||
|
|
||
|
if (iRemainder < str.size())
|
||
|
OutUTF8String(out, str.c_str() + iRemainder, str.size() - iRemainder);
|
||
|
}
|
||
|
|
||
|
void SaveNodes(ostream& out, SimpleXMLNode* pNode)
|
||
|
{
|
||
|
USES_UTF8_CONVERSION;
|
||
|
|
||
|
for (vector< auto_ptr<SimpleXMLNode> >::const_iterator it = pNode->children.begin();
|
||
|
it != pNode->children.end();
|
||
|
it++) {
|
||
|
out << '<';
|
||
|
OutXMLString(out, (*it)->tag);
|
||
|
|
||
|
for (SimpleXMLNode::attrs_t::const_iterator itattr = (*it)->attrs.begin();
|
||
|
itattr != (*it)->attrs.end();
|
||
|
itattr++) {
|
||
|
out << ' ';
|
||
|
OutXMLString(out, (*itattr).first);
|
||
|
out << "=\"";
|
||
|
OutXMLString(out, (*itattr).second);
|
||
|
out << '"';
|
||
|
}
|
||
|
|
||
|
if ((*it)->children.empty() && (*it)->text.empty()) {
|
||
|
out << " />";
|
||
|
}
|
||
|
else {
|
||
|
out << ">";
|
||
|
|
||
|
SaveNodes(out, (*it).get());
|
||
|
|
||
|
if (!(*it)->text.empty())
|
||
|
OutXMLString(out, (*it)->text);
|
||
|
|
||
|
out << "</";
|
||
|
OutXMLString(out, (*it)->tag);
|
||
|
out << ">";
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SimpleXMLDocument::SaveFile(LPCTSTR szFilename) const
|
||
|
{
|
||
|
USES_CONVERSION;
|
||
|
|
||
|
ofstream out(T2CA(szFilename), ios::out | ios::trunc | ios::binary);
|
||
|
if (!out.is_open())
|
||
|
ThrowLastError();
|
||
|
|
||
|
out << "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n";
|
||
|
|
||
|
SaveNodes(out, m_pTopNode.get());
|
||
|
}
|