XML Sample

This topic walks you through a sample that covers the basic XML API functions.

Sample Overview

The sample is loading an existing XML document from a file. It modifies this XML document and iterates through the document with the possible techniques. The file the sample is using has the following structure:

<Order>
  <Name>Scott Bradley</Name>
  <OrderItems>
    <Item id="1">Lord of the Rings</Item>
  </OrderItems>
  <Price>25.90</Price>
</Order>

Starting

First, we create a new project (the project type does not matter). Then, we take the XML snapshot from the Sample Overview section, store it in a text file and add it to the data file section of our project. Store the file as ORDER.XML.

Create a new plain script and include the XmlAPI.BDH file with the following statement:
use "XMLAPI.BDH"

Loading the XML Document

The first step is to load the document and get the document handle. We need a variable that is storing the handle (number) and one string variable to get the absolute path of our XML document. We have to get the absolute path because XmlCreateDocumentFromFile requires the absolute path to the file that should be loaded - GetDataFilePath is the function that returns the absolute path to a file in the data file section.

As you can see in the sample code below we have to check if the returned handle value is not null. In case of an error - every XML function that returns a handle will return a 0. As you can further see - we have to free the handles when they are no longer needed - so we free the document handle at the end of our script.

dcltrans
  transaction TmyTrans1
  var
    hDoc     : number;
    sXmlFile : string;
  begin
    GetDataFilePath("order.xml", sXmlFile);
    hDoc := XmlCreateDocumentFromFile (sXmlFile);
    if (hDoc <> 0) then
      ...
      XmlFreeHandle(hDoc);
    end;
  end TmyTrans1;

Iterating through an XML Document

We now have a document handle in hDoc which is in fact the "virtual" document root node - a node that is the parent node of all top level nodes in the document - in our case it would be the parent of the node "Order".

If we want to iterate through all nodes in the XML document we have to get all child nodes of the document and iterate through them. And we also go through the child nodes of the returned child nodes - so to iterate through the whole document we have to write a function that will be called recursively - to make things easier in this sample we only iterate 2 node levels.

XmlGetChildNodes returns a nodelist handle. XmlGetCount returns the number of items in the nodelist and with XmlGetItem we can access one node in the list identified by the index (0 for the first item; XmlGetCount - 1 for the last).

We are going to write the node names and values to the output file:

dcltrans
  transaction TmyTrans1
  var
    hDoc, hChildren, hChildNode, nChildIx, nChildCount : number;
    hChildren2, hChildNode2, nChildIx2, nChildCount2   : number;
    sXmlFile, sName, sValue                            : string;
  begin
    GetDataFilePath("order.xml", sXmlFile);
    hDoc := XmlCreateDocumentFromFile(sXmlFile);
    if (hDoc <> 0) then
      hChildren := XmlGetChildNodes(hDoc); // get the nodelist to the children
      nChildCount := XmlGetCount(hChildren); // get the number of items in the nodelist
      for nChildIx := 0 to nChildCount -1 do// iterate through the children
        hChildNode := XmlGetItem(hChildren, nChildIx); // get a child out of the list
        XmlGetNodeName(hChildNode, sName);
        XmlGetNodeValue(hChildNode, sValue);
        writeln(sName + ":" + sValue);
        // now we do the same for the children of this child node
        hChildren2 := XmlGetChildNodes(hChildNode);
        nChildCount2 := XmlGetCount(hChildren2);
        for nChildIx2 := 0 to nChildCount2 -1 do
          hChildNode2 := XmlGetItem(hChildren2, nChildIx2);
          XmlGetNodeName(hChildNode2 , sName);
          XmlGetNodeValue(hChildNode2 , sValue);
          writeln(" " + sName + ":" + sValue);
          XmlFreeHandle(hChildNode2);
        end;
        XmlFreeHandle(hChildren2);
        XmlFreeHandle(hChildNode);
      end;
      XmlFreeHandle(hChildren);
      XmlFreeHandle(hDoc);
    end;
  end TmyTrans1;

Output File:

Order:
  Name:Scott Bradley
  OrderItems:
  Price:

Execute Queries

If you know exactly which node in the document you want to get - you can execute an X-Path query. There are two functions that allow you to execute queries - XmlSelectNodes and XmlSelectSingleNode. XmlSelectNodes returns a nodelist of all resulting nodes whereas XmlSelectSingleNode returns the first resulting node or 0 if there were no nodes matching the query. In our sample we want to query all nodes with name "Item" and write the values to the output file.

dcltrans
  transaction TmyTrans1
  var
    hDoc, hResultNodes, hNode, nResultIx, nResultCount : number;
    sXmlFile, sName, sValue                            : string;
  begin
    GetDataFilePath("order.xml", sXmlFile);
    hDoc := XmlCreateDocumentFromFile(sXmlFile);
    if (hDoc <> 0) then
      hResultNodes := XmlSelectNodes(hDoc, "//Item"); // get all nodes of name Item
      nResultCount := XmlGetCount(hResultNodes); // get the number of items in the nodelist
      for nResultIx := 0 to nResultCount -1 do // iterate through the children
        hNode := XmlGetItem(hResultNodes, nResultIx); // get a child out of the list
        XmlGetNodeName(hNode, sName);
        XmlGetNodeValue(hNode, sValue);
        writeln(sName + ":" + sValue);
        XmlFreeHandle(hNode);
      end;
      XmlFreeHandle(hResultNodes);
      XmlFreeHandle(hDoc);
    end;
  end TmyTrans1;

Output File:

Item: Lord of the Rings

Creating Nodes

Now lets create a new Item node in our XML document. We can either create the whole XML node (including value and attributes) with XmlCreateNodeFromXml or we can create the node with XmlCreateNode and set the value and attributes with other API functions. After creating the node and setting the values - we are appending the node to the OrderItems node. So we have to do the following steps: Create the node, Set the value and attributes, Get the OrderItems node and append the created node to the order items node.

dcltrans
transaction TmyTrans1
var
  hDoc, hOrderItems, hNewItem : number;
  sXmlFile, sXml              : string;
begin
  GetDataFilePath("order.xml", sXmlFile);
  hDoc := XmlCreateDocumentFromFile(sXmlFile);
  if (hDoc <> 0) then
    hOrderItems := XmlSelectSingleNode(hDoc, "/Order/OrderItems"); // get the OrderItems node
    hNewItem := XmlCreateNode("Item"); // Create a new Item Node
    XmlSetNodeAttribute(hNewItem, "id", "2"); // set the node attribute
    XmlSetNodeValue(hNewItem, "Star Wars - Episode II"); // set the node value
    XmlAppendChild(hOrderItems, hNewItem); // append the node
    // now lets get the XML representation
    XmlGetXml(hDoc, sXml);
    writeln(sXml);
    XmlFreeHandle(hNewItem);
    XmlFreeHandle(hOrderItems);
    XmlFreeHandle(hDoc);
  end;
end TmyTrans1;

Output File:

<Order>
  <Name>Scott Bradley</Name>
  <OrderItems>
    <Item id="1">Lord of the Rings</Item>
    <Item id="2">Star Wars - Episode II</Item>
  </OrderItems>
  <Price>25.90</Price>
</Order>

Working with Nodes / Removing

As you have already seen in the previous examples - there are several functions to get the name and value of a node XmlGetNodeValue, XmlGetNodeName) and there are functions to set the value and attributes of a node (XmlSetNodeValue, XmlSetNodeAttribute).

Iterating through nodes is also already described above - but there is also another way to get a child node of a node. XmlGetChildNodeByIndex directly returns the node handle to a child of a node. This function is useful if you know exactly which child node you want to access. Otherwise, you need to iterate through the nodelist returned by XmlGetChildNodes.

Attributes can also be accessed either by name or by index. XmlGetAttributeByIndex returns the value of an attribute identified by its index whereas XmlGetAttributeByName returns the value of an attribute identified by its name.

XmlRemoveChild allows you to remove a node from the document.

The following sample code will make use of some of the above mentioned functions to remove the Item node with the attribute value "2" for attribute "id":

dcltrans
  transaction TmyTrans1
  var
    hDoc, hOrderItems, hItems, hItem, nItemCount, nItemIx : number;
    sXmlFile, sXml                                        : string;
  begin
    GetDataFilePath("order.xml", sXmlFile);
    hDoc := XmlCreateDocumentFromFile(sXmlFile);
    if (hDoc <> 0) then
      hOrderItems := XmlSelectSingleNode(hDoc, "/Order/OrderItems/*"); // get all Item nodes
      nItemCount := XmlGetCount(hOrderItems);
      for nItemIx := 0 to nItemCount -1 do
        hItem := XmlGetItem(hOrderItems, nItemIx);
        XmlGetAttributeByName(hItem, "id", sAttrValue);
        if(StrICmp(sAttrValue, "2") = 0) then
          XmlRemoveChild(hItem);
        end;
        XmlFreeHandle(hItem);
      end;
      // now lets get the XML representation
      XmlGetXml(hDoc, sXml);
      writeln(sXml);
      XmlFreeHandle(hOrderItems);
      XmlFreeHandle(hDoc);
    end;
  end TmyTrans1;

Output File:

<Order>
  <Name>Scott Bradley</Name>
  <OrderItems>
    <Item id="1">Lord of the Rings</Item>
  </OrderItems>
  <Price>25.90</Price>
</Order>