Using .NET XML API in PowerBuilder

LG
Cut-out: Even the newest buzz word in Internet programming– AJAX – is related to
XML. The good news is that .NET framework provides a very powerful API for
manipulating XML, and you, as a PowerBuilder developer, can leverage on that API
through the .NET interoperability feature released in PowerBuilder 11.
Xue-song Wu is a staff software engineer at Sybase Asia Development Center,
Singapore, and the leader of PowerBuilder Virtual Machine and Compiler team.
Header: XML
Using .NET XML API in PowerBuilder
Exploring the .NET framework
Xue-song Wu
XML is becoming the standard for data exchange. More and more software
products and technologies are being built on top of it. Even the newest buzz word in
Internet programming– AJAX – is related to XML. The good news is that .NET
framework provides a very powerful API for manipulating XML, and you, as a
PowerBuilder developer, can leverage on that API through the .NET interoperability
feature released in PowerBuilder 11.
In this article, I will walk you through a code sample, showing how to:
 Read and write XML files,
 Traverse a XML DOM tree, and
 Query XML data using XPath.
The code sample, which is a .NET Windows Forms target, contains three PBLs:
usexml.pbl, netxml.pbl, and netui.pbl. The majority of the code is in usexml.pbl, while
netxml,pbl and netui.pbl provide some utilities for XML and GUI effects, respectively.
Figure 1 shows the main window of the sample.
Figure 1 – The main window
Writing XML files
Clicking on the “Writing XML” button will open up the “Writing XML” window,
as shown in Figure 2. The window contains a DataWindow and two buttons. When you
click on the “Save as XML” button, a GetSaveFileName dialog will pop up prompting
you to specify a file name and then save the DataWindow to the file in XML format (see
Listing 1). The root node – CustomerDetails – contains a number of Customer nodes,
which, in turn, contain FirstName, LastName, CompanyName, and PhoneNumber nodes.
Figure 2. –Writing XML
Now let’s examine the code. Listing 2 is the script of the clicked event of the
“Save as XML” button. The script opens up a GetSaveFileName dialog. If a file name is
specified correctly, it calls the saveXmlFile() function (Listing 3).
The function creates an instance of the .NET XmlWriter class and writes a
CustomerDetails node (the root node). Then for each row of the DataWindow, it writes a
Customer, then under that, writes four nodes for the FirstName, LastName,
CompanyName, and PhoneNumber, respectively.
The XmlWriter class is an abstract class. The real type of the object doing the job
is actually XmlTextWriter, which provides a fast, non-cached, forward-only way of
generating streams or files containing XML data. The major methods and properties of
the class include Close, Flush, Formatting, WriteAttribues, WriteAttributeString,
WriteComment, WriteElementString, WriteElementString, WriteEndAttribute,
WriteEndDocument, WriteState, and WriteStartDocument. For details, please refer to
MSDN.
Reading XML files
The w_readxml window (Figure 3) demonstrates how to read an XML file and
populate a TreeView with the elements of the XML file.
Figure 3 – Reading XML file
The clicked event of the “Read XML” button calls the readXmlFile() function,
which uses the .NET XmlTextReader class to read the elements from an XML file. The
code of the readXmlFile() function is shown in Listing 4.
The function creates an instance of the XmlTextReader class, which provides
forward-only, read-only access to a stream of XML data. The XmlTextReader.NodeType
property indicates the type of the current node, which can be element, attribute, text,
CDATA, comment, and so on. An element node can have attributes. The
XmlTextReader.Name and XmlTextReader.Value properties are for getting the name and
value of the current node. Please refer to MSDN for details.
In order to insert the XML nodes into the TreeView, the function maintains an
array of TreeViewItem handles and named handles, representing the current tree branch,
which allows it to traverse back from the leaf node to the root node.
If the current node is an element, the function inserts an element node. If the
element has attributes, it adds an attribute node under the element node. If the current
node is text, it inserts a text node under the current element node.
Traversing XML DOM
While the XmlTextReader class provides a fast way for reading an XML file, it
doesn’t provide you a DOM tree. If you want to load an XML file into memory and then
manipulate the DOM tree, the XmlDocument class is more appropriate for that purpose.
The w_dom window, which looks very similar to w_readxml, shows how to use this
class.
The main code is in the readXmlFile function of this window object. The function
creates an instance of the .NET XmlDocument class, creates an instance of the
XmlTextReader class, and loads the XML file into memory through the XmlTextReader
object. With the DOM tree we can populate the TreeView pretty easily by calling the
recursive function, populateTree.
Notice that we pass an n_xmlElement object to the populateTree function rather
than passing a .NET XmlElement object directly. This is because PowerBuilder 11
doesn’t allow you to use a .NET type as a parameter of a function. To overcome this
limitation, the n_xmlElement NVO is defined to wrap an instance of XmlElement.
The code of the populateTree function is shown in Listing 5. For each child node
of the given XmlElement, the function adds it to the TreeView. If a child node is an
element, the populateTree is called recursively to add the child node to the TreeView.
With the XML DOM in hand, you can add new nodes, remove nodes, or modify
nodes of the DOM tree. You can also save the DOM tree to a file.
Querying XML data using XPath
The XmlDocument class has certain limitations. First of all, the entire document
needs to be loaded into memory. So the class is not suitable for huge XML files. The
XPathDocument and XPathNavigator classes are intended to address this, as they allow
you to process XML data without loading the entire DOM tree.
The w_xpath window (Figure 4) demonstrates how to use XPathDocument and
XPathNavigator classes.
Figure 4 – The w_xpath window
In this screen shot, we choose the XML file generated with the w_writexml
window and then ask the program to list all LastNames with the XPath expression,
CustomerDetails/Customer/LastName.
In fact, you can choose any XML file and use appropriate XPath expression to the
process the data.
The code is in the clicked event of the Search button (Listing 6). We first create
an instance of XpathDocument, then create an XPathNavigator instance by calling the
XpathDocument.CreateNavigator method, then get an XpathNodeIterator with the
specified XPath expression. With the XpathNodeIterator in hand, we can iterate through
the items and add them to the ListBox.
Further Readings on .NET XML APIs
I hope I have shown you the power of the .NET XML API. The .NET XML API
allows you to do many other things besides those that are shown in the sample. To
explore how to use the API, besides MSDN, I highly recommend the following articles:
1) XML in .NET: .NET Framework XML Classes and C# Offer Simple, Scalable
Data Manipulation (http://msdn.microsoft.com/msdnmag/issues/01/01/xml/).
2) The “.NET XML Best Practices” series:
– Part I: Choosing an XML API
(http://support.softartisans.com/kbview.aspx?ID=673).
– Part II: Reading XML Documents
(http://support.softartisans.com/kbview.aspx?ID=674)
– Part III: Writing XML Documents
(http://support.softartisans.com/kbview.aspx?ID=675)
Some GUI Effects
You may have already noticed that the shape of the “Write XML” button on the
main window is oval. This is achieved by calling the f_makeOval function object.
Trying to resize the four sub-windows, you will see the controls anchoring to the
borders of the window quite nicely. It is the f_anchorControl function object that does the
trick. Please figure out how these two functions work by your own by examining the
downloadable sample code.
Conclusion
The .NET interoperability feature of PowerBuilder 11 makes the .NET framework
widely open to you. Explore the .NET framework and you will be able to do many things
that were impossible or hard to do with PowerBuilder. There are still some limitations in
.NET interoperability in PowerBuilder 11 (e.g., the code for calling .NET classes has to
be inside “if defined pbdotnet/#end if” code blocks, and .NET classes can’t be used as
parameters and return types of functions). We will gradually remove the limitations and
enhance the .NET interoperability feature in future releases.
Listing 1: XML format for customer details
<?xml version="1.0" encoding="utf-8" ?>
- <!-XMLTextWriter Example
-->
- <CustomerDetails>
- <Customer>
<FirstName>Michaels aaa</FirstName>
<LastName>Devlin</LastName>
<CompanyName>The Power Group</CompanyName>
<PhoneNumber>2015558966</PhoneNumber>
</Customer>
- <Customer>
<FirstName>Beth</FirstName>
<LastName>Reiser</LastName>
<CompanyName>AMF Corp.</CompanyName>
<PhoneNumber>2125558725</PhoneNumber>
</Customer>
…
</CustomerDetails>
Listing 2: The script of the clicked event of the “Save as XML” button
string ls_path, ls_file
int li_rc
li_rc = GetFileSaveName ( "Save XML File", ls_path, ls_file,&
"XML", "Xml Files (*.xml),*.xml" , "", 32770)
IF li_rc = 1 Then
saveXmlFile(ls_path, dw_1)
End If
Listing 3. SaveXmlFile
#if defined pbdotnet then
system.xml.xmlwriter wt
wt = system.xml.xmlwriter.create(path)
int rows
rows = dw.rowcount()
//wt.Formatting = system.xml.Formatting.Indented
wt.WriteStartDocument() //Start a new document
// Write the Comment
wt.WriteComment("XMLTextWriter Example")
// Insert an Start element tag
wt.WriteStartElement("CustomerDetails")
long i
for i = 1 to rows
// Write the Customer element
wt.WriteStartElement("Customer","")
// Write the FirstName elemenent and its data
wt.WriteStartElement("FirstName","")
wt.WriteString(dw.getItemString(i, 1))
wt.WriteEndElement()
// Write the LastName Element and its data
wt.WriteStartElement("LastName","")
wt.WriteString(dw.getItemString(i, 2))
wt.WriteEndElement()
// Write the CompanyName element and its data
wt.WriteStartElement("CompanyName","")
wt.WriteString(dw.getItemString(i, 3))
wt.WriteEndElement()
// Write the PhoneNumber element and its data
wt.WriteStartElement("PhoneNumber","")
wt.WriteString(dw.getItemString(i, 4))
wt.WriteEndElement()
wt.WriteEndElement()
next
// End all the tags here
wt.WriteEndDocument()
wt.Flush()
wt.Close()
#end if
Listing 4. The readXmlFile function
#if defined pbdotnet then
system.xml.XmlTextReader reader
reader = create system.xml.XmlTextReader(path)
long handles[]
int depth
long i
long currHandle
reader.MoveToContent()
Do While Not reader.EOF
choose case reader.nodeType
case system.xml.XmlNodeType.Element!
depth = reader.depth
if depth > 0 then
currHandle = tv_1.insertItemLast(handles[depth], &
"Element : " + reader.name, 0)
handles[depth+1] = currHandle
if reader.hasAttributes then
For i = 0 To reader.AttributeCount - 1
reader.MoveToAttribute(i)
tv_1.insertItemLast(currHandle, "Attribute : " + &
reader.Name + " : " + reader.value, 0)
Next
reader.MoveToElement()
end if
else
handles[1] = tv_1.insertItemLast(0, reader.name, 0)
end if
case system.xml.XmlNodeType.Text!
tv_1.insertItemLast(currHandle, "Text : "+reader.value, 0)
end choose
reader.Read()
Loop
reader.Close()
#end if
long ll_tvi
ll_tvi = tv_1.FindItem(RootTreeItem! , 0)
tv_1.ExpandItem(ll_tvi)
Listing 5. The readXmlFile function of window w_dom
long ll_tvi
#if defined PBDOTNET then
system.xml.xmlDocument doc
doc = create system.xml.XmlDocument
system.xml.xmlTextReader reader
reader = create system.xml.XmlTextReader(path)
reader.WhitespaceHandling = system.xml.WhitespaceHandling.None!
doc.Load(reader)
n_xmlelement nelement
nelement = create n_xmlElement
nelement.element = doc.documentElement
populateTree(0, nelement)
#end if
ll_tvi = tv_1.FindItem(RootTreeItem! , 0)
tv_1.ExpandItem(ll_tvi)
Listing 5. The populateTree function of w_dom
#if defined pbdotnet then
system.xml.xmlElement currEle
currEle = element.element
long curr
curr = tv_1.insertItemLast(parentNode, currEle.Name, 0)
long n
n = currEle.childNodes.count
n_xmlElement ele
ele = create n_xmlElement
long i
system.xml.xmlNode node
for i = 0 to n - 1
node = currEle.childNodes.Item(i)
choose case node.nodeType
case system.xml.xmlNodeType.Element!
ele.element = node
populateTree(curr, ele)
case system.xml.xmlNodeType.Attribute!
tv_1.insertItemLast(curr, "Attribute : " + node.name + &
" : " + node.value, 0)
case system.xml.xmlNodeType.Text!
tv_1.insertItemLast(curr, "Text : " + node.value, 0)
end choose
next
return curr
#end if
return 0
Listing 6. Using XpathDocument and XPathNavigator
#if defined pbdotnet then
system.xml.xpath.xpathDocument doc
doc = create system.xml.xpath.XPathDocument(sle_1.text)
system.xml.xpath.XPathNavigator nav
nav = doc.CreateNavigator()
system.xml.xpath.XPathNodeIterator iter
iter = nav.Select(sle_2.text)
do While iter.MoveNext()
lb_1.AddItem(iter.Current.Value)
loop
#end if