Skip to main content
Omnitracs Knowledge Base

An Example Using .NET

Please note that this is not a complete end-to-end example, but serves to illustrate some recommended best practices. This example uses C#.NET 2005. Key things about this example:

  • The call to Dequeue is not represented here.
  • It is assumed you've already called Dequeue, extracted the transaction block, converted it from base64 to string, and passed it to this function below.
  • We will be looking for T.1.08 transactions (Trailer Position Report), and only that transaction type. This is shown as an illustration. Other transaction types can be processed similarly.
  • We instantiate a custom class named TrailerPosition. This class holds the data for a trailer position, which we extract from the XML, and fill into the TrailerPosition instance. Then we return that instance to the caller.

We'll show you the code first, then call out some important points beneath.

001 private TrailerPosition string parseXML(string sInput)

002 {

003    System.IO.StringReader   stringReader = new System.IO.StringReader(sInput);

004    System.Xml.XmlTextReader xmlReader    = new System.Xml.XmlTextReader(stringReader);

005    xmlReader.WhitespaceHandling          = System.Xml.WhitespaceHandling.None;

006

007    // Read through the XML until an element with name is T.1.08.0 is encountered

008    // indicating a Trailer Position event.

009    bool bInside = false;

010    while (xmlReader.Read())

011    {

012       if (xmlReader.NodeType == XmlNodeType.Element && xmlReader.Name == "T.1.08.0")

013       {

014          // Found a T.1.08.0 element.  Instantiate a TrailerPosition object and

015          // fill it by parsing out the elements and attributes of interest.

016          bInside = true;

017          TrailerPosition trailerPos = new TrailerPosition();

018

019          while (bInside && xmlReader.Read())

020          {

021             switch (xmlReader.NodeType)

022             {

023                case XmlNodeType.Element:

024                {

025                   switch (xmlReader.Name)

026                   {

027

028                      case "eventTS":

029                         trailerPos.EventTS = xmlReader.ReadString();

030                         break;

031

032                      case "equipment":

033                         if (xmlReader.GetAttribute("equipType") == "trailer")

034                         {

035                            trailerPos.SCAC        = xmlReader.GetAttribute("SCAC");

036                            trailerPos.TrailerID   = xmlReader.GetAttribute("ID");

037                            trailerPos.UnitAddress = xmlReader.GetAttribute("unitAddress");

038                         }

039                         break;

040

041                      case "position":

042                         trailerPos.PositionTimestamp = xmlReader.GetAttribute("posTS");

043                         trailerPos.Latitude          = xmlReader.GetAttribute("lat");

044                         trailerPos.Longitude         = xmlReader.GetAttribute("lon");

045                         break;

046

047                   // end switch reader.Name

048

049                   break;

050

051                // end case XmlNodeType.Element

052

053                case XmlNodeType.Text:

054                   break;

055                case XmlNodeType.CDATA:

056                   break;

057                case XmlNodeType.ProcessingInstruction:

058                   break;

059                case XmlNodeType.Comment:

060                   break;

061                case XmlNodeType.XmlDeclaration:

062                   break;

063                case XmlNodeType.Document:

064                   break;

065                case XmlNodeType.DocumentType:

066                   break;

067                case XmlNodeType.EntityReference:

068                   break;

069

070                // The parse continues until an EndElement for the T.1.08.0 Element

071                // is encountered.

072                case XmlNodeType.EndElement:

073                {

074                   if (xmlReader.Name == "T.1.08.0")

075                     bInside = false;

076                }

077

078             // end switch XmlNodeType.Element

079

080          // end while inside T.1.08

081

082       // end if found T.1.08

083

084    // end while reading XML

085

086    return trailerPos;

087

088 // end function parseXML

Using an XML Reader

We use .NET's "XmlTextReader" class, which implements a SAX-like XML parser. This parser is forward-only, and read-only, but we don't need to go backward or modify the XML that we were given. All we want to do is loop through the nodes in the XML document, find the elements that matter to us (T.1.08), and then look deeper.

SAX parsers, including the XmlTextReader, move through the XML document "sequentially", telling you about each node it encounters. The nodes are visited like this:

There's always a root node. Each root node has one or more children. This is an in-order tree traversal, and should be familiar to anyone with a programming background.

Looking for Desired Transaction Type

On line 010 we start looping through the XML document. Each element and attribute is a "node", and each time you issue a Read(), you get the next element or attribute in the document.

On line 012, we ask: "Is the current node an element? Is it a T.1.08 element?"

(red star) Key Point:
With this simple IF statement, we're ignoring all other transaction types. This makes our application flexible. ESS could add ten new transaction types next week, and our parser wouldn't have to change. If ESS ever removed the T.1.08 transaction type (which would never happen), the parser would still be happy. We're scanning for the things we're interested in, and ignoring everything else.

Processing Different Node Types Differently

Once we've identified a T.1.08 element, we move inside, and look deeper.

On line 019 we start looping through the nodes inside the T.1.08 element.

On line 021 we ask "what kind of node is this?" While most nodes will be Elements or Attributes, there are several other node types. In this switch statement, we handle each type of node we can encounter. Notice starting on line 053 we basically ignore a bunch of node types, but if you wanted to handle them, you would do so between lines 053 and 068.

Looking for Specific Nodes by Name

We know from the schema that the T.1.08 element has several sub-elements, like an event timestamp element, an equipment element (telling us whcih trailer sent the report), and a position element.

On line 023 we handle ALL "element" nodes within the T.1.08 element.

On line 025 we ask, "which element am I looking at right now?" Here's where we look for specific elements by their name. If we have the "eventTS" element, we can look deeper. If we have the "position" element, we can look deeper. Knowing the schema is key, knowing what parts of data you want to harvest is also key. Knowing both will tell you how to write your code.

(red star) Key Point:
We're looking for data by its name, not by its position. We're not asking for the third sub-element of the T.1.08 element. That won't always be the "eventTS" element. Looking for things positionally simply isn't the way to do this. You need to look for data by its tag name.

Getting Data Out of Nodes

So now we're inside the T.1.08, and we've found a sub-element within it.

On line 041 we ask, "is this sub-element the position sub-element?" If so, we know from the schema that that element has some attributes. Once again, we ask for things by name, not position, so we can call GetAttribute() to harvest the three attributes we care about, and stuff their values into our local holding variable.

(red star) Key Point:
Once again, flexibility is in play here. We ask for three attributes inside the "position" element. But notice, if the "position" element had five additional attributes, we would gladly ignore them here. We're only interested in the things we are interested in; the rest we ignore, and no matter what "the rest" is, it won't break us.

On line 032 we've identified a sub-element as an "equipment" element. Just to be sure we're dealing with a trailer here (and not a vehicle), we again call GetAttribute for the "equipType" and examine its value. The schema tells us that attribute can be either "trailer" or "tractor". If we have a "tractor", we ignore it. If we have a "backhoe", "cruise ship", or "lunar lander", we ignore them too. Flexibility!

Location Awareness

We use a boolean variable (bInside) to tell us whether we're still inside a T.1.08 element or not. This isn't required, but it's a good exercise. During debugging or testing, you may wish to know whether or not you've encountered the closing tag for an element (ex: </T.1.08> ).

On line 072 we handle the case where we get an "end element" node type. Once again, this isn't required, but is a good idea in case you have several levels of nesting, so that you can know what part of the XML document you're currently in.

  • Was this article helpful?