A worthwhile but minimal eXpat example usage
"Nick MacDonald" <[email protected]> Fri, 8 Aug 2008 14:41:41 -0400
| Newsgroups | gmane.text.xml.expat.general |
|---|---|
| Message-ID | <[email protected]> |
I have been using eXpat for a couple or more years, and I have been on this mailing list for much of that time. I notice that we seem to get asked some of the same questions over and over, and that for some reason the examples included with eXpat don't appear to be instructive enough. I wonder if I might supply the attached example program as a solution to those people who need a more fleshed out example. Its designed to find a pattern in the XML and extract a subset. Its deliberately kept simplified to avoid introducing too much complexity which would be distracting for a beginner. I would be perfectly happy to have this file included with eXpat or I would like a recommendation from others here on the list if there is something specific that would make it useful. I don't have my own page on which I can host it, but others may be willing to do so. Thanks, Nick _______________________________________________ Expat-discuss mailing list [email protected] http://mail.libexpat.org/mailman/listinfo/expat-discuss
eXpatExample.c
(application/octet-stream, 18 KB)
/*
This is an example XML parser implemented using eXpat.
This example parses the following XML file, and extracts all the
addresses of guys who's last name are Smith.
This code was written by:
Nick MacDonald
2008-Aug-07
This code was written and tested against eXpat 2.0.1.
This code is not bullet proof, because it is only a demo and too
much error checking would distract from the purpose of instruction.
XML can be pretty complictated to process, for example, what would
you expect from the following bit of input:
<Residence Type="Bungalow" SquareFeet="1025">
<Address>
123 Any Street
<Owner FirstName="Bob" LastName="Jones"/>
Bathurst, NB
</Address>
</Residence>
Note how there is a new tag right in the middle of the address. For
this particular program, this does not make a lot of sense, but it
does still parse correctly, but it adds an extra line break in the middle
of the address where the <Owner> tag is extacted. If you want your
program to handle anything that can be put in a valid XML file and thrown
at it, then you have a very complex design task ahead of you. I think
its probably better to focus on getting something to work and then work
on detecting all the rest that you can't handle.
This code is public domain, but I expect to receive proper credit
if you base your code on mine. This code was not written for any
purpose other than to be an example. This code is not sponsored
by me or by my employer--it comes with no warranty or support, nor
any promise to meet any particular purpose or function... you use
it at your sole risk.
This is the sample input file:
<?xml version="1.0"?>
<!-- This is a sample file to be parsed by eXpat as part of
example code written by Nick MacDonald.
The example program will extract the street addresses of
all guys who's last name is Smith.
-->
<AddressList>
<Residence Type="Bungalow" SquareFeet="1200">
<Owner LastName="Smith" FirstName="Tom"/>
<Address>
123 Any Street
Moncton, NB, Canada
</Address>
</Residence>
<Residence Type="Apt" SquareFeet="875">
<Owner LastName="Smythe" FirstName="Bill"/>
<Address>
402 Any Street
Apt. 28786
Chatham, NB, Canada
</Address>
</Residence>
<Residence Type="TwoStory" SquareFeet="2750">
<Owner LastName="Smith" FirstName="Danny"/>
<Address>
9876 Any Street
Saint John, NB, Canada
</Address>
</Residence>
</AddressList>
The output produced is:
Match found:
Name: Tom, Smith
Address:
123 Any Street
Moncton, NB, Canada
Match found:
Name: Danny, Smith
Address:
9876 Any Street
Saint John, NB, Canada
XML file parsing successful, 2 matches found for Smith
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "expat.h"
#define BUFFERSIZE 4*1024
#define MAX_ADDRESS 512
#define MAX_FIRST_NAME 32
#define MAX_LAST_NAME 32
#define MAX_STACK_DEPTH 10
// Elements of the <Residence Type="" SquareFeet=""> tag
#define XML_TAG_RESIDENCE "Residence"
#define XML_TAG_RESIDENCE_PARM_TYPE "Type"
#define XML_TAG_RESIDENCE_PARM_SQUAREFEET "SquareFeet"
// Elements of the <Owner FirstName="" LastName=""/> tag
#define XML_TAG_OWNER "Owner"
#define XML_TAG_OWNER_PARM_FIRSTNAME "FirstName"
#define XML_TAG_OWNER_PARM_LASTNAME "LastName"
// Elements of the <Address></Address> tag
#define XML_TAG_ADDRESS "Address"
/* When the XML parsing finds data that it is seeking, it
passes the data to a callback function of this type for
the processing (which is simply output in this example.)
*/
typedef void (*tCallbackToProcessFoundRecords)(
const char* ownerFirstName,
const char* ownerLastName,
const char* address
);
/* Set this to one to see extra debugging messages */
static int debugOutputEnabled =0;
/*
A pointer to one of these structures is passed into eXpat and then
along to any callback function calls it makes. This allows the
callback functions to have persistant state during the event based
parsing of the XML file.
*/
struct sXMLParseState
{
unsigned int numberMatchesFound;
int parsingResidenceTag;
int currentlyParsingAddress;
int lastNameMatches;
char lastNameToMatch[MAX_LAST_NAME];
char currentAddress[MAX_ADDRESS];
char currentFirstName[MAX_FIRST_NAME];
char currentLastName[MAX_LAST_NAME];
int errorEncountered;
tCallbackToProcessFoundRecords callbackToProcessFoundRecords;
XML_Parser eXpatXMLParser;
unsigned int stackPointer;
char* tagStack[MAX_STACK_DEPTH];
char* tagContent[MAX_STACK_DEPTH];
unsigned int tagContentSize[MAX_STACK_DEPTH];
};
/* A utility function to initialize the stack state
*/
static void initStack(
struct sXMLParseState* XMLParseState
)
{
unsigned int i;
XMLParseState->stackPointer =0;
for(i=0; i<MAX_STACK_DEPTH; i++)
{
XMLParseState->tagStack[i] =NULL;
XMLParseState->tagContent[i] =NULL;
XMLParseState->tagContentSize[i] =0;
}
}
/* A utility function to push (add) a new element onto the top of the stack
*/
static int pushTagOntoStack(
struct sXMLParseState* XMLParseState,
const char* tagToPush
)
{
int rv =0;
if (XMLParseState->stackPointer+1 < MAX_STACK_DEPTH)
{
XMLParseState->stackPointer++;
XMLParseState->tagStack[XMLParseState->stackPointer] =strdup(tagToPush);
rv =1;
}
else
{
fprintf(stderr, "%s: not enough room on stack for new tag (%s)\n",
__FUNCTION__, tagToPush);
}
return rv;
}
/* A utility function to pop (remove) an element from the top of the stack
*/
static void popTagFromStack(
struct sXMLParseState* XMLParseState
)
{
if (XMLParseState->stackPointer > 1)
{
if (NULL != XMLParseState->tagStack[XMLParseState->stackPointer])
{
free(XMLParseState->tagStack[XMLParseState->stackPointer]);
XMLParseState->tagStack[XMLParseState->stackPointer] =NULL;
}
if (NULL != XMLParseState->tagContent[XMLParseState->stackPointer])
{
free(XMLParseState->tagContent[XMLParseState->stackPointer]);
XMLParseState->tagContent[XMLParseState->stackPointer] =NULL;
}
XMLParseState->tagContentSize[XMLParseState->stackPointer] =0;
XMLParseState->stackPointer--;
}
}
/* A utility function to add data to the content being accumulated
in the current top of stack item. It is not optimized for any
memory management, so it assumes that the memory manager handles
malloc() and realloc() in a reasonably efficient way. If this is
not true, and there is a lot of memcpy()ing going on, then it should
be simple enough to add an extra element for current size, and make
initial malloc()s and subsequent realloc()s larger than needed to
reduce the number of reallocations that might occur.
*/
int addDataToCurrentlyStackedTag(
struct sXMLParseState* XMLParseState,
const char* textToAdd,
int lengthOfTextToAdd
)
{
int rv =0;
if (NULL == XMLParseState->tagContent[XMLParseState->stackPointer])
{
if (debugOutputEnabled)
{
printf("%s: lengthOfTextToAdd=%d\n", __FUNCTION__, lengthOfTextToAdd);
}
XMLParseState->tagContentSize[XMLParseState->stackPointer] =lengthOfTextToAdd;
XMLParseState->tagContent[XMLParseState->stackPointer] =malloc(lengthOfTextToAdd+1);
if (NULL != XMLParseState->tagContent[XMLParseState->stackPointer])
{
strncpy(XMLParseState->tagContent[XMLParseState->stackPointer], textToAdd, lengthOfTextToAdd);
XMLParseState->tagContent[XMLParseState->stackPointer][lengthOfTextToAdd] ='\0';
rv =1;
}
}
else
{
unsigned int oldLength =XMLParseState->tagContentSize[XMLParseState->stackPointer];
unsigned int newLength =oldLength+lengthOfTextToAdd;
XMLParseState->tagContentSize[XMLParseState->stackPointer] =newLength;
if (debugOutputEnabled)
{
printf("%s: oldLength=%u, lengthOfTextToAdd=%d newLength=%u\n", __FUNCTION__, oldLength, lengthOfTextToAdd, newLength);
}
XMLParseState->tagContent[XMLParseState->stackPointer] =realloc(XMLParseState->tagContent[XMLParseState->stackPointer], newLength+1);
if (NULL != XMLParseState->tagContent[XMLParseState->stackPointer])
{
strncpy(XMLParseState->tagContent[XMLParseState->stackPointer]+oldLength, textToAdd, lengthOfTextToAdd);
XMLParseState->tagContent[XMLParseState->stackPointer][newLength] ='\0';
rv =1;
}
}
return rv;
}
/* A utility function that returns a pointer to the data current
accumlated at the stop of the stack.
*/
static const char* getTopTagContentPtr(
struct sXMLParseState* XMLParseState
)
{
return XMLParseState->tagContent[XMLParseState->stackPointer];
}
/* A utility function that looks at the pairings of parameter tags
and values passed to the Start Tag callback, and looks for a
specific parameter to get its value.
*/
static const char* findParameter(
const char* parms[],
const char* parmToLookFor
)
{
const char* rv =NULL;
unsigned int i;
for(i=0; NULL != parms[i]; i+=2)
{
if (0 == strcmp(parms[i], parmToLookFor))
{
rv =parms[i+1];
break;
}
}
return rv;
}
/* Every time eXpat parses the start of a new XML tag, it
calls this callback to allow the code to update its internal
state, and determine what further processing might be
necessary
*/
static void XMLCALL startElementCallback(
void* userData,
const char* tagText,
const char** atts
)
{
struct sXMLParseState* XMLParseState =(struct sXMLParseState*)userData;
if (debugOutputEnabled)
{
printf("Matched start tag: %s\n", tagText);
printf("Seeking last name=%s\n", XMLParseState->lastNameToMatch);
}
if (!pushTagOntoStack(XMLParseState, tagText))
{
XMLParseState->errorEncountered =1;
}
else
{
if (0 == strcmp(tagText, XML_TAG_RESIDENCE))
{
// Starting a new <Residence> tag, so clear any previous state
XMLParseState->lastNameMatches =0;
memset(XMLParseState->currentAddress, '\0', sizeof(XMLParseState->currentAddress));
memset(XMLParseState->currentFirstName, '\0', sizeof(XMLParseState->currentFirstName));
memset(XMLParseState->currentLastName, '\0', sizeof(XMLParseState->currentLastName));
if (0 != XMLParseState->parsingResidenceTag)
{
fprintf(stderr, "Found nested Residence tag, which is not allowed\n");
XMLParseState->errorEncountered =1;
}
else
{
XMLParseState->parsingResidenceTag =1;
}
}
else
{
// The remaining tags of interest are only interesting when they are inside of
// a residence tag, so check that now
if (0 != XMLParseState->parsingResidenceTag)
{
if (0 == strcmp(tagText, XML_TAG_OWNER))
{
const char* firstName =NULL;
const char* lastName =NULL;
firstName =findParameter(atts, XML_TAG_OWNER_PARM_FIRSTNAME);
lastName =findParameter(atts, XML_TAG_OWNER_PARM_LASTNAME);
if ( (NULL != firstName) && (NULL != lastName) )
{
strncpy(XMLParseState->currentFirstName, firstName, MAX_FIRST_NAME);
strncpy(XMLParseState->currentLastName, lastName, MAX_LAST_NAME);
if (0 == strcmp(lastName, XMLParseState->lastNameToMatch))
{
XMLParseState->lastNameMatches =1;
}
}
}
if (0 == strcmp(tagText, XML_TAG_ADDRESS))
{
XMLParseState->currentlyParsingAddress =1;
}
}
}
}
if (0 != XMLParseState->errorEncountered)
{
XML_StopParser(XMLParseState->eXpatXMLParser, 0);
}
}
/* Every time eXpat parses the end of an XML tag, it calls
this callback to notify the code of the event. This
function tries to determine if the current state is
relevant to overall goal, and if so, makes a callback of
its own to process the extracted data/state.
*/
static void XMLCALL endElementCallback(
void* userData,
const char* tagText
)
{
struct sXMLParseState* XMLParseState =(struct sXMLParseState*)userData;
const char* currentTagContent =NULL;
if (debugOutputEnabled)
{
printf("Matched end tag: %s\n", tagText);
printf("Seeking last name=%s\n", XMLParseState->lastNameToMatch);
}
currentTagContent =getTopTagContentPtr(XMLParseState);
if (0 == strcmp(tagText, XML_TAG_RESIDENCE))
{
XMLParseState->parsingResidenceTag =0;
}
if (0 != XMLParseState->parsingResidenceTag)
{
if (0 == strcmp(tagText, XML_TAG_ADDRESS))
{
XMLParseState->currentlyParsingAddress =0;
strncpy(XMLParseState->currentAddress, currentTagContent, sizeof(XMLParseState->currentAddress));
if (0 != XMLParseState->lastNameMatches)
{
// Found a match for the type of record we're searching for
XMLParseState->numberMatchesFound++;
if (NULL != XMLParseState->callbackToProcessFoundRecords)
{
(*XMLParseState->callbackToProcessFoundRecords)( XMLParseState->currentLastName,
XMLParseState->currentFirstName,
XMLParseState->currentAddress );
}
}
}
}
popTagFromStack(XMLParseState);
if (0 != XMLParseState->errorEncountered)
{
XML_StopParser(XMLParseState->eXpatXMLParser, 0);
}
}
/* All the content/data that is between the XML tags is passed into
this callback. eXpat seems to make a number of small chunks of the
data, rather than larger blocks. The blocks are also not terminated
(with the usual C \0 terminator) so you need to handle the in some
way to concantenate them all together. Additionally, because tags
are legallaly allowed to nest in the middle of such content, you
need to implement a stack based approach to be able to collect the
data before and after an embedded tag and treat the data as one
complete block (if that is appropriate for your parsing needs.)
*/
static void XMLCALL elementTextCallback(
void* userData,
const char* text,
int len
)
{
struct sXMLParseState* XMLParseState =(struct sXMLParseState*)userData;
if (debugOutputEnabled)
{
printf("%s: len=%d\n", __FUNCTION__, len);
}
if (len > 0)
{
if (!addDataToCurrentlyStackedTag(XMLParseState, text, len)) XMLParseState->errorEncountered =1;
}
if (0 != XMLParseState->errorEncountered)
{
XML_StopParser(XMLParseState->eXpatXMLParser, 0);
}
}
/* When a record is found, this callback function (part of the
design of the example, not a fundemental part of the design
of eXpat itself) is called. In this simple case, it just
produces a printout of the found data.
*/
static void callbackToProcessFoundRecords(
const char* ownerFirstName,
const char* ownerLastName,
const char* address
)
{
fprintf(stdout, "Match found:\n");
fprintf(stdout, " Name: %s, %s\n", ownerLastName, ownerFirstName);
fprintf(stdout, " Address: %s\n", address);
}
/* Setup calls to eXpat to be able to find all the residences
owned by an owner with the last name of "Smith", and print
out the owners name, and the address of the residence.
*/
static void ParseXMLToExtractAddressesForLastName(
FILE* fileToParse,
const char* lastNameToExtractBy
)
{
char XMLFileReadBuffer[BUFFERSIZE];
int done =0;
XML_Parser XMLparser =NULL;
struct sXMLParseState XMLParseState;
memset(&XMLParseState, '\0', sizeof(XMLParseState));
memset(XMLFileReadBuffer, '\0', sizeof(XMLFileReadBuffer));
strncpy(XMLParseState.lastNameToMatch, lastNameToExtractBy, sizeof(XMLParseState.lastNameToMatch));
XMLParseState.parsingResidenceTag =0;
XMLParseState.currentlyParsingAddress =0;
XMLParseState.numberMatchesFound =0;
XMLParseState.errorEncountered =0;
XMLParseState.callbackToProcessFoundRecords =callbackToProcessFoundRecords;
initStack(&XMLParseState);
XMLparser =XML_ParserCreate(NULL);
if (NULL != XMLparser)
{
XMLParseState.eXpatXMLParser =XMLparser;
XML_SetUserData(XMLparser, &XMLParseState);
XML_SetElementHandler(XMLparser, startElementCallback, endElementCallback);
XML_SetCharacterDataHandler(XMLparser, elementTextCallback);
do
{
size_t len = fread(XMLFileReadBuffer, 1, sizeof(XMLFileReadBuffer), fileToParse);
done = len < sizeof(XMLFileReadBuffer);
if (XML_Parse(XMLparser, XMLFileReadBuffer, len, done) == XML_STATUS_ERROR)
{
fprintf(stderr,
"%s at line %ld\n",
XML_ErrorString(XML_GetErrorCode(XMLparser)),
XML_GetCurrentLineNumber(XMLparser));
XMLParseState.errorEncountered =1;
break;
}
} while (!done);
XML_ParserFree(XMLparser);
}
if (0 == XMLParseState.errorEncountered)
{
printf("XML file parsing successful, %u matches found for %s\n",
XMLParseState.numberMatchesFound, XMLParseState.lastNameToMatch);
}
}
/* This code was compiled as a standalone executable, so it needs a main() */
int main(int argc, char* argv[])
{
int rv =0;
FILE* xmlFileToWorkOn =NULL;
if (2 == argc)
{
xmlFileToWorkOn =fopen(argv[1], "r");
if (NULL != xmlFileToWorkOn)
{
ParseXMLToExtractAddressesForLastName( xmlFileToWorkOn, "Smith" );
fclose(xmlFileToWorkOn);
xmlFileToWorkOn =NULL;
}
else
{
fprintf(stderr, "unable to open XML file %s for read\n", argv[1]);
rv =2;
}
}
else
{
fprintf(stderr, "You must supply a parameter to indicate which XML file to parse\n");
rv =1;
}
return rv;
}