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 30i 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.
Now, lets say you are looking for specific nodes in an XML document. As an example, take this 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:
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.
Lars Lohndorf-Larsen (Lohndorf)
Microsoft Dynamics UK
I am working with NAV 2009 R2 and ReadChildNodes does not exist. Thoughts?
Read the article?
So create the function ReadChildNodes.
The function has one parameter:
CurrentXMLNode : Automation "'Microsoft XML, v4.0'.IXMLDOMNode"
@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;
FOR j := 1 TO XMLNodeList2.length DO BEGIN
DOMNode2 := XMLNodeList2.item(j);
upsRatesArray[j] := DOMNode2.text;
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').