To define event handlers, two things must be done:
One or more new classes must implement the Asn1NamedEventHandler interface.
Objects of these classes must be created and registered prior to calling the generated decode method for a particular type.
The best way to illustrate this procedure is through examples. We will first show a simple event handler application to provide a customized formatted printout of the fields in a BER message. Then we will show a simple XML converter class that will convert the data in a BER message to XML.
The ASN1C evaluation and distribution kits include a sample program for doing a formatted print of parsed data. This code can be found in the csharp/sample_ber/EventHandler directory. Parts of the code will be reproduced here for reference, but refer to this directory to see the full implementation.
The format for the printout will be simple. Each element name will be printed followed by an equal sign (=) and an open brace ({) and newline. The value will then be printed followed by another newline. Finally, a closing brace (}) followed by another newline will terminate the printing of the element. An indentation count will be maintained to allow for a properly indented printout.
We will first create a class called PrintHandler that implements the Asn1NamedEventHandler interface and handles the formatted printing of the data. The rule for the implementation of interfaces is that you must provide an implementation for each of the methods listed. That is it. You can add as many additional methods, member variables, etc., that you like.
The PrintHandler implementation that we created is as follows:
class PrintHandler : Asn1NamedEventHandler { protected string mVarName; protected int mIndentSpaces = 0; public PrintHandler (string varName) { mVarName = varName; System.Console.Out.WriteLine (mVarName + " = {"); mIndentSpaces += 3; } public void StartElement (string name, int index) { Indent(); System.Console.Out.Write (name); if (index >= 0) System.Console.Out.Write ("[" + index + "]"); System.Console.Out.WriteLine (" = {"); mIndentSpaces += 3; } public void EndElement (string name, int index) { mIndentSpaces -= 3; Indent (); System.Console.Out.WriteLine ("}"); } public void Characters (string svalue, short typeCode) { Indent (); System.Console.Out.WriteLine (svalue); } private void Indent () { for (int i = 0; i < mIndentSpaces; i++) System.Console.Out.Write (" "); } }
In this definition, we chose to add the mVarName and mIndentSpaces member variables to keep track of these items. The user is free to add any type of member variables he or she wants. The only firm requirement in defining this class is the implementation of the methods defined in the interface.
We implement these methods as follows:
In StartElement, we print the name, equal sign, and opening brace:
public void StartElement (string name, int index) { Indent(); System.Console.Out.Write (name); if (index >= 0) System.Console.Out.Write ("[" + index + "]"); System.Console.Out.WriteLine (" = {"); mIndentSpaces += 3; }
In this simplified implementation, we simply indent (this is another private method within the class) and print out the name, equal sign, and opening brace. We then increment the indent level. Logic is also present to check the index value to see if it is zero or greater. If it is, an array subscript is added to the element name.
In EndElement, we simply terminate our brace block as follows:
public void EndElement (string name, int index) { mIndentSpaces -= 3; Indent (); System.Console.Out.WriteLine ("}"); }
The Characters method simply indents and prints the stringified value:
public void Characters (string svalue, short typeCode) { Indent (); System.Console.Out.WriteLine (svalue); }
That completes the PrintHandler class implementation.
Next, we need to create an object of the class and register it prior to invoking the decode method. In the Reader.cs program, the following lines do this:
// Register event handler object PrintHandler printHandler = new PrintHandler ("personnelRecord"); decodeBuffer.AddNamedEventHandler (printHandler);
The addEventHandler method defined in the Asn1DecodeBuffer base class is the mechanism used to do this. Note that event handler objects can be stacked. Several can be registered before invoking the decode function. When this is done, the entire list of event handler objects is iterated through and the appropriate event handling callback function invoked whenever a defined event is encountered.
The implementation is now complete. The program can now be compiled and run. When this is done, the resulting output is as follows:
employee = { name = { givenName = { "John" } initial = { "P" } familyName = { "Smith" } } ...
This can certainly be improved. For one thing it can be changed to print primitive values out in a “name = value” format (i.e., without the braces). But this should provide the general idea of how it is done.
The ASN1C XML Encoding Rules (XER) encode and decode capabilities were presented in an earlier section of this document. An alternate way to create an XML document from ASN.1 data is through the event handler interface.
It turns out that with event handlers, this conversion is fairly easy. As the handler events fire, all of the required symbolic data is passed out to generate an XML document. The programmer is free to massage this data any way he or she wants to comply with whatever DTD or XML Schema is in use.
The ToXML sample program demonstrates the conversion of ASN.1 data to XML using event handlers. The sample is not intended to be a robust implementation – it is merely designed to provide guidance in how one would go about doing this transformation.
The sample program can be found in the csharp/sample_ber/ToXML subdirectory within the ASN1C installation. The complete class definition for the XMLHandler class is as follows:
class XMLHandler : Asn1NamedEventHandler { protected string mVarName; protected int mIndentSpaces = 0; public XMLHandler (string varName) { mVarName = varName; System.Console.Out.WriteLine ("<" + mVarName + ">"); mIndentSpaces += 3; } public void StartElement (string name, int index) { Indent(); System.Console.Out.WriteLine ("<" + name + ">"); mIndentSpaces += 3; } public void EndElement (string name, int index) { mIndentSpaces -= 3; Indent (); System.Console.Out.WriteLine ("</" + name + ">"); } public void Characters (string svalue, short typeCode) { Indent (); string typeName = new string (Asn1Type.getTypeName(typeCode)); typeName.Replace (' ', '_'); System.Console.Out.Write ("<" + typeName + ">"); System.Console.Out.Write (svalue); System.Console.Out.WriteLine ("</" + typeName + ">"); } public void Finished () { System.Console.Out.WriteLine ("</" + mVarName + ">"); } private void Indent () { for (int i = 0; i < mIndentSpaces; i++) System.Console.Out.Write (" "); } }
This is very similar to the PrintHandler class defined earlier. The StartElement method simply opens an XML element block:
public void StartElement (string name, int index) { Indent(); System.Console.Out.WriteLine ("<" + name + ">"); mIndentSpaces += 3; }
The EndElement method closes it:
public void EndElement (string name, int index) { mIndentSpaces -= 3; Indent (); System.Console.Out.WriteLine ("</" + name + ">"); }
The Characters method outputs the data with a type wrapper:
public void Characters (string svalue, short typeCode) { Indent (); string typeName = new string (Asn1Type.getTypeName(typeCode)); typeName.Replace (' ', '_'); System.Console.Out.Write ("<:" + typeName + ">"); System.Console.Out.Write (svalue); System.Console.Out.WriteLine ("</:" + typeName + ">"); }
This illustrates the use of the typeCode argument for obtaining information on the ASN.1 type of the data. Note that this is a simplified version of an XER formatting method. A true implementation would need to do some massaging of the stringified data to fit the XER rules which, in general, do not follow the ASN.1 value formatting rules. The implementation would also need some logic to check if the type wrapper should be output or not; it is not always done in certain cases.
Finally note the constructor and finished method. The constructor prints out the outer-level wrapper tag. Since C# does not have destructors, a finished method is defined to terminate this tag. This method must be called manually from within the application program after the C# decode method. See the Reader.cs program to see how this is done.
Object registration is done as before in the PrintHandler example. The only difference is that an object of the XMLHandler class is created instead of the PrintHandler class.
When compiled and executed, the output from the Reader program looks like this:
<PersonnelRecord> <name> <givenName> <IA5String>'John'</IA5String> </givenName> <initial> <IA5String>'P'</IA5String> </initial> <familyName> <IA5String>'Smith'</IA5String> </familyName> </name> <number> <INTEGER>51</INTEGER> </number> <title> <IA5String>'Director'</IA5String> </title> <dateOfHire> <IA5String>'19710917'</IA5String> </dateOfHire> <nameOfSpouse> <givenName> <IA5String>'Mary'</IA5String> </givenName> <initial> <IA5String>'T'</IA5String> </initial> <familyName> <IA5String>'Smith'</IA5String> </familyName> </nameOfSpouse> <children> <element> <name> <givenName> <IA5String>'Ralph'</IA5String> </givenName> <initial> <IA5String>'T'</IA5String> </initial> <familyName> <IA5String>'Smith'</IA5String> </familyName> </name> <dateOfBirth> <IA5String>'19571111'</IA5String> </dateOfBirth> </element> <element> <name> <givenName> <IA5String>'Susan'</IA5String> </givenName> <initial> <IA5String>'B'</IA5String> </initial> <familyName> <IA5String>'Jones'</IA5String> </familyName> </name> <dateOfBirth> <IA5String>'19590717'</IA5String> </dateOfBirth> </element> </children> </PersonnelRecord>
Add an XML document header and you should be able to display this data in XML-enabled browser.