Sample of how to read a simple XML document using MSXML DOM in NAV

Sample of how to read a simple XML document using MSXML DOM in NAV

  • Comments 5

This is a follow-up to the post "how to write a simple XML document". The other post conitains a few more background details which I won't repeat here. So even if you only want to read XML Documents, then you may want to have a look there anyway.

But let's get straight to the point. This is how you can read an XML Document from Microsoft Dynamics NAV, using MSXML DOM: 

1. Create a new codeunit.

2. Declare these 5 new global variables:

XMLDoc   Type = Automation 'Microsoft XML, v4.0'.DOMDocument
DOMNode   Type = Automation 'Microsoft XML, v4.0'.IXMLDOMNode
XMLNodeList   Type = Automation 'Microsoft XML, v4.0'.IXMLDOMNodeList

CurrentElementName   Type = Text  30
i   Type = Integer

 

3. Initialize the Dom Document:
CREATE(XMLDoc);
XMLDoc.async(FALSE);
XMLDoc.load('C:\XML\MyXML.xml');

4. Set up a loop to browse through the nodes in the document:
XMLNodeList := XMLDoc.childNodes;
for i := 0 to XMLNodeList.length - 1 do begin
  DOMNode := XMLNodeList.item(i);
  ReadChildNodes(DOMNode);
end;

You also have to create ReadChildNodes as a function with one parameter:
CurrentXMLNode, Type = Automation 'Microsoft XML, v4.0'.IXMLDOMNode.

The reason for the functionis to enable you to run it recursively. This is useful because you don't know how many child nodes there are. And there can easily be a child node inside of another child node.


So create the function ReadChildNodes.
The function has one parameter:
CurrentXMLNode : Automation "'Microsoft XML, v4.0'.IXMLDOMNode"

And a number of local variables:
TempXMLNodeList    Automation = 'Microsoft XML, v4.0'.IXMLDOMNodeList
TempXMLAttributeList    Automation = 'Microsoft XML, v4.0'.IXMLDOMNamedNodeMap
j    Integer
k   Integer

The function checks to see what type of node it was called with. A node can be an element, text (data), attribute etc. The node property .nodeType tells you which it is. See the documentation (included in MSXMLSDK) for a complete list of possible nodeTypes.

This is the function:

ReadChildNodes(CurrentXMLNode : Automation "'Microsoft XML, v4.0'.IXMLDOMNode")
CASE FORMAT(CurrentXMLNode.nodeType) OF

'1': // Element
  BEGIN
    // Global variable CurrentElementName to keep track of what node
    // we are currently processing
    CurrentElementName := CurrentXMLNode.nodeName;

    // Process Attributes
    // If the element has attributes, then browse through those.
    TempXMLAttributeList := CurrentXMLNode.attributes;
    FOR k := 0 TO TempXMLAttributeList.length - 1 DO
      ReadChildNodes(TempXMLAttributeList.item(k));

    // Process Child nodes
    TempXMLNodeList := CurrentXMLNode.childNodes;
    FOR j := 0 TO TempXMLNodeList.length - 1 DO
      ReadChildNodes(TempXMLNodeList.item(j));
  END;

'2': // Attribute
  BEGIN
    MESSAGE(CurrentElementName + ' Attribute : ' +
    FORMAT(CurrentXMLNode.nodeName) + ' = ' + FORMAT(CurrentXMLNode.nodeValue));
  END;

'3': // Text
  BEGIN
    MESSAGE(CurrentElementName + ' = ' + FORMAT(CurrentXMLNode.nodeValue));
  END;
END;

Now, try to run the codeunit against any XML document, and it will give a MESSAGE for each node and each attribute. This codeunit can be used for scanning any XML document from begin to end.

 

Next example:

Now, lets say you are looking for specific nodes in an XML document. As an example, take this document:

- <Document>
- <Order ID="1001">
- <Line>
  10000
  <No>70000</No>
  <Amount>77</Amount>
  </Line>
- <Line>
  20000
  <No>70001</No>
  <Amount>99</Amount>
  </Line>
  </Order>
- <Order ID="1002">
- <Line>
  10000
  <No>70011</No>
  <Amount>12</Amount>
  </Line>
- <Line>
  20000
  <No>70012</No>
  <Amount>45</Amount>
  </Line>
- <Line>
  30000
  <No>70014</No>
  <Amount>37</Amount>
  </Line>
  </Order>
  </Document>

 

One of the good things in MSXML DOM is, that it reads the whole document into memory, so you can jump between nodes, and browse them, and select sub-sets based on node names. So you do not have to read a document from top to bottom if you know exactly which parts of the document you are interested in.

First, we can count how many orders the document contains (see how many "Order"-elements are under the root-element):


OnRun()
CREATE(XMLDoc);
XMLDoc.async(FALSE);
XMLDoc.load('C:\XML\Order.xml');

XMLNodeList := XMLDoc.getElementsByTagName('Document/Order');
MESSAGE('The document has %1 elements called Order',XMLNodeList.length);

This will open the XML document, then use the getElementsByTagName-function to get all elements that match the tag name. Note that the tag name you specify is case sensitive (like everything else in XML)! So in this case, it would not find elements called "order".

Once you have the XMLNodeList, then you can go through the nodes in that list, using the first example.

 

Finally, an example which will read the XML document shown above, and show an Amount-total for each order:


OnRun()
CREATE(XMLDoc);
XMLDoc.async(FALSE);
XMLDoc.load('C:\XML\Order.xml');

XMLNodeList := XMLDoc.getElementsByTagName('Document/Order');
FOR i := 0 TO XMLNodeList.length - 1 DO BEGIN
  NumberDecTotal := 0;
  DOMNode := XMLNodeList.item(i);
  XMLNodeList2 := DOMNode.selectNodes('Line/Amount');
  FOR j := 0 TO XMLNodeList2.length - 1 DO BEGIN
    DOMNode2 := XMLNodeList2.item(j);
    NumberAsText := FORMAT(DOMNode2.text);
    EVALUATE(NumberDec,NumberAsText);
    NumberDecTotal := NumberDecTotal + NumberDec;
  END;
  MESSAGE('Order has total amount of %1.',NumberDecTotal);
END;

 

This example uses DomNode.SelectNodes, which will select all matching nodes. If you are looking for an individual node, then you can use DomNode.selectSingleNode which will only return one node (the first that matches).

In an XML document, everything is Text. So you have to convert to numbers where needed. Final note: Make sure to make your code more robust than this! What if Amount contains a character? What if an element is missing? If you don't take these things into consideration, then your code can fail with very unfriendly errors.

 

These postings are provided "AS IS" with no warranties and confer no rights. You assume all risk for your use. 

 

Best regards


Lars Lohndorf-Larsen (Lohndorf)

Microsoft Dynamics UK

Leave a Comment
  • Please add 5 and 1 and type the answer here:
  • Post
  • I am working with NAV 2009 R2 and ReadChildNodes does not exist.  Thoughts?

  • Read the article?

    <quote>

    So create the function ReadChildNodes.

    The function has one parameter:

    CurrentXMLNode : Automation "'Microsoft XML, v4.0'.IXMLDOMNode"

    </quote>

  • @Sebastiaan Lubbers, thanks!  I noticed that, right after I posted!

    I have another question:

    I am trying to grab two values, from two different elements inside my xml file, and here is what I have so far (it isn't working):

    XMLNodeList := XMLDoc.getElementsByTagName('RatedShipment/Service/Code');

    XMLNodeList2 := XMLDoc.getElementsByTagName('GrandTotal/MonetaryValue');

    FOR i := 1 TO XMLNodeList.length -1 DO BEGIN

     DOMNode := XMLNodeList.item(i);

     upsServiceCodesArray[i] := DOMNode.text;

    END;

    FOR j := 1 TO XMLNodeList2.length  DO BEGIN

       DOMNode2 := XMLNodeList2.item(j);

       upsRatesArray[j] := DOMNode2.text;

    END;

    I tried to put them in an array, to make things easier, but maybe that is not the best route?

  • Without using XPath you can try using getElementsByTagName('Code') and getElementsByTagName('MonetaryValue').

  • PS, you might want to use XmlDoc.selectNodes('RatedShipment/Service/Code').

Page 1 of 1 (5 items)