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.
|
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?"
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.
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.
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.