This demo is designed to show the simplest demo of parsing available. It allows you to parse an XML file and report any errors you received. It will also fill a listbox with all of the element names it encounters. It demonstrates the use of an ErrorHandler and a ContentHandler. For a more complex demo please see the Filling a TreeView demo.
In order to use a parser you must add the parser unit file to the your project. (The technical reasons behind this are simple, the intialization code for each parser package must be called. By adding the unit to the project you can be sure that the project will execute the code in the package's initialization section)
First click "Project | Add To Project
" on the
file menu. In this demo you should see that SAXKW
has already been added for you. This allows you to use
the default parser provided by Keith Wood. You
can add another parser by including the unit that declares
that package's vendor information. To see how to choose a
parser from a list of available parsers, please see the demo
"Choosing a parser from a list".
Another important decision is whether or not you will be using WideStrings in your application. If you are using simple ASCII files you don't need to-- however, to support some Unicode encodings you need to use WideChar and WideStrings. Within SAX you can control this using a conditional directive in the Project Options. To use WideStrings, add SAX_WIDESTRINGS to the Conditional Defines. If you change the value for this, you may need to recompile (build) your entire project for the setting to take effect. (Note: If you are using the KDSSAXExpat wrapper, you will also need to include the conditional define XML_WIDESTRINGS)
For this demo we will need to add a few components to
the form. First we will need a listbox for our output.
Second we will need a button to start our parsing. We also
want to add an OpenDialog so that we can search for the file.
Finally we will need to add a TSAXContentHandler
and a TSAXErrorHandler
. The SAX components are installed
to the SAX tab in the Component Palette by default.
Now that you have added all of the components let's set up some events.
We will begin by adding event handlers to the TSAXErrorHandler
.
In each event we will simply add the message to the our Listbox1.
procedure TForm1.SAXErrorHandler1Error(Sender: TObject; const Error: ISAXParseError); begin ListBox1.Items.Add('[Error] ' + Error.getMessage + ' Line ' + IntToStr(Error.getLineNumber) + ' Column ' + IntToStr(Error.getColumnNumber)); end; procedure TForm1.SAXErrorHandler1FatalError(Sender: TObject; const Error: ISAXParseError); begin ListBox1.Items.Add('[Fatal Error] ' + Error.getMessage + ' Line ' + IntToStr(Error.getLineNumber) + ' Column ' + IntToStr(Error.getColumnNumber)); end; procedure TForm1.SAXErrorHandler1Warning(Sender: TObject; const Error: ISAXParseError); begin ListBox1.Items.Add('[Warning] ' + Error.getMessage + ' Line ' + IntToStr(Error.getLineNumber) + ' Column ' + IntToStr(Error.getColumnNumber)); end;
That's all we need to report errors. Now we we add an OnStartElement event to our
TSAXContentHandler
. It will simply add the name of the element
to our Listbox1.
procedure TForm1.SAXContentHandler1StartElement(Sender: TObject; const NamespaceURI, LocalName, QName: WideString; const Atts: IAttributes); begin ListBox1.Items.Add('[Element] ' + QName); end;
Notice that we aren't outputing the Line and Column information. The IErrorHandler interface provides the Line and Column information for us automatically. However, in order to get this information for other interfaces we need to use an ILocator. This information usually isn't necassary though (except when reporting errors). For more information on ILocator please see the demo "Using Locator Information".
We've got the setup out of the way, now we are ready to parse an XML document. We'll put all of our parsing code in the OnClick event handler for Button1.
We will begin by locating the file we want to parse. We can do this using our OpenDialog. Then we will need to obtain an XMLReader. Because we have already included a SAX Parser in our project, we can just get the default vendor and default XMLReader interface. Next, we'll need to tell the XMLReader interface about our ContentHandler and ErrorHandler so it will fire our events. After that, all we need to do is call the parse method.
procedure TForm1.Button1Click(Sender: TObject); var XMLReader : IXMLReader; begin ListBox1.Items.Clear; if (OpenDialog1.Execute) then begin // Get the Default SAX Vendor and XML Reader XMLReader:= GetSAXVendor.XMLReader; XMLReader.setContentHandler(SAXContentHandler1); XMLReader.setErrorHandler(SAXErrorHandler1); XMLReader.parse(OpenDialog1.FileName); XMLReader:= nil; end; end;
We could leave our code like this and everything would work great. Instead,
let's add one more step: checking to see if our Vendor is a
TSAXBufferedVendor
. It is important to be aware that in SAX
for Pascal there are two kinds of Vendors: Normal and Buffered. A
Buffered Vendor is a vendor that is relying on the Buffered
(or PSAXChar) versions of the interfaces, such as
IBufferedXMLReader
. Generally these parsers are much
faster because they require fewer string allocations. A good example of
a Buffered SAX vendor is KDSSAXExpat.
We'll have to add the BSAX
unit to our uses clause. In SAX
for Pascal, all of the Buffered interfaces are in units that begin with
a "B". In order to make use of the Buffered Vendor we'll use an adapter
class, so we'll also need to use the unit SAXAdapters
.
procedure TForm1.Button1Click(Sender: TObject); var XMLReader : IXMLReader; XMLBufReader: IBufferedXMLReader; Vendor: TSAXVendor; ContentHandler: IContentHandler; begin ListBox1.Items.Clear; ContentHandler:= SAXContentHandler1; if (OpenDialog1.Execute) then begin // Get the Default SAX Vendor Vendor:= GetSAXVendor; if Vendor is TBufferedSAXVendor then begin XMLBufReader:= TBufferedSAXVendor(Vendor).BufferedXMLReader; XMLBufReader.setContentHandler(Adapt(ContentHandler, XMLBufReader)); XMLBufReader.setErrorHandler(SAXErrorHandler1); XMLBufReader.parse(OpenDialog1.FileName); XMLBufReader:= nil; end else begin XMLReader:= Vendor.XMLReader; XMLReader.setContentHandler(ContentHandler); XMLReader.setErrorHandler(SAXErrorHandler1); XMLReader.parse(OpenDialog1.FileName); XMLReader:= nil; end; end; end;
We grab a reference to the Default Vendor just as we did before. However,
we check to see what kind of Vendor it is before using it. Based on the
type we access the XMLReader
or BufferedXMLReader
property. As a final difference, we must use an adapter for our
ContentHandler because the two interfaces use different string
conventions. In the SAXAdapters unit, there are many pre-built adapters
for converting the two kinds of interfaces back and forth. The
Adapt
function is overloaded to understand almost any kind
of SAX interface you send to it (this excludes IXMLReader
and
IBufferedXMLReader
).
In the Simple Parsing Demo folder there are two XML documents. The first
document, Document1.xml
, when parsed should have the following
output:
[Element] Document [Element] Element1 [Element] Element2 [Element] Element3 [Element] Element4 [Element] Element5 [Element] Element6 [Element] Element7
The second document, Document2.xml
, contains errors. However
we have registered an ErrorHandler so we should see the Error messages reported
in ListBox1. When parsed, you should have the following output:
[Element] Document [Element] Element1 [Element] Element2 [Element] Element3 [Fatal Error] Mismatched closing tag - Element4 - should be Document Line 7 Column 17