This demo is designed to show you how to fill a TreeView that shows the document structure. Primarily you should pay attention to how the items are added. Throughout the parse, the CurrNode is updated. This allows the program to know where it should add each item in the tree. In general, SAX does not maintain state information (where it is in the tree with regard to parent/child context relationships). This means that you have to keep track of that information if it is important to you.
In this demo, we will skip all of the basic project setup information. If you need to know more about the project setup, please see the "Choosing a Parser Demo" or the "Simple Parsing Demo".
For this demo we will need to add a few components to
the form. First we will need a ComboBox to choose our parser. Second
we'll add a TreeView for our output. In our demo we have also added
an ImageList so that our TreeView nodes will have images for attributes
and elements. Next we will need a listbox to show
our error messages. Next 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.
The event handlers for the TSAXErrorHandler
will work exactly
as they did in our "Simple Parsing Demo", so we won't explain those here.
Our FormCreate event will work exactly as it did in the "Choosing a Parser
Demo" as well. The important events for this demo are the OnClick event for
Button1
and the OnStartElement and OnEndElement events
for TSAXContentHandler1
. Let's begin with the OnClick event
handler:
procedure TForm1.Button1Click(Sender: TObject); var XMLReader : IXMLReader; XMLBufReader: IBufferedXMLReader; Vendor: TSAXVendor; ContentHandler: IContentHandler; begin TreeView1.Items.Clear; Listbox1.Items.Clear; CurrNode:= nil; ContentHandler:= SAXContentHandler1; if (OpenDialog1.Execute) then begin Vendor:= GetSAXVendor(ComboBox1.Items[ComboBox1.ItemIndex]); 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 begin by clearing the TreeView and ListView so that our output is
fresh. Next we set CurrNode to nil. Wait a second, what is CurrNode? CurrNode
is our temporary TTreeNode
that we will use while we are
building the tree. We will add this variable declaration to our Form's
private section:
private CurrNode : TTreeNode; end;
Finally we get a reference to our ContentHandler as an interface so that we can use it later in the function. After we have initialized all of our variables, we are ready to begin parsing. The rest of the code works exactly as it did in the "Simple Parsing Demo." Remember, in order to use Buffered Vendors with our normal ContentHandler, we need to use an adapter from the SAXAdapters unit.
Now that we have created the parsing code we need to add the OnStartElement and OnEndElement event handlers. Let's look at the OnStartElement handler first:
procedure TForm1.SAXContentHandler1StartElement(Sender: TObject; const NamespaceURI, LocalName, QName: WideString; const Atts: IAttributes); var I : Integer; ANode : TTreeNode; begin // Set the node if (CurrNode = nil) then CurrNode:= TreeView1.Items.Add(nil, qName) else CurrNode:= TreeView1.Items.AddChild(CurrNode, qName); // Set the image CurrNode.ImageIndex:= 0; // Add the attributes if (Atts <> nil) then begin for I:= 0 to Atts.getLength - 1 do begin ANode:= TreeView1.Items.AddChild(CurrNode, Atts.getQName(I)); ANode.ImageIndex:= 1; ANode.SelectedIndex:= 1; end; end; end;
The first thing we need to do is to check if CurrNode is nil. Remember we set CurrNode to nil before we began our parse. If CurrNode is nil then this is the first time that the OnStartElement callback has ocurred. This indicates that we need to add the root node to our TreeView. Otherwise we need to add a new node as a child of CurrNode. As we add the item, we also assign CurrNode so that we remember where we are at in the TreeView. Finally we set the ImageIndex so that we can tell that it is an element in the TreeView.
After we have created the basic TTreeNode
for the element,
we can loop through the attributes and add each of those. Again, we will
add the items as children of CurrNode. Now let's look at the OnEndElement
handler:
procedure TForm1.SAXContentHandler1EndElement(Sender: TObject; const NamespaceURI, LocalName, QName: WideString); begin // Expand the item if (CurrNode <> nil) then CurrNode.Expand(false); // Set the node CurrNode:= CurrNode.Parent; end;
Again, our primary interest is tracking the CurrNode. All we need to do is set the CurrNode to it's Parent Node. Before we have done this though we expand that section of the TreeView.
Just in case you don't have a complete understanding of how this works,
we will break it down into each step. Remember, as the SAX parser is parsing
the document it will fire an OnStartElement event at the start of every
element it encounters in the document. It will fire and OnEndElement at
the end of each element it encounters. For now, we will not go in to detail
about other events that are not being handled in this demo. The following
is a summary of what happens when parsing Document1.xml
included with this demo.
Document
in
the XML document. The parser will immediately fire an OnStartElement
event. Within the OnStartElement handler, CurrNode is initially nil so
we add the root item to the TreeView. We add the QName (Qualified Name)
which is a combination of the element name and namespace prefix. We
also set CurrNode equal to the new TTreeNode
. So at this
point CurrNode points to the first and only node in the TreeView,
"Document".
TTreeNode
in our
TreeView, so instead of adding a root node, we will create a new item
that is a child of our CurrNode. Again we assign CurrNode to our new
item in the TreeView. This time, however, we must also add nodes for
the attributes. Notice, that we do not change the value of CurrNode
when adding the attribute items. So, now we have received two OnStartElement
events but no OnEndElement events. Also we have five nodes in our
TreeView: "Document", its child "Element1", and the three attribute
nodes "attribute1", "attribute2" and "attribute3". CurrNode still
points to "Element1".
TTreeNode
in our
TreeView, so instead of adding a root node, we will create a new item
that is a child of our CurrNode. Again we assign CurrNode to our new
item in the TreeView, so CurrNode points to "Element2".
<Element3/>
which is the shorthand XML for an
empty element. Even empty elements will trigger OnStartElement and
OnEndElement events.
At this point you should see how the events are functioning to fill the TreeView. The events continue in this manner until the end of the document is reached. If the parser encounters an error, it will stop firing the events. The TreeView will be filled until the point of the error in the document.