Expat is a library, written in C, for parsing XML documents. It's the underlying
XML parser for the open source Mozilla project, Perl's XML::Parser,
Python's xml.parsers.expat, and other open-source XML parsers.
This library is the creation of James Clark, who's also given us groff (an nroff look-alike), Jade (an implementation of ISO's DSSSL stylesheet language for SGML), XP (a Java XML parser package), XT (a Java XSL engine). James was also the technical lead on the XML Working Group at W3C that produced the XML specification.
This is free software, licensed under the MIT/X Consortium license. You may download it from the Expat home page.
The bulk of this document was originally commissioned as an article by XML.com. They graciously allowed Clark Cooper to retain copyright and to distribute it with Expat. This version has been substantially extended to include documentation on features which have been added since the original article was published, and additional information on using the original interface.
Table of Contents
- Overview
- Building and Installing
- Using Expat
-
Reference
- Parser Creation Functions
- Parsing Functions
-
Handler Setting Functions
- XML_SetStartElementHandler
- XML_SetEndElementHandler
- XML_SetElementHandler
- XML_SetCharacterDataHandler
- XML_SetProcessingInstructionHandler
- XML_SetCommentHandler
- XML_SetStartCdataSectionHandler
- XML_SetEndCdataSectionHandler
- XML_SetCdataSectionHandler
- XML_SetDefaultHandler
- XML_SetDefaultHandlerExpand
- XML_SetExternalEntityRefHandler
- XML_SetExternalEntityRefHandlerArg
- XML_SetSkippedEntityHandler
- XML_SetUnknownEncodingHandler
- XML_SetStartNamespaceDeclHandler
- XML_SetEndNamespaceDeclHandler
- XML_SetNamespaceDeclHandler
- XML_SetXmlDeclHandler
- XML_SetStartDoctypeDeclHandler
- XML_SetEndDoctypeDeclHandler
- XML_SetDoctypeDeclHandler
- XML_SetElementDeclHandler
- XML_SetAttlistDeclHandler
- XML_SetEntityDeclHandler
- XML_SetUnparsedEntityDeclHandler
- XML_SetNotationDeclHandler
- XML_SetNotStandaloneHandler
- Parse Position and Error Reporting Functions
- Attack Protection
-
Miscellaneous Functions
- XML_SetUserData
- XML_GetUserData
- XML_UseParserAsHandlerArg
- XML_SetBase
- XML_GetBase
- XML_GetSpecifiedAttributeCount
- XML_GetIdAttributeIndex
- XML_GetAttributeInfo
- XML_SetEncoding
- XML_SetParamEntityParsing
- XML_SetHashSalt
- XML_UseForeignDTD
- XML_SetReturnNSTriplet
- XML_DefaultCurrent
- XML_ExpatVersion
- XML_ExpatVersionInfo
- XML_GetFeatureList
- XML_FreeContentModel
- XML_MemMalloc
- XML_MemRealloc
- XML_MemFree
Overview
Expat is a stream-oriented parser. You register callback (or handler) functions with the parser and then start feeding it the document. As the parser recognizes parts of the document, it will call the appropriate handler for that part (if you've registered one.) The document is fed to the parser in pieces, so you can start parsing before you have all the document. This also allows you to parse really huge documents that won't fit into memory.
Expat can be intimidating due to the many kinds of handlers and options you can set. But you only need to learn four functions in order to do 90% of what you'll want to do with it:
-
XML_ParserCreate - Create a new parser object.
-
XML_SetElementHandler - Set handlers for start and end tags.
-
XML_SetCharacterDataHandler - Set handler for text.
-
XML_Parse - Pass a buffer full of document to the parser
These functions and others are described in the reference part of this document. The reference section also describes in detail the parameters passed to the different types of handlers.
Let's look at a very simple example program that only uses 3 of the above
functions (it doesn't need to set a character handler.) The program outline.c prints an element outline, indenting child
elements to distinguish them from the parent element that contains them. The
start handler does all the work. It prints two indenting spaces for every level
of ancestor elements, then it prints the element and attribute information.
Finally it increments the global Depth variable.
int Depth;
void XMLCALL
start(void *data, const char *el, const char **attr) {
int i;
for (i = 0; i < Depth; i++)
printf(" ");
printf("%s", el);
for (i = 0; attr[i]; i += 2) {
printf(" %s='%s'", attr[i], attr[i + 1]);
}
printf("\n");
Depth++;
} /* End of start handler */
The end tag simply does the bookkeeping work of decrementing Depth.
void XMLCALL
end(void *data, const char *el) {
Depth--;
} /* End of end handler */
Note the XMLCALL annotation used for the callbacks. This is used to
ensure that the Expat and the callbacks are using the same calling convention in
case the compiler options used for Expat itself and the client code are
different. Expat tries not to care what the default calling convention is, though
it may require that it be compiled with a default convention of "cdecl" on some
platforms. For code which uses Expat, however, the calling convention is
specified by the XMLCALL annotation on most platforms; callbacks
should be defined using this annotation.
The XMLCALL annotation was added in Expat 1.95.7, but existing
working Expat applications don't need to add it (since they are already using the
"cdecl" calling convention, or they wouldn't be working). The annotation is only
needed if the default calling convention may be something other than "cdecl". To
use the annotation safely with older versions of Expat, you can conditionally
define it after including Expat's header file:
#include <expat.h> #ifndef XMLCALL #if defined(_MSC_VER) && !defined(__BEOS__) && !defined(__CYGWIN__) #define XMLCALL __cdecl #elif defined(__GNUC__) #define XMLCALL __attribute__((cdecl)) #else #define XMLCALL #endif #endif
After creating the parser, the main program just has the job of shoveling the document to the parser so that it can do its work.
Building and Installing Expat
The Expat distribution comes as a compressed (with GNU gzip) tar file. You may download the latest version from Source Forge. After unpacking this, cd into the directory. Then follow either the Win32 directions or Unix directions below.
Building under Win32
If you're using the GNU compiler under cygwin, follow the Unix directions in the
next section. Otherwise if you have Microsoft's Developer Studio installed, you
can use CMake to generate a .sln file, e.g. cmake -G"Visual
Studio 17 2022" -DCMAKE_BUILD_TYPE=RelWithDebInfo . , and build Expat
using msbuild /m expat.sln after.
Alternatively, you may download the Win32 binary package that contains the "expat.h" include file and a pre-built DLL.
Building under Unix (or GNU)
First you'll need to run the configure shell script in order to configure the Makefiles and headers for your system.
If you're happy with all the defaults that configure picks for you, and you have permission on your system to install into /usr/local, you can install Expat with this sequence of commands:
./configure make make install
There are some options that you can provide to this script, but the only one
we'll mention here is the --prefix option. You can find out all the
options available by running configure with just the --help option.
By default, the configure script sets things up so that the library gets
installed in /usr/local/lib and the associated header file in
/usr/local/include. But if you were to give the option,
--prefix=/home/me/mystuff, then the library and header would get
installed in /home/me/mystuff/lib and
/home/me/mystuff/include respectively.
Configuring Expat Using the Pre-Processor
Expat's feature set can be configured using a small number of pre-processor definitions. The symbols are:
- XML_GE
-
Added in Expat 2.6.0. Include support for general
entities (syntax
&e1;to reference and syntax<!ENTITY e1 'value1'>(an internal general entity) or<!ENTITY e2 SYSTEM 'file2'>(an external general entity) to declare). WithXML_GEenabled, general entities will be replaced by their declared replacement text; for this to work for external general entities, in addition anXML_ExternalEntityRefHandlermust be set usingXML_SetExternalEntityRefHandler. Also, enablingXML_GEmakes the functionsXML_SetBillionLaughsAttackProtectionMaximumAmplificationandXML_SetBillionLaughsAttackProtectionActivationThresholdavailable.
WithXML_GEdisabled, Expat has a smaller memory footprint and can be faster, but will not load external general entities and will replace all general entities (except the predefined five:amp,apos,gt,lt,quot) with a self-reference: for example, referencing an entitye1via&e1;will be replaced by text&e1;. - XML_DTD
-
Include support for using and reporting DTD-based content. If this is defined,
default attribute values from an external DTD subset are reported and attribute
value normalization occurs based on the type of attributes defined in the
external subset. Without this, Expat has a smaller memory footprint and can be
faster, but will not load external parameter entities or process conditional
sections. If defined, makes the functions
XML_SetBillionLaughsAttackProtectionMaximumAmplificationandXML_SetBillionLaughsAttackProtectionActivationThresholdavailable. - XML_NS
- When defined, support for the Namespaces in XML specification is included.
- XML_UNICODE
-
When defined, character data reported to the application is encoded in UTF-16
using wide characters of the type
XML_Char. This is implied ifXML_UNICODE_WCHAR_Tis defined. - XML_UNICODE_WCHAR_T
-
If defined, causes the
XML_Charcharacter type to be defined using thewchar_ttype; otherwise,unsigned shortis used. Defining this impliesXML_UNICODE. - XML_LARGE_SIZE
-
If defined, causes the
XML_SizeandXML_Indexinteger types to be at least 64 bits in size. This is intended to support processing of very large input streams, where the return values ofXML_GetCurrentByteIndex,XML_GetCurrentLineNumberandXML_GetCurrentColumnNumbercould overflow. It may not be supported by all compilers, and is turned off by default. - XML_CONTEXT_BYTES
-
The number of input bytes of markup context which the parser will ensure are
available for reporting via
XML_GetInputContext. This is normally set to 1024, and must be set to a positive integer to enable. If this is set to zero, the input context will not be available andXML_GetInputContextwill always reportNULL. Without this, Expat has a smaller memory footprint and can be faster. - XML_STATIC
- On Windows, this should be set if Expat is going to be linked statically with the code that calls it; this is required to get all the right MSVC magic annotations correct. This is ignored on other platforms.
- XML_ATTR_INFO
-
If defined, makes the additional function
XML_GetAttributeInfoavailable for reporting attribute byte offsets.
Using Expat
Compiling and Linking Against Expat
Unless you installed Expat in a location not expected by your compiler and
linker, all you have to do to use Expat in your programs is to include the Expat
header (#include <expat.h>) in your files that make calls to
it and to tell the linker that it needs to link against the Expat library. On
Unix systems, this would usually be done with the -lexpat argument.
Otherwise, you'll need to tell the compiler where to look for the Expat header
and the linker where to find the Expat library. You may also need to take steps
to tell the operating system where to find this library at run time.
On a Unix-based system, here's what a Makefile might look like when Expat is installed in a standard location:
CC=cc
LDFLAGS=
LIBS= -lexpat
xmlapp: xmlapp.o
$(CC) $(LDFLAGS) -o xmlapp xmlapp.o $(LIBS)
If you installed Expat in, say, /home/me/mystuff, then the Makefile
would look like this:
CC=cc
CFLAGS= -I/home/me/mystuff/include
LDFLAGS=
LIBS= -L/home/me/mystuff/lib -lexpat
xmlapp: xmlapp.o
$(CC) $(LDFLAGS) -o xmlapp xmlapp.o $(LIBS)
You'd also have to set the environment variable LD_LIBRARY_PATH to
/home/me/mystuff/lib (or to
${LD_LIBRARY_PATH}:/home/me/mystuff/lib if LD_LIBRARY_PATH already
has some directories in it) in order to run your application.
Expat Basics
As we saw in the example in the overview, the first step in parsing an XML
document with Expat is to create a parser object. There are three functions in the Expat API for creating a parser object.
However, only two of these (XML_ParserCreate and XML_ParserCreateNS) can be used for constructing
a parser for a top-level document. The object returned by these functions is an
opaque pointer (i.e. "expat.h" declares it as void *) to data with further
internal structure. In order to free the memory associated with this object you
must call XML_ParserFree. Note that if
you have provided any user data that gets stored in the
parser, then your application is responsible for freeing it prior to calling
XML_ParserFree.
The objects returned by the parser creation functions are good for parsing only one XML document or external parsed entity. If your application needs to parse many XML documents, then it needs to create a parser object for each one. The best way to deal with this is to create a higher level object that contains all the default initialization you want for your parser objects.
Walking through a document hierarchy with a stream oriented parser will require a good stack mechanism in order to keep track of current context. For instance, to answer the simple question, "What element does this text belong to?" requires a stack, since the parser may have descended into other elements that are children of the current one and has encountered this text on the way out.
The things you're likely to want to keep on a stack are the currently opened element and it's attributes. You push this information onto the stack in the start handler and you pop it off in the end handler.
For some tasks, it is sufficient to just keep information on what the depth of the stack is (or would be if you had one.) The outline program shown above presents one example. Another such task would be skipping over a complete element. When you see the start tag for the element you want to skip, you set a skip flag and record the depth at which the element started. When the end tag handler encounters the same depth, the skipped element has ended and the flag may be cleared. If you follow the convention that the root element starts at 1, then you can use the same variable for skip flag and skip depth.
void
init_info(Parseinfo *info) {
info->skip = 0;
info->depth = 1;
/* Other initializations here */
} /* End of init_info */
void XMLCALL
rawstart(void *data, const char *el, const char **attr) {
Parseinfo *inf = (Parseinfo *) data;
if (! inf->skip) {
if (should_skip(inf, el, attr)) {
inf->skip = inf->depth;
}
else
start(inf, el, attr); /* This does rest of start handling */
}
inf->depth++;
} /* End of rawstart */
void XMLCALL
rawend(void *data, const char *el) {
Parseinfo *inf = (Parseinfo *) data;
inf->depth--;
if (! inf->skip)
end(inf, el); /* This does rest of end handling */
if (inf->skip == inf->depth)
inf->skip = 0;
} /* End rawend */
Notice in the above example the difference in how depth is manipulated in the start and end handlers. The end tag handler should be the mirror image of the start tag handler. This is necessary to properly model containment. Since, in the start tag handler, we incremented depth after the main body of start tag code, then in the end handler, we need to manipulate it before the main body. If we'd decided to increment it first thing in the start handler, then we'd have had to decrement it last thing in the end handler.
Communicating between handlers
In order to be able to pass information between different handlers without using
globals, you'll need to define a data structure to hold the shared variables. You
can then tell Expat (with the XML_SetUserData function) to pass a pointer to this
structure to the handlers. This is the first argument received by most handlers.
In the reference section, an argument to a callback
function is named userData and have type void * if the
user data is passed; it will have the type XML_Parser if the parser
itself is passed. When the parser is passed, the user data may be retrieved using
XML_GetUserData.
One common case where multiple calls to a single handler may need to communicate
using an application data structure is the case when content passed to the
character data handler (set by XML_SetCharacterDataHandler) needs to
be accumulated. A common first-time mistake with any of the event-oriented
interfaces to an XML parser is to expect all the text contained in an element to
be reported by a single call to the character data handler. Expat, like many
other XML parsers, reports such data as a sequence of calls; there's no way to
know when the end of the sequence is reached until a different callback is made.
A buffer referenced by the user data structure proves both an effective and
convenient place to accumulate character data.
XML Version
Expat is an XML 1.0 parser, and as such never complains based on the value of the
version pseudo-attribute in the XML declaration, if present.
If an application needs to check the version number (to support alternate
processing), it should use the XML_SetXmlDeclHandler function to set a
handler that uses the information in the XML declaration to determine what to do.
This example shows how to check that only a version number of "1.0"
is accepted:
static int wrong_version;
static XML_Parser parser;
static void XMLCALL
xmldecl_handler(void *userData,
const XML_Char *version,
const XML_Char *encoding,
int standalone)
{
static const XML_Char Version_1_0[] = {'1', '.', '0', 0};
int i;
for (i = 0; i < (sizeof(Version_1_0) / sizeof(Version_1_0[0])); ++i) {
if (version[i] != Version_1_0[i]) {
wrong_version = 1;
/* also clear all other handlers: */
XML_SetCharacterDataHandler(parser, NULL);
...
return;
}
}
...
}
Namespace Processing
When the parser is created using the XML_ParserCreateNS, function, Expat performs
namespace processing. Under namespace processing, Expat consumes
xmlns and xmlns:... attributes, which declare
namespaces for the scope of the element in which they occur. This means that your
start handler will not see these attributes. Your application can still be
informed of these declarations by setting namespace declaration handlers with
XML_SetNamespaceDeclHandler.
Element type and attribute names that belong to a given namespace are passed to
the appropriate handler in expanded form. By default this expanded form is a
concatenation of the namespace URI, the separator character (which is the 2nd
argument to XML_ParserCreateNS),
and the local name (i.e. the part after the colon). Names with undeclared
prefixes are not well-formed when namespace processing is enabled, and will
trigger an error. Unprefixed attribute names are never expanded, and unprefixed
element names are only expanded when they are in the scope of a default
namespace.
However if XML_SetReturnNSTriplet has been called with
a non-zero do_nst parameter, then the expanded form for names with
an explicit prefix is a concatenation of: URI, separator, local name, separator,
prefix.
You can set handlers for the start of a namespace declaration and for the end of
a scope of a declaration with the XML_SetNamespaceDeclHandler function.
The StartNamespaceDeclHandler is called prior to the start tag handler and the
EndNamespaceDeclHandler is called after the corresponding end tag that ends the
namespace's scope. The namespace start handler gets passed the prefix and URI for
the namespace. For a default namespace declaration (xmlns='...'), the prefix will
be NULL. The URI will be NULL for the case where the
default namespace is being unset. The namespace end handler just gets the prefix
for the closing scope.
These handlers are called for each declaration. So if, for instance, a start tag had three namespace declarations, then the StartNamespaceDeclHandler would be called three times before the start tag handler is called, once for each declaration.
Character Encodings
While XML is based on Unicode, and every XML processor is required to recognized UTF-8 and UTF-16 (1 and 2 byte encodings of Unicode), other encodings may be declared in XML documents or entities. For the main document, an XML declaration may contain an encoding declaration:
<?xml version="1.0" encoding="ISO-8859-2"?>
External parsed entities may begin with a text declaration, which looks like an XML declaration with just an encoding declaration:
<?xml encoding="Big5"?>
With Expat, you may also specify an encoding at the time of creating a parser. This is useful when the encoding information may come from a source outside the document itself (like a higher level protocol.)
There are four built-in encodings in Expat:
- UTF-8
- UTF-16
- ISO-8859-1
- US-ASCII
Anything else discovered in an encoding declaration or in the protocol encoding
specified in the parser constructor, triggers a call to the
UnknownEncodingHandler. This handler gets passed the encoding name
and a pointer to an XML_Encoding data structure. Your handler must
fill in this structure and return XML_STATUS_OK if it knows how to
deal with the encoding. Otherwise the handler should return
XML_STATUS_ERROR. The handler also gets passed a pointer to an
optional application data structure that you may indicate when you set the
handler.
Expat places restrictions on character encodings that it can support by filling
in the XML_Encoding structure. include file:
- Every ASCII character that can appear in a well-formed XML document must be represented by a single byte, and that byte must correspond to it's ASCII encoding (except for the characters $@\^'{}~)
- Characters must be encoded in 4 bytes or less.
- All characters encoded must have Unicode scalar values less than or equal to 65535 (0xFFFF)This does not apply to the built-in support for UTF-16 and UTF-8
- No character may be encoded by more that one distinct sequence of bytes
XML_Encoding contains an array of integers that correspond to the
1st byte of an encoding sequence. If the value in the array for a byte is zero or
positive, then the byte is a single byte encoding that encodes the Unicode scalar
value contained in the array. A -1 in this array indicates a malformed byte. If
the value is -2, -3, or -4, then the byte is the beginning of a 2, 3, or 4 byte
sequence respectively. Multi-byte sequences are sent to the convert function
pointed at in the XML_Encoding structure. This function should
return the Unicode scalar value for the sequence or -1 if the sequence is
malformed.
One pitfall that novice Expat users are likely to fall into is that although Expat may accept input in various encodings, the strings that it passes to the handlers are always encoded in UTF-8 or UTF-16 (depending on how Expat was compiled). Your application is responsible for any translation of these strings into other encodings.
Handling External Entity References
Expat does not read or parse external entities directly. Note that any external
DTD is a special case of an external entity. If you've set no
ExternalEntityRefHandler, then external entity references are
silently ignored. Otherwise, it calls your handler with the information needed to
read and parse the external entity.
Your handler isn't actually responsible for parsing the entity, but it is
responsible for creating a subsidiary parser with XML_ExternalEntityParserCreate that
will do the job. This returns an instance of XML_Parser that has
handlers and other data structures initialized from the parent parser. You may
then use XML_Parse or XML_ParseBuffer calls against this parser. Since
external entities my refer to other external entities, your handler should be
prepared to be called recursively.
Parsing DTDs
In order to parse parameter entities, before starting the parse, you must call
XML_SetParamEntityParsing
with one of the following arguments:
-
XML_PARAM_ENTITY_PARSING_NEVER - Don't parse parameter entities or the external subset
-
XML_PARAM_ENTITY_PARSING_UNLESS_STANDALONE -
Parse parameter entities and the external subset unless
standalonewas set to "yes" in the XML declaration. -
XML_PARAM_ENTITY_PARSING_ALWAYS - Always parse parameter entities and the external subset
In order to read an external DTD, you also have to set an external entity reference handler as described above.
Temporarily Stopping Parsing
Expat 1.95.8 introduces a new feature: its now possible to stop parsing temporarily from within a handler function, even if more data has already been passed into the parser. Applications for this include
- Supporting the XInclude specification.
- Delaying further processing until additional information is available from some other source.
- Adjusting processor load as task priorities shift within an application.
- Stopping parsing completely (simply free or reset the parser instead of resuming in the outer parsing loop). This can be useful if an application-domain error is found in the XML being parsed or if the result of the parse is determined not to be useful after all.
To take advantage of this feature, the main parsing loop of an application needs to support this specifically. It cannot be supported with a parsing loop compatible with Expat 1.95.7 or earlier (though existing loops will continue to work without supporting the stop/resume feature).
An application that uses this feature for a single parser will have the rough structure (in pseudo-code):
fd = open_input()
p = create_parser()
if parse_xml(p, fd) {
/* suspended */
int suspended = 1;
while (suspended) {
do_something_else()
if ready_to_resume() {
suspended = continue_parsing(p, fd);
}
}
}
An application that may resume any of several parsers based on input (either from the XML being parsed or some other source) will certainly have more interesting control structures.
This C function could be used for the parse_xml function mentioned
in the pseudo-code above:
#define BUFF_SIZE 10240
/* Parse a document from the open file descriptor 'fd' until the parse
is complete (the document has been completely parsed, or there's
been an error), or the parse is stopped. Return non-zero when
the parse is merely suspended.
*/
int
parse_xml(XML_Parser p, int fd)
{
for (;;) {
int last_chunk;
int bytes_read;
enum XML_Status status;
void *buff = XML_GetBuffer(p, BUFF_SIZE);
if (buff == NULL) {
/* handle error... */
return 0;
}
bytes_read = read(fd, buff, BUFF_SIZE);
if (bytes_read < 0) {
/* handle error... */
return 0;
}
status = XML_ParseBuffer(p, bytes_read, bytes_read == 0);
switch (status) {
case XML_STATUS_ERROR:
/* handle error... */
return 0;
case XML_STATUS_SUSPENDED:
return 1;
}
if (bytes_read == 0)
return 0;
}
}