Stating the Problem
You will continue with the application you wrote during the second part of the tutorial. You have a store where books and CDs are sold. You keep information about the items in a XML document that is parsed with SAX. The XML document you used is:
<?xml version="1.0" encoding="utf-8"?>
<store>
<book isbn="10000001">
<title>The Lord Of The Rings</title>
<author>J.R.R. Tolkien</author>
</book>
<book>
<title>Maitreyi</title>
<author>Mircea Eliade</author>
</book>
<cd>
<title>The Wall</title>
<artist>Pink Floyd</artist>
<track length="3:40">Another Brick in the Wall</track>
<track length="5:33">Mother</track>
</cd>
<cd>
<title>Come on Over</title>
<artist>Shania Twain</artist>
<track length="4:40">From This Moment On</track>
<track length="3:33">You're Still The One</track>
</cd>
</store>
But now, you will introduce an error in this file. For instance, change the closing tag of the last track:
<cd>
<title>Come on Over</title>
<artist>Shania Twain</artist>
<track length="4:40">From This Moment On</track>
<track length="3:33">You're Still The One</track_error>
</cd>
With this, the element is no longer valid; thus, the entire file is corrupted. However, you want to keep and display all the information you successfully read before the error occurred.
Approaching the Problem
In the first two articles, the content handlers contained a stack where objects were pushed as the startElement() event was fired and popped out when the endElement() event occurred. But, if an error occurred, the parsing was aborted and you didn't take any care of cleaning up the stack, thus ending up with memory leaks. Obviously, that's not acceptable.
In this implementation, you continue to work with the Book, CD, Track, Store, SAXContentHandlerImpl, and SAXErrorHandlerImpl classes from the previous articles. However, you will add a pure virtual method to SAXContentHandlerImpl to notify the handler that a fatal error has occurred.
virtual void FatalError() = 0;
Basically, you will use the same SAXBooks, SAXCDs, and SAXTracks content handlers. They will follow the same logic as in the previous article, but their stack implementation will be slightly different and they also will implement the FatalError() method.
To ease the stack operation (especially the cleaning up), you will work with a custom stack:
// types of all elements
enum ElemType {SStore, SBook, SCD, STrack, SWString};
// element of the stack
struct StackElement
{
StackElement(void* element, ElemType elemtype):
type(elemtype), data(element)
{
}
ElemType type;
void* data;
};
// stack defines
class SAXStack
{
std::stack<StackElement> elements;
public:
void push(const StackElement& value)
{
elements.push(value);
}
StackElement& top()
{
return elements.top();
}
void pop()
{
elements.pop();
}
void empty();
};
SAXStack is based on std::stack, with elements of type StackElement. It's the same structure you used in the first article. SAXStack provides operations for pushing a new element into the stack, popping the element at top, accessing the top element, and emptying the stack. The method that takes all the elements out of the stack also takes care of deleting allocated memory.
void SAXStack::empty()
{
while(!elements.empty())
{
StackElement elem = elements.top();
elements.pop();
switch(elem.type)
{
case SStore: delete (Store*)elem.data; break;
case SBook: delete (Book*)elem.data; break;
case SCD: delete (CD*)elem.data; break;
case STrack: delete (Track*)elem.data; break;
case SWString: delete (std::wstring*)elem.data; break;
}
}
}
The new implementation of SAXBooks looks like this:
class SAXBooks :
public SAXContentHandlerImpl
{
std::vector<Book> books;
SAXStack stack;
bool hasText;
long m_RefCount;
public:
SAXBooks(void);
virtual ~SAXBooks(void);
unsigned long __stdcall AddRef(void)
{
return InterlockedIncrement(&m_RefCount);
}
unsigned long __stdcall Release(void)
{
long nRefCount=0;
nRefCount=InterlockedDecrement(&m_RefCount) ;
if (nRefCount == 0) delete this;
return nRefCount;
}
std::vector<Book> GetBooks() const {return books;}
virtual HRESULT STDMETHODCALLTYPE startElement(
wchar_t __RPC_FAR *pwchNamespaceUri,
int cchNamespaceUri,
wchar_t __RPC_FAR *pwchLocalName,
int cchLocalName,
wchar_t __RPC_FAR *pwchRawName,
int cchRawName,
ISAXAttributes __RPC_FAR *pAttributes);
virtual HRESULT STDMETHODCALLTYPE endElement(
wchar_t __RPC_FAR *pwchNamespaceUri,
int cchNamespaceUri,
wchar_t __RPC_FAR *pwchLocalName,
int cchLocalName,
wchar_t __RPC_FAR *pwchRawName,
int cchRawName);
virtual HRESULT STDMETHODCALLTYPE characters(
wchar_t __RPC_FAR *pwchChars,
int cchChars);
virtual void FatalError();
};
SAXBooks::SAXBooks(void)
:hasText(false),
m_RefCount(0)
{
}
SAXBooks::~SAXBooks(void)
{
}
HRESULT STDMETHODCALLTYPE SAXBooks::startElement(
wchar_t __RPC_FAR *pwchNamespaceUri,
int cchNamespaceUri,
wchar_t __RPC_FAR *pwchLocalName,
int cchLocalName,
wchar_t __RPC_FAR *pwchRawName,
int cchRawName,
ISAXAttributes __RPC_FAR *pAttributes)
{
hasText = false;
std::wstring localName(pwchLocalName);
if(localName.compare(L"book") == 0)
{
stack.push(StackElement(
new Book(GetAttributeValue(pAttributes, L"isbn", L"unknown")),
SBook));
}
else if(localName.compare(L"title") == 0 ||
localName.compare(L"author") == 0)
{
stack.push(StackElement(new std::wstring(), SWString));
hasText = true;
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE SAXBooks::endElement(
wchar_t __RPC_FAR *pwchNamespaceUri,
int cchNamespaceUri,
wchar_t __RPC_FAR *pwchLocalName,
int cchLocalName,
wchar_t __RPC_FAR *pwchRawName,
int cchRawName)
{
hasText = false;
// take the data from the stack's top
StackElement elem = stack.top();
// pop the element at the top
stack.pop();
std::wstring localName(pwchLocalName);
if(localName.compare(L"book") == 0)
{
books.push_back(*(Book*)elem.data);
delete (Book*)elem.data;
}
else if(localName.compare(L"title") == 0)
{
((Book*)stack.top().data)->SetTitle(*(std::wstring*)elem.data);
delete (std::wstring*)elem.data;
}
else if(localName.compare(L"author") == 0)
{
((Book*)stack.top().data)->SetAuthor(*(std::wstring*)elem.data);
delete (std::wstring*)elem.data;
}
else
{
// put data back to the stack
stack.push(elem);
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE SAXBooks::characters(
wchar_t __RPC_FAR *pwchChars,
int cchChars)
{
if(hasText)
{
// append current text to the existing text
std::wstring* top = (std::wstring*)stack.top().data;
std::wstring text(pwchChars, cchChars);
*top += text;
}
else
{
// read data that is not part of recognized element
}
return S_OK;
}
void SAXBooks::FatalError()
{
stack.empty();
}
Notice that you handle the events and work with the stack in the same manner. But, when you are notified that an error has occurred, you must clean up the stack, but you don't remove any of the successfully parsed elements (those from books).
For SAXCDs and SAXTracks, look into the sample application files.
The SAXMultiContentHandler is the same from the previous article, but being a content handler, it also implements the FatalError() interface by notifying all the registered handlers or the error occurrence.
void SAXMultiContentHandler::FatalError()
{
for(std::vector<ElementHandler>::iterator it =
handlers.begin();
it != handlers.end(); ++it)
{
(*it).handler->FatalError();
}
}
Error Handler Implementation
You will derive a handler from SAXErrorHandlerImpl. It will implement only the fatalError() interface because it's the only one that is called. The error handler keeps a pointer to a SAXMultiContentHandler object and will notify it when an error occurs. The notification then will be dispatched to all the handlers that have registered to the multi content handler. This way, you are sure that all handlers that are interested in the content of the document are notified about the error and can perform cleanup.
class SAXMultiErrorHandler :
public SAXErrorHandlerImpl
{
SAXMultiContentHandler* handler;
public:
SAXMultiErrorHandler(SAXMultiContentHandler* hdl);
virtual ~SAXMultiErrorHandler(void);
virtual HRESULT STDMETHODCALLTYPE fatalError(
ISAXLocator __RPC_FAR *pLocator,
wchar_t * pwchErrorMessage,
HRESULT errCode);
};
SAXMultiErrorHandler::SAXMultiErrorHandler(SAXMultiContentHandler* hdl):
handler(hdl)
{
}
SAXMultiErrorHandler::~SAXMultiErrorHandler(void)
{
}
HRESULT STDMETHODCALLTYPE SAXMultiErrorHandler::fatalError(
ISAXLocator __RPC_FAR *pLocator,
wchar_t * pwchErrorMessage,
HRESULT errCode)
{
// get information about the file, line, and column where error
// occurred
int errorLine = -1;
int errorCol = -1;
wchar_t *filepath[MAX_PATH+1];
pLocator->getLineNumber(&errorLine);
pLocator->getColumnNumber(&errorCol);
pLocator->getSystemId(filepath);
std::wstring errorText(pwchErrorMessage);
// print an error message
std::wcout << *filepath << L"(line " << errorLine
<< L", column " << errorCol "" L"): error "
"" std::hex << errCode
<< L": " << errorText << std::endl;
// notify the multi content handler about the error
handler->FatalError();
return E_FAIL;
}
When fatalError() gets called, you use the ISAXLocator interface to get information about the document, line, and column where the error occurred. You also have an error code identifying the error reason and a textual description of the error. All that information is displayed on the console. After notifying the content handler, the function returns E_FAIL, indicating that the parsing should stop.
Comments
There are no comments yet. Be the first to comment!