0% found this document useful (0 votes)
58 views

DotNet XML Tutorial

This document presents work on serializing Eiffel objects to XML format using .NET classes. It provides an introduction to using Eiffel.NET and the Envision plugin in Visual Studio to create .NET applications. It also covers using reflection to investigate and manipulate types at runtime, and using XML classes to create and parse XML documents. The main topic is an XML serialization application that uses reflection and XML classes to store any Eiffel object in an XML file.

Uploaded by

Helen Gray
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
58 views

DotNet XML Tutorial

This document presents work on serializing Eiffel objects to XML format using .NET classes. It provides an introduction to using Eiffel.NET and the Envision plugin in Visual Studio to create .NET applications. It also covers using reflection to investigate and manipulate types at runtime, and using XML classes to create and parse XML documents. The main topic is an XML serialization application that uses reflection and XML classes to store any Eiffel object in an XML file.

Uploaded by

Helen Gray
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 58

COSC 4080 Computer Science Project Serializing object in XML format in Eiffel.

NET Applications

Student:

Jonathan Amir cs983037

Professor: Jonathan Ostroff January 13, 2003

Table of content 1. Introduction 2. The .NET Framework


2.1 Structure of a .NET application

3. Eiffel.NET
3.1 Using .NET classes in an Eiffel project
3.1.1 The Object Browser

3.2 Using custom C# classes in an Eiffel application 3.3 Using Eiffel classes in a C# application 3.4 Common problems (and their solutions)
3.4.1 Manipulating strings 3.4.2 Output 3.4.3 .NET arrays

4. An introduction to .NET Reflection


4.1 Investigating types 4.2 Getting fields values 4.3 Setting fields values 4.4 Instantiating objects

5. An introduction to .NET XML classes


5.1 XmlWriter and XmlTextWriter
5.1.1 Creating a blank XML document 5.1.2 Creating a XML document with some data

5.2 XmlReader and XmlTextReader


5.2.1 Understanding the cursor position 5.2.2 Ignoring whitespaces 5.2.3 Parsing element nodes 5.2.4 Skipping unwanted nodes

6. The XML Serialization application


6.1 The REFLECTOR class 6.2 The XML_OBJECT_WRITER class

6.3 The XML_OBJECT_READER class 6.4 Error handling 6.5 Testing 6.6 Source code
6.6.1 REFLECTOR Class 6.6.2 XML_OBJECT_WRITER Class 6.6.3 XML_OBJECT_READER Class 6.6.4 TEST_CLASS Class

1. Introduction
This document presents my work as part of course COSC 4080 during fall 2002, under the supervision of Professor Jonathan Ostroff. The initial purpose of the project was to write a reusable component to store Eiffel objects in XML format, and also to investigate a new product from Eiffel Software, namely Eiffel Envision. Eiffel Envision is a plug-in for Visual Studio.NET that facilitate the writing, debugging and testing of Eiffel code in Visual Studio.NET. Needless to say, the integration between Eiffel and the .NET Framework (hereinafter referred to as Eiffel.NET) is not just at the GUI level. Eiffel is now one of the few languages that are .NET compatible. This means that programmers can compile Eiffel code to run on the .NET Common Language Runtime (more on this issue in the next section). This document is written in the form of a tutorial, and it reflects, in a way, the learning curve that I was facing when working on the project. Although the final code for the XML Serialization application is fairly short, I have revised it and restructured it dozens of time; at every step along the way I have learned something new, and realized that the code I have written was wrong, or perhaps it wasnt efficient. These are the topics that are covered in this document: Eiffel.NET and Envision I present a tutorial about the integration between Eiffel and the .NET Framework, including step-by- step examples on how to use Envision, how to create Eiffel.NET applications, how to use C# classes in Eiffel code and how to use Eiffel classes in C# code (two-ways integration). Reflection I introduce the concept of reflection and how it can be used in the .NET Framework. There are a few code samples that demonstrate the power of reflections most of them are written in Eiffel. XML As a part of this project, I learned how to process XML files using the .NET built-in classes. The .NET class library contains an XML API that makes life for the programmer fairly easy, and I use those classes in the XML Serialization application, which is all written in Eiffel. XML Serialization The final deliverable of this project is the XML Serialization application. It is an application that can take any Eiffel object, and store it in an XML file. The application uses both the reflection API and the XML API of the .NET class library.

2. The .NET Framework


The .NET Framework consists of all the components, tools and libraries that are required for writing .NET compatible applications. It provides libraries, such as System.IO, to perform standard I/O tasks, compiler tools to compile your code and browsing tools, such as Ilasm.exe, to manage and deploy your applications. An important part of the .NET Framework is the Common Language Runtime (CLR). The various .NET compilers, such as the C# compiler, compile the written code into a CLR code (also known as the Microsoft Intermediate Language MSIL). One feature of the CLR that will be demonstrated in this document is the language independent nature of the .NET framework. The CLR provides a language independent platform. Components written in different languages can cooperate and run together. One class can inherit from another class written in a different language. In this article I will demonstrate the power of this feature by incorporating classes from .NET libraries into an Eiffel application.

2.1 Structure of a .NET application


Applications that are written for the .NET Framework must be organized in assemblies, and the classes need to be organized in namespaces (assemblies are the equivalent of Java .jar files, and namespaces are the equivalent of Java packages). For example, an integral part of the CLR is an assembly file named mscorlib.dll. This assembly contain many namespaces, including the indispensables System, System.IO and System.Collections, just to name a few.

The following sample code shows a simple C# class defined in a namespace:

This picture is taken from a Visual Studio.NET (VS.NET) project. The next picture shows the projects settings window, in which I defined the project to be compiled as an assembly into a .dll file.

In a normal development environment, many times the developer of a project needs to use resources from other projects. Simply put, a class in project A might need to use a class from project B. To achieve that, project B must be compiled as an assembly and the assemblys .dll file should be copied into project A. VS.NET provides an easy mechanism to do this. From the Solution Explorer window (usually at the top right side of VS.NET), click on the References folder and choose Add Reference, and browse to the dll file of the project that you wish to reference. The referenced project will be compiled as an assembly and the .dll file will be copied into the \bin\debug directory of the current project (In the next chapter there is an example of how to do this). Further more, whenever changes are made to the referenced project the copied dll file to be updated as well.

The following sample code shows a class in one project using a class from another project.

As shown above, .NET applications are organized in assemblies. Normally, each project in VS.NET will map to one assembly file (one dll file). Each assembly contains classes that are organized in various namespaces. The CLR provides implementation of many classes and namespaces such as System, System.IO and more, commonly known as the .NET class library.

3. Eiffel.NET
Imagine that you are given the task of porting the Eiffel programming language to the .NET Framework. What difficulties will you face? First, you will have to extend the Eiffel compiler so it is capable of producing MSIL code, potentially compromising some features of the Eiffel language, such as multiple-inheritance. (This is just a teaser example - Eiffel didnt really loose any functionally on its way to the .NET framework). The list below describes a few of the administrative, or structural challenges, that the Eiffel team faced during the migration, and briefly describes their solutions. These issues will be addressed again later in this document with greater details:

Namespaces: Eiffel has one universe that contains all classes, so classes must have distinct names. In contrast, .NET applications are organized in namespaces, so classes may have the same name (for example, System.String and MyNameSpace.String). The solution for this problem is to convert classes names to a fully qualified name; for example, System.String becomes SYSTEM_STRING. Also, a prefix is added to the classes names of some assemblies; for example, the assembly System.xml has a prefix XML_, so the class XmlReader (XML_READER) it contains becomes XML_XML_READER. Clusters: the other side of the namespaces coin deals with assigning Eiffel classes an appropriate namespaces. By default, classes in the root cluster belong to the namespace RootCluster.Impl, so a class like ROOT_CLASS will become RootCluster.Impl.RootClass. Other clusters below the root cluster will simply have another level of nesting in their namespaces. Standard convention: Eiffels convention dictates that classes names should be capitalized, such as INTEGER. .NET convention dictates that classes names should be Capitalized per word, such as XmlReader. Similarly, an Eiffel feature might be named get_name, where as the same feature in .NET will be named GetName. The Eiffel team made the conversion between notations automatic and seamless.

3.1 Using .NET classes in an Eiffel project


The starting point for an Eiffel.NET developer is to be familiar with the .NET class library, and how to use it in an EIFFEL.NET project. The following example shows how to create an EIFFEL.NET in VS.NET using the Envision plug-in, and shows a simple class that uses other classes from the .NET class library. a) First, you need to create a new Eiffel project. Choose File -> New -> Project -> Eiffel Projects -> Blank Project.

b) Click Finish to choose the default settings for the project.

c) Open the ROOT_CLASS for editing and copy the following code:
class ROOT_CLASS

creation make feature {ANY} make is local input: TEXT_READER; output: TEXT_WRITER; -- this is the traditional eiffel array eiffel_ints: ARRAY[INTEGER]; -- this is the dotnet array dotnet_ints: NATIVE_ARRAY[INTEGER]; do -- this is equivalent of input = System.Console.In input := feature {SYSTEM_CONSOLE}.in; -- this is equivalent of input = System.Console.Out output := feature {SYSTEM_CONSOLE}.out; !!eiffel_ints.make (0,4); -- this is equivalent of int[] dotnet_ints = new int[5] !!dotnet_ints.make (5); dotnet_ints.put (0, 17); -- this line prints "17" output.write_line_integer (dotnet_ints.item (0)); end end -- class ROOT_CLASS

There are a few things to note in the code above:

A .NET instance of type TEXT_WRITER is instantiated in order to refer to the System.Console.Out object, which is the default output stream for .NET applications. Similarly, a TEXT_READER is instantiated as well. The field Out of class System.Console is a static field. Notice that Eiffel uses the notation feature {CLASS_NAME}.feature_name in order to refer the static methods of .NET classes Two arrays are created. The first one is the regular Eiffel array, whose constructor takes two arguments. The second is the .NET array, whose constructor takes only one argument. In a C# code, the array will be created as follows: int[] ints = new ints[5]; In the Eiffel.NET world, this way of constructing the array was converted to a creation routing make with a single argument. In a C# code, accessing the elements of the array is done as follows:

i = ints[3] In the Eiffel.NET world, this way of accessing the array element was simply converted to a featured called item (in .NET arrays, item is the indexer property. For more information about indices, please refer to the .NET documentation). d) Choose Debug -> Run Without Debugging. 3.1.1 The Object Browser The Object browser is an integral part of any VS.NET project. Eiffel.NET projects also use the object browser. However, in an Eiffel.NET project the Object Browser displays two versions the .NET notation and the Eiffel notation - of each class and its methods. This provides a very useful mechanism to browse the .NET class hierarchy, find out what classes are available for use, and view the content of each assembly.

3.2 Using custom C# classes in an Eiffel application


The next step, after understanding how to use .NET classes in your Eiffel code, is to write your own code in C# and use it in an Eiffel application. a) First, you need to create a new C# project. Choose File -> New -> Project -> Visual C# projects - Class Library. This will create a project that is compiled into a dll file.

b) Create a new class called MyPerson.cs and copy the following code:
using System; namespace csharpcode { public class MyPerson { private String _name; private int _age; public MyPerson() { } // a getter and setter methods public String GetName () { return _name; } public void SetName (String name) { _name = name;

} // an overloaded setter method public void SetName (String first, String last) { _name = first + " " + last; } // the preferred alternative to getter and setter methods // is to use a property with get and set clauses public int Age { get { return _age; } set { _age = value; } } } // end of class MyPerson

c) Choose Debug -> Compile. d) Using an explorer window, verify that the file c:\projects\csharpcode\bin\debug\csharpcode.dll exists. e) Choose File -> Close Solution, and follow the instruction from the previous section to create a new Eiffel project, or simply open an existing Eiffel project. f) From the Solution Explorer view, choose your project (eiffelcode1 in this example) -> Properties -> Assembly References. Click the add button to add a local assembly and browse for the csharpcode.dll file (see picture below).

g) Compile the project and then have a look at the Object Browser. The picture below shows what you are expected to see:

There are a few things to note in the picture above:


The class name MyPerson was automatically converted to MY_PERSON. The getter and setter methods, GetName and SetName where converted to get_name and set_name features. The property age was converted to set_age and age features. Remember that in Eiffel there is no way to distinguish between public features and public routines with no parameters. We dont care whether age is a field (feature) or a getter method (routine). Overloaded methods get distinguished names according to their parameters. The method SetName is overloaded. Its first version accepts a String as a parameter, and it corresponds to the feature set_name (SYSTEM_STRING). Its second version accepts two Strings as a parameter, and it corresponds to set_name_string_string (SYSTEM_STRING, SYSTEM_STRING).

h) Open the root class for editing and copy the following code:
make is local person: MY_PERSON; do -- using the MY_PERSON class from csharpcode dll file !!person.make; person.set_age (10); person.set_name_string_string (("Jonathan").to_cil, ("Amir").to_cil); feature {SYSTEM_CONSOLE}.write_line_string (person.get_name); io.read_line; end

For now, dont worry for now if there are some things in the code that are not clear (the to_cil feature, and the feature keyword). These things are explained later on in this document.

3.3 Using Eiffel classes in a C# application


The integration between Eiffel and .NET allows developers two-ways integration. By that I mean to say that it is also possible to use Eiffel classes in a regular .NET application. This is possible because Eiffel code is translated to CLR code. In this section I will show how a code written in C# uses a class that was written in Eiffel. a) Create a new Eiffel application, and choose to set the application as a dll file instead of an exe file, as shown in the picture below.

b) From the solution explorer view, right click the projects name (in this example, eiffeldll), choose properties and modify the fields System Name and Default namespace. The System Name affects the name of the generated dll file, and the Default Namespace affects all the classes in the project. I chose the namespace eiffeldll.people, as shown in the picture below (:

c) Create a new class MY_EIFFEL_PERSON with the following code:


class MY_EIFFEL_PERSON

creation make feature {ANY} make is require do ensure end set_age (new_age: INTEGER) is require new_age_ok: new_age > 0; do age := new_age; ensure age_ok: age = new_age; end get_age (): INTEGER is require do result := age

ensure result_ok: result = age; end set_name (new_name: SYSTEM_STRING) is require new_name_ok: new_name /= void and then new_name.length > 0; do name := new_name; ensure name_ok: name = new_name; end get_name (): SYSTEM_STRING is require do result := name ensure result_ok: result = name; end feature {NONE} age: INTEGER; name: SYSTEM_STRING; end -- class NEW_CLASS

d) Compile the project and make sure that the file eiffeldll.dll exists in the folder C:\projects\eiffeldll\debug\EIFGEN\W_code. e) Create a new C# project as explained earlier, but this time choose a Console Application instead of a Class Library. From the Project Explorer view, choose Project -> Properties -> Referenced Assemblies, and reference the Eiffel project that you created in steps a through d above. f) Reference the following files as well:
C:\Program Files\Eiffel ENViSioN! 1.0 Free Edition\Compiler\ebcl\debug\Eifgen\W_code\ebcl.dll C:\Program Files\Eiffel ENViSioN! 1.0 Free Edition\Compiler\Studio\spec\windows\bin\ ISE.Runtime.dll

A look at the object browser reveals some interesting facts:

Inside the eiffeldll assembly, there are a few namespaces. Mostly important, there are the namespaces Eiffeldll.People.RootCluster and Eiffeldll.People.RootCluster.Impl. The first one contains only interfaces and abstract classes, and the second one contains the actual implementation of the assembly. Also, all the features of the original Eiffel class, including the name of the class itself, were converted to the .NET notation (e.g. get_age became GetAge). f) Create a new class TestEiffelPerson.cs with the following code:
using System; using Eiffeldll.People.RootCluster.Impl; namespace testeiffelperson { class TestEiffelPerson { static void Main(string[] args) { MyEiffelPerson person = new MyEiffelPerson (); person.SetName ("Jonathan Amir"); System.Console.WriteLine (person.GetName ()); } // end of Main } } // end of class

g) Choose Debug -> Run Without Debugging.

As you can see, the program runs without a hitch. The class TestEiffelPerson does not need to know in what language the class MyEiffelPerson was written, because both of these classes all compiled to CLR code.

3.4 Common problems (and their solutions)


Before describing common problems that occur in an Eiffel.NET application, I will quickly describe the Eiffel.NET object hierarchy. In the traditional Eiffel world, all objects inherit ANY. This way, all of the objects in an Eiffel program share common features, such as equal, deep_equal and out, just to name a few. In the .NET world, all objects inherit Object (SYSTEM_OBJECT in Eiffel.NET). This way, all of the objects in a .NET program share common methods, such as ToString and Equals. In the Eiffel.NET world, the class ANY inherits class SYSTEM_OBJECT, so both of the inheritance structures are maintained. Any object that a developer writes in the Eiffel.NET world inherits ANY; hence, it also inherits SYSTEM_OBJECT. The classes in the .NET class libraries, such as SYSTEM_STRING and ARRAY_LIST, inherit SYSTEM_OBJECT directly. They do not inherit ANY. The following diagram presents this structure in a graphical way:

3.4.1 Manipulating strings Most programming languages provide a native, constructor-free way to create and manipulate strings. For example, in C#, the following statements are all valid:
string st = "hello world"; st = st + " from C#"; // now st is "hello world from C#"

Also, in a traditional Eiffel application the following statement is valid:


-- assuming that st is of type STRING st := "Hello World from Eiffel";

However, in an Eiffel.NET application things can be a bit confusing:


-- assuming that st is of type STRING -- and sys_st is of type SYSTEM_STRING st := "Hello World"; sys_st := "Hello World"; -- BAD sys_st := st; -- BAD

As you can see, there is no intuitive way to convert, or switch between Eiffel strings and .NET strings. There are a few ways around this hassle. First, we can extract a SYSTEM_STRING from every object by calling its to_string feature that is inherited from SYSTEM_OBJECT. Hence, the following statements are valid:
-- assuming that i is an INTEGER sys_st := i.to_string;

In addition to that, Eiffel strings have a feature called to_cil that returns a SYSTEM_STRING object. This feature can be used with declared Eiffel strings as well as with native strings, as shown below:
-- assuming that st is of type STRING -- and sys_st is of type SYSTEM_STRING sys_st := st.to_cil; -- the native string must be enclosed by parenthesis sys_st := ("Hello World").to_cil;

The code below demonstrates how to convert a .NET string to an Eiffel string:
-- the Eiffel string now has an additional -- creation routine, make_from_cil !!st.make_from_cil (sys_st);

3.4.2 Output In a traditional Eiffel application, output is achieved using the io feature of class ANY and its related features such as put_string. This way of achieving output is perfectly valid in an Eiffel.NET application, but it is not capable of printing .NET objects that inherit SYSTEM_OBJECT directly. There are a few ways to print a SYSTEM_OBJECT object, such as SYSTEM_STRING, and non of them is clean (it messes up the code). The first way is to convert it to an Eiffel string and then print it using the regular Eiffel output mechanism.
-- assuming that st is of type STRING -- and sys_st is of type SYSTEM_STRING

!!st.make_from_cil (sys_st); io.put_string (st); io.new_line;

The second way is to use the static methods of the class System.Console.
feature {SYSTEM_CONSOLE}.write_line_string (sys_st);

The third way is to use the static Out object of System.Console.


-- assuming that con is of type TEXT_WRITER con := feature {SYSTEM_CONSOLE}.out; con. write_line_string (sys_st); -- assuming that sys_obj is of type SYSTEM_OBJECT con. write_line_object (sys_obj.to_string); con. write_line_integer (i);

3.4.3 .NET arrays There are many methods in the .NET class library that return an array of some type. For example, the ArrayList class has a ToArray method that return an Object[] array, and String class has a ToCharArray that return an char[] array. A close look at the object browser reveals that in the Eiffel.NET world, these features return type is an ARRAY of some type (remember, in Eiffel ARRAY is a parameterized class).

However, the following code is not valid and it will generate an error.
-- assuming that chars is of type ARRAY[CHARACTER] chars := sys_st.to_char_array -- generates an error

The correct way to write this code is to declare chars to be of type NATIVE_ARRAY, rather than of type ARRAY. NATIVE_ARRAY is a parameterized class that, unlike ARRAY, inherits from SYSTEM_ARRAY.
-- assuming that chars is of type NATIVE_ARRAY[CHARACTER] chars := sys_st.to_char_array -- this one is OK

I dont know why the Object Browser presents this wrong information. I suspect that it is a bug in the documentation of the .NET methods signatures.

4. An introduction to .NET Reflection


The .NET Framework contains a System.Reflection namespace that provides reflection capabilities. Using reflection, a developer can inspect objects at runtime without knowing their type in advance. He or she can look at the objects fields, methods, and constructors, change the value of selected fields, and even instantiate objects without knowing their type. In this document I only focus on the reflection of fields; I dont deal with the reflection of methods.

4.1 Investigating types


The Type class (in the System namespace) provides basic ability to investigate the runtime types of objects. All objects have a GetType method that is inherited from Object. Once the type of an object is obtained, we can find out information about the types characteristics (is it an array? is it a primitive object?) as well as about the assembly that the type belongs to. The following code demonstrates a simple reflection of a string and an integer and a complex object.
using System; namespace reflectionExample { class ReflectionSample { static void Main(string[] args) { String st = "Hello"; int i = 5; Automobile auto = new Automobile (); Type stType = st.GetType (); Type iType = i.GetType (); Type autoType = auto.GetType (); // print "System.String" System.Console.WriteLine (stType.FullName); // print "False" System.Console.WriteLine (stType.IsPrimitive); // print "System.Int32" System.Console.WriteLine (iType.FullName); // print "True" System.Console.WriteLine (iType.IsPrimitive); // print "reflectionExample.Automobile" System.Console.WriteLine (autoType.FullName); // print "false" System.Console.WriteLine (autoType.IsPrimitive); } // end of Main } } // end of class

Similarly, reflection also works in an Eiffel.NET application:


make is local st: STRING; sys_st: SYSTEM_STRING; i: INTEGER; auto: AUTOMOBILE; con: TEXT_WRITER; do con := feature {SYSTEM_CONSOLE}.out; st := "hello"; sys_st := ("hello").to_cil; i := 5; !!auto.make; -- print "BaseNet.Kernel.Dotnet.Impl.String" con.write_line_string (st.get_type.full_name); -- print "False" con.write_line_boolean (st.get_type.is_primitive); -- print "System.String" con.write_line_string (sys_st.get_type.full_name); -- print "False" con.write_line_boolean (sys_st.get_type.is_primitive); -- print "System.Int32" con.write_line_string (i.get_type.full_name); -- print "True" con.write_line_boolean (i.get_type.is_primitive); -- print "RootCluster.Impl.Automobile" con.write_line_string (auto.get_type.full_name); -- print "False" con.write_line_boolean (auto.get_type.is_primitive); io.read_line; end

From this point on, I will only present sample code in Eiffel rather than in C#, but needless to say, all the information that is presented here works in a C# application as well.

4.2 Getting fields values


The following code segment demonstrates how we can use the TYPE class to obtain information about the fields of an object:
make is local i: INTEGER; con: TEXT_WRITER; person: PERSON; type: TYPE; field_infos: NATIVE_ARRAY[FIELD_INFO]; field_info: FIELD_INFO; do con := feature {SYSTEM_CONSOLE}.out; !!person.make; type := person.get_type; field_infos := type.get_fields_binding_flags (feature {BINDING_FLAGS}.instance | feature {BINDING_FLAGS}.public | feature {BINDING_FLAGS}.non_public); from i := 0; until i = field_infos.length loop field_info := field_infos.item (i); io.put_string ("Field name: "); con.write_string (field_info.name); io.put_string (", Field type: "); con.write_line_string (field_info.field_type.full_name); i := i + 1; end io.read_line; end

The output from the code above is as follows:


Field Field Field Field Field name: name: name: name: name: $$____type, Field type: ISE.Runtime.EIFFEL_DERIVATION $$name, Field type: BaseNet.Kernel.Dotnet.String $$nickname, Field type: System.String $$age, Field type: System.Int32 $$dad, Field type: RootCluster.Person

There are a few things to note in the code above: All the fields of objects that are written in Eiffel start with two dollar signs (for example $$age). All the objects that are written in Eiffel contain a field named $$____type of type EIFFEL_DERIVATION. I dont know what this field is used for; in the XML Serialization application that is presented later, you will see that I ignore this field. The following code segment demonstrates how to obtain the value of a particular field:
person.set_name ("Jonathan"); type := person.get_type;

field_info := type.get_field_string_binding_flags (("$$name").to_cil, feature {BINDING_FLAGS}.instance | feature {BINDING_FLAGS}.public | feature {BINDING_FLAGS}.non_public); st ?= field_info.get_value (person); -- print "Jonathan" io.put_string (st);

Notice the assignment attempt when calling the get_value routine. Because of its nature, the routine returns a SYSTEM_OBJECT type.

4.3 Setting fields values


The following code shows how to set a field of an object using reflection:
person.set_name ("Jonathan"); type := person.get_type; field_info := type.get_field_string_binding_flags (("$$name").to_cil, feature {BINDING_FLAGS}.instance | feature {BINDING_FLAGS}.public | feature {BINDING_FLAGS}.non_public); field_info.set_value (person, "George"); -- print "George" io.put_string (person.name);

4.4 Instantiating objects


The following code shows how to instantiate an object of a type that is unknown during runtime:
assembly_name := ("sample").to_cil; type_name := ("RootCluster.Impl.Person").to_cil; person ?= (feature {ACTIVATOR}.create_instance_string_string (assembly_name, type_name)).unwrap;

The ACTIVATOR class offers a tool to create instances of types without knowing the type in advance. The create_instance static routine attempts to invoke the default constructor of class PERSON. If class PERSON doesnt have an empty, zero-arguments constructor visible an exception will be thrown. This fact is crucial for the XML Serialization application. As I will explain later, users of the XML Serialization application can only serialize objects for which they explicitly provide a default, zero-arguments constructor.

5. An introduction to .NET XML classes


In recent years the use of XML has been widely spread, and today XML is considered the standard for many technologies; various applications, such as web servers, use XML syntax for their configuration files. XML is also used in Databases technologies. The .NET class library contains the System.Xml namespace that provides classes to read and write XML documents, as well as more advance classes to support W3C standards, such as DOM. For more information about these standards, please visit http://msdn.microsoft.com/library/default.asp?url=/library/enus/cpguide/html/cpconemployingxmlinnetframework.asp There are quite a few ways to process XML documents. The Document Object Model (DOM) loads an entire XML document into memory and provides tool to traverse through the document forward and backward. This model is very flexible but is also very memory consuming. An alternative to DOM is to process XML documents in a forward only, read-only manner, maintaining a cursor to know the current position in the document. This way is less flexible then that of the DOM, but it is a lot faster and cheaper (requires less memory). As a side note, allow me to quickly mention that there are two models for forward only reading of XML documents the push model and the pull model. The Simple API for XML (XML) uses the push model. In contrast to SAX, the .NET XmlReader class uses the pull model. For more information about the differences between the push model and pull model, please visit http://msdn.microsoft.com/library/default.asp?url=/library/enus/cpguide/html/cpconcomparingxmlreadertosaxreader.asp

5.1 XmlWriter and XmlTextWriter


The XmlWriter class is an abstract class that offers fast, forward only means to write XML documents to files or streams. The XmlTextWriter class inherits from XmlWriter and provides an implementation of its abstract base class. In this section I will demonstrate how to use the XmlTextWriter class (in Eiffel code, of course). 5.1.1 Creating a blank XML document The following code demonstrates how to create an initial XML document, which only contains a root element:
make is local x: XML_XML_TEXT_WRITER; do !!x.make_from_filename_and_encoding (("c:\filename.txt").to_cil, feature {ENCODING}.utf8); -- setting indentation to 3 spaces x.set_formatting (feature {XML_FORMATTING}.Indented); x.set_indentation (3); x.write_start_document (); x.write_start_element (("root_element").to_cil); x.write_end_element; x.write_end_document (); -- adding a carriage return at the end of the file x.write_raw (("%N").to_cil); x.close; io.read_line; end

The resulting XML documents is as follows:


<?xml version="1.0" encoding="utf-8"?> <root_element />

5.1.2 Creating a XML document with some data The following code segment demonstrates how to create an initial XML document with some data in it:
x.write_start_document (); x.write_start_element (("Person").to_cil); x.write_attribute_string (("type").to_cil, ("RootCluster.Impl.Person").to_cil); x.write_attribute_string (("student").to_cil, ("true").to_cil); x.write_start_element (("name").to_cil); x.write_string (("Jonathan Amir").to_cil); x.write_end_element; x.write_start_element (("nickname").to_cil); x.write_attribute_string (("void").to_cil, ("true").to_cil); x.write_end_element; x.write_start_element (("age").to_cil); x.write_string (("25").to_cil); x.write_end_element; x.write_end_element; x.write_end_document ();

The resulting XML documents is as follows:


<?xml version="1.0" encoding="utf-8"?> <Person type="RootCluster.Impl.Person" student="true"> <name>Jonathan Amir</name> <nickname void="true" /> <age>25</age> </Person>

There are a few things to note in the code above:

The resulting XML document contains elements, attributes and data. Notice how easy it is build this document from scratch using the various features of the XML_XML_TEXT_WRITER class. This class takes away the hassle of text parsing from the programmer, so he or she dont need to worry about issues like syntax correctness and focus on their actual program.

5.2 XmlReader
The XmlReader class is an abstract class that offers fast, forward only means to read XML documents from files or streams. The .NET class library offers three implementations of this class, namely XmlTextReader, XmlNodeReader and XmlValidatingReader classes. In this section I will demonstrate how to use the XmlTextWriter class. 5.2.1 Understanding the cursor position Consider the following XML document:
<?xml version="1.0" encoding="utf-8"?> <Person type="RootCluster.Impl.Person" student="true"> <name>Jonathan Amir</name> <nickname void="true" /> <age>25</age> </Person>

The following code segment creates an XmlReader object that reads this document from a file.
local do x: XML_XML_TEXT_READER; !!x.make_from_url (("c:\filename.txt").to_cil);

The reader object behaves in a similar fashion to an enumerator. It has a cursor, and upon creation the cursor is positioned just before the first element. Every time that the cursor is advanced forward, it points to a different piece of the document. Those pieces are referred to as nodes, and are represented by the XmlNode class. At each step, the cursor points to a different XmlNode of the document. In order to move the cursor forward you need to use the read feature of the reader object, as demonstrated by the following code:
!!x.make_from_url (("c:\filename.txt").to_cil); from bool := x.read; until bool = false loop io.put_string ("Node name: "); con.write_string (x.name); io.put_string (", type: "); con.write_line_string (x.node_type.to_string); bool := x.read; end

After the first call, the cursor is positioned on the XML declaration of the document.
<?xml version="1.0" encoding="utf-8"?> <Person type="RootCluster.Impl.Person" student="true"> <name>Jonathan Amir</name> <nickname void="true" /> <age>25</age> </Person>

After the third call, the cursor is positioned on the first element of the document.
<?xml version="1.0" encoding="utf-8"?> <Person type="RootCluster.Impl.Person" student="true"> <name>Jonathan Amir</name> <nickname void="true" /> <age>25</age> </Person>

The output of the previous code is as follows:


Node Node Node Node Node Node Node Node Node Node Node Node Node Node Node Node name: name: name: name: name: name: name: name: name: name: name: name: name: name: name: name: xml, type: XmlDeclaration , type: Whitespace Person, type: Element , type: Whitespace name, type: Element , type: Text name, type: EndElement , type: Whitespace nickname, type: Element , type: Whitespace age, type: Element , type: Text age, type: EndElement , type: Whitespace Person, type: EndElement , type: Whitespace

There are a few things to note in the output above:


Each XmlNode has a name, value, and potentially some attributes (more on attributes in the next chapter). Notice that whitespaces, such as tabs, line-feed characters or simply spaces are considered as valid nodes (more on this issue in the next section). The read routine returns false when it cannot read anymore from the file.

5.2.2 Ignoring whitespaces As you can see from the example above, whitespaces are not ignored during the processing of an XML document. This is often not necessary and can be even annoying sometimes. For this reason, the XmlReader class allows the programmer to change this behaviour by specifying that whitespaces should be ignored, as the following code demonstrates:
!!x.make_from_url (("c:\filename.txt").to_cil); x.set_whitespace_handling (feature {XML_WHITESPACE_HANDLING}.none); from bool := x.read; until bool = false loop io.put_string ("Node name: "); con.write_string (x.name); io.put_string (", type: "); con.write_line_string (x.node_type.to_string); bool := x.read; end

The output of the previous code is as follows:


Node Node Node Node Node Node Node Node Node Node name: name: name: name: name: name: name: name: name: name: xml, type: XmlDeclaration Person, type: Element name, type: Element , type: Text name, type: EndElement nickname, type: Element age, type: Element , type: Text age, type: EndElement Person, type: EndElement

5.2.3 Parsing element nodes Perhaps the most interesting node in an XML document is the element node. An element node, aside from its name, can also contain attributes. Consider the following XML document, and consider a reader with a cursor positioned on the highlighted element node:
<?xml version="1.0" encoding="utf-8"?> <Person type="RootCluster.Impl.Person" student="true"> <name>Jonathan Amir</name> <nickname void="true" /> <age>25</age> </Person>

The following code segment demonstrates how to read the attributes of this element node:
from until loop bool := x.move_to_first_attribute; bool = false io.put_string ("Attribute name: "); con.write_string (x.name); io.put_string (", value: "); con.write_line_string (x.value); bool := x.move_to_next_attribute;

end

The output of the previous code is as follows:


Attribute name: type, value: RootCluster.Impl.Person Attribute name: student, value: true

5.2.4 Skipping unwanted nodes Sometimes the programmer is not interested in any node in the document. For example, comments can usually appear anywhere in the document, but they are not considered part of the data and the programmer would like the reader to skip them. For example, consider the following XML document, and consider a reader with a cursor positioned on the highlighted element node:
<?xml version="1.0" encoding="utf-8"?> <Person type="RootCluster.Impl.Person" student="true"> <!-- this is a comment --> <!-- this is another comment --> <!-- and yet one more comment --> <name>Jonathan Amir</name> <nickname void="true" /> <age>25</age> </Person>

Clearly, the programmer doesnt need to know about all the comments between the current element and the next element. Using the read routine alone is not sufficient because it will position the cursor on the first comment. The XmlReader class offers a method MoveToContent that skips comments, whitespaces, XML declarations and other XML nodes that are not content, and positions the cursor on the next available content node (Element, EndElement, CDATA, EntityReference and EndEntity) The following code demonstrates how to skip unwanted nodes:
from until loop bool := x.read; bool = false io.put_string ("Node name: "); con.write_string (x.name); io.put_string (", type: "); con.write_line_string (x.node_type.to_string); bool := x.read; -- assuming that node_type is of type XML_XML_NODE_TYPE node_type := x.move_to_content;

end

6. The XML Serialization application


The goal of the XML Serialization application is to provide a simple, universal tool to serialize Eiffel objects in XML format. This tool combines the two features of the .NET Framework that were described in this document, and puts them at work together. Taking a fairly simplistic view of the Eiffel.NET world, here are the steps necessary to save an object in XML: a) Using reflection, obtain a list of all the fields (public and private) that the object contains. b) Using the XML classes, create an XML document that represents the file, and write each field as an Element node. c) Repeat step b recursively for each complex field that contains other fields in it. In order to read an object from an XML document, one should simple reverse the process that is described above. First read information from the XML file, and as you go allow dynamically create to necessary objects. The XML Serialization application is not long. It only contains a few classes, but this is only because it uses existing tools from the .NET class libraries. There are a few limitations and restrictions that users of the XML Serialization should be aware of. First, users can only attempt to serialize and deserialize objects that have a default constructor that is, a constructor with no arguments (a simple make creation routine). Second, it is not possible to save objects that contain circular references. Given the simplistic view presented above, circular references will cause an infinite loop. Third, references to the same objects (e.g. to index locations in an array referring to the same actual object) will cause the object to be cloned. Two copies of the object will be saved in the XML file, one in each index location. The next few sections provide a high level description of the classes in the XML Serialization application. The actual source code is provided at the end of this document. (To thoroughly understand the code, I recommend that you read the comments in the source code).

6.1 The REFLECTOR class


The REFLECTOR class offers the necessary routines that the writer and reader classes need to use.
get_fields_names (obj: SYSTEM_OBJECT): NATIVE_ARRAY[SYSTEM_STRING]

This routine returns a NATIVE_ARRAY containing the names of all the fields of obj after removing the leading $$ from those names, and ignores the field name "____type" of type EIFFEL_DERIVATION. The reason that the $$ are removed is because of the W3C XML standards. Elements' names in XML documents can not start with a $ sign!!
get_field_type (obj: SYSTEM_OBJECT; name: SYSTEM_STRING): TYPE

This routine returns the type of a field of an object. It adds the leading $$ signs to the field's name before attempting to discover its type.
get_field_value (obj: SYSTEM_OBJECT; name: SYSTEM_STRING): SYSTEM_OBJECT

This routine returns the value of a field of an object. It adds the leading $$ signs to the field's name before attempting to discover its value. Because this routine returns a SYSTEM_OBJECT value, the callers to this routine should use an assignment attempt (?=) or simply assign the result to a SYSTEM_OBJECT object.
set_field_value (obj: SYSTEM_OBJECT; name: SYSTEM_STRING; value: SYSTEM_OBJECT)

This routine sets the value of a field in an object. If the type of value doesn't conform to the type of the field, an exception may be thrown.
create_instance (type: TYPE): SYSTEM_OBJECT

This routine uses a type (that can be determined at runtime) and creates an instance of this type by calling its default, zero-arguments constructor.
find_type (assembly_name: SYSTEM_STRING; type_name: SYSTEM_STRING): TYPE

This routine finds a type using the specified assembly_name and type_name. It first tries to instantiate the type using the ACTIVATOR class, and if that fails (no constructor available) it tries to find the type using the TYPE class. The reason for the dual attempts is that the TYPE class has difficulties finding some types that come from Eiffel clusters. Eiffel classes become assemblies during their transition to CLR code, but not all the assemblies are saved to files. Some of those assemblies are stored in memory. The TYPE class can only find types in assemblies that were saved to files.

6.2 The XML_OBJECT_WRITER class


write_object_to_file (obj: SYSTEM_OBJECT; file_name: STRING)

This routine serializes an object to a file using the default utf8 encoding. The object must have a public, default zero-arguments constructor, or else an Exception will be thrown.
write_object_to_stream (obj: SYSTEM_OBJECT; stream: SYSTEM_STREAM)

This routine serializes an object to an output stream using the default utf8 encoding. The object must have a public, default zero-arguments constructor, or else an Exception will be thrown.
write_object_to_text_writer (obj: SYSTEM_OBJECT; text_writer: TEXT_WRITER)

This routine serializes an object to a text writer. There is no need to specify an encoding because text writers already have an encoding set. The object must have a public, default zero-arguments constructor, or else an Exception will be thrown.
write_object_to_stdout (obj: SYSTEM_OBJECT)

This routine serializes an object to standard output. The object must have a public, default zeroarguments constructor, or else an Exception will be thrown.
write_object_to_file_with_encoding (obj: SYSTEM_OBJECT; file_name: STRING; encoding: ENCODING)

This routine serializes an object to a file using the specified encoding. The object must have a public, default zero-arguments constructor, or else an Exception will be thrown.
write_object_to_stream_with_encoding (obj: SYSTEM_OBJECT; stream: SYSTEM_STREAM; encoding: ENCODING)

This routine serializes an object to an output stream using the specified encoding. The object must have a public, default zero-arguments constructor, or else an Exception will be thrown.
do_write_object (obj: SYSTEM_OBJECT; x: XML_XML_TEXT_WRITER)

This routine performs the initial setup of the XML writer, and writes the beginning and the end of the XML document.
write_object (obj: SYSTEM_OBJECT; element_name: SYSTEM_STRING; x: XML_XML_TEXT_WRITER)

This routing is the "hub" of this class. It inspects an object to see if it is a primitive object, a string, a complex object or an array, and then calls the appropriate routine to serialize the object.

write_primitive_object (obj: SYSTEM_OBJECT; element_name: SYSTEM_STRING; x: XML_XML_TEXT_WRITER)

This routine writes a primitive object as a single XML element. For example: <Int32 type="System.Int32" assembly="mscorlib">4</Int32>
write_string_object (obj: SYSTEM_OBJECT; element_name: SYSTEM_STRING; x: XML_XML_TEXT_WRITER)

This routine writes a string object as a single XML element. For example: <String type="System.String" assembly="mscorlib">hello</String> Although strings are complex objects, the nature of XML deals easily with string therefore I decided to make them a special case and save them as primitive objects (pure text) rather than saving their underlying array and other metadata information.
write_complex_object (obj: SYSTEM_OBJECT; element_name: SYSTEM_STRING; x: XML_XML_TEXT_WRITER)

This routine accepts a complex Eiffel object, writes its start element, recursively writes each of the object's fields and writes its end element
write_array_object (obj: SYSTEM_OBJECT; element_name: SYSTEM_STRING; x: XML_XML_TEXT_WRITER)

This routine accepts an array object, writes its start element, recursively writes each of the array's items and writes its end element.

6.3 The Reader class


read_object_from_file (file_name: STRING): SYSTEM_OBJECT

This routine deserializes an object from a file.


read_object_from_stream (stream: SYSTEM_STREAM): SYSTEM_OBJECT

This routine deserializes an object from a stream.


read_object_from_text_reader (text_reader: TEXT_READER): SYSTEM_OBJECT

This routine deserializes an object from a text reader.


do_read_object (x: XML_XML_TEXT_READER): SYSTEM_OBJECT

This routine performs the initial setup of the XML reader, reads the object from the file and returns it to the caller.
read_object (x: XML_XML_TEXT_READER): SYSTEM_OBJECT

This routing is the "hub" of this class. It inspects the current XML Node to see if it represents a primitive object, a string, a complex object or an array, and then calls the appropriate routine to deserialize the object.
read_primitive_object (x: XML_XML_TEXT_READER; object_type: TYPE): SYSTEM_OBJECT

This routine reads a primitive object from a single XML element.


read_complex_object (x: XML_XML_TEXT_READER; object_type: TYPE): SYSTEM_OBJECT

This routine reads a complex element from an XML Node by reading the XML Node and its children recursively.
read_array_object (x: XML_XML_TEXT_READER; object_type: TYPE): SYSTEM_OBJECT

This routine reads an array element from an XML Node by reading the XML Node and its children recursively.

6.4 Error Handling


The XML Serialization application heavily relies on classes from the .NET class library. Those classes may, in the event of an error, throw an Exception. In the Eiffel.NET world, if an Exception is thrown from a .NET class, it will be caught in the Eiffel code as a SYSTEM_EXCEPTION, and can be handled using the Eiffel exception handling mechanism rescue and retry clauses, and use of the EXCEPTIONS class. Therefore, any programmer who wishes to use the XML Serialization application must take into account the possibility that errors may occur. Most likely, errors will occur if the XML file were tampered with, or if IO errors occur.

6.5 Testing
The TEST_CLASS provides a few test cases for the XML Serialization application (primitive cases, string cases, complex case and array case). As such, it also serves as an example for a user guide an example that shows both how to serialize objects and how to deserialize them. The source code for TEST_CLASS can be found at the end of this document. However, to fully understand the operation of the TEST_CLASS, one must view the content of the XML files that are generated by the class. i.txt
<?xml version="1.0" encoding="utf-8"?> <Int32 type="System.Int32" assembly="mscorlib">4</Int32>

d.txt
<?xml version="1.0" encoding="utf-8"?> <Double type="System.Double" assembly="mscorlib">4.5</Double>

b.txt
<?xml version="1.0" encoding="utf-8"?> <Boolean type="System.Boolean" assembly="mscorlib">True</Boolean>

c.txt
<?xml version="1.0" encoding="utf-8"?> <Char type="System.Char" assembly="mscorlib">Y</Char>

st.txt
<?xml version="1.0" encoding="utf-8"?> <String type="BaseNet.Kernel.Dotnet.Impl.String" assembly="ebcl">hello</String>

sys_st.txt
<?xml version="1.0" encoding="utf-8"?> <String type="System.String" assembly="mscorlib">hello</String>

p.txt
<?xml version="1.0" encoding="utf-8"?> <Person type="XmlSerialization.RootCluster.Impl.Person" assembly="xml_serialization_test"> <name type="BaseNet.Kernel.Dotnet.Impl.String" assembly="ebcl">Jonathan</name> <nickname type="System.String" assembly="mscorlib">joni</nickname> <age type="System.Int32" assembly="mscorlib">10</age> <dad type="XmlSerialization.RootCluster.Impl.Person" assembly="xml_serialization_test"> <name type="BaseNet.Kernel.Dotnet.Impl.String" assembly="ebcl">Moshe</name> <nickname void="true" /> <age type="System.Int32" assembly="mscorlib">20</age> <dad void="true" /> </dad> </Person>

p2.txt
Same as p.txt

people.txt
<?xml version="1.0" encoding="utf-8"?> <ArrayAny type="BaseNet.Kernel.Dotnet.Impl.ArrayAny" assembly="ebcl"> <lower type="System.Int32" assembly="mscorlib">0</lower> <upper type="System.Int32" assembly="mscorlib">3</upper> <area type="BaseNet.Kernel.Dotnet.Impl.SpecialAny" assembly="ebcl"> <nativeArray type="System.Object[]" assembly="mscorlib"> <Person type="XmlSerialization.RootCluster.Impl.Person" assembly="xml_serialization_test"> <name type="BaseNet.Kernel.Dotnet.Impl.String" assembly="ebcl">Jonathan</name> <nickname type="System.String" assembly="mscorlib">joni</nickname> <age type="System.Int32" assembly="mscorlib">10</age> <dad type="XmlSerialization.RootCluster.Impl.Person" assembly="xml_serialization_test"> <name type="BaseNet.Kernel.Dotnet.Impl.String" assembly="ebcl">Moshe</name> <nickname void="true" /> <age type="System.Int32" assembly="mscorlib">20</age> <dad void="true" /> </dad> </Person> <Person type="XmlSerialization.RootCluster.Impl.Person" assembly="xml_serialization_test"> <name type="BaseNet.Kernel.Dotnet.Impl.String" assembly="ebcl">Jonathan</name> <nickname type="System.String" assembly="mscorlib">joni</nickname> <age type="System.Int32" assembly="mscorlib">10</age> <dad type="XmlSerialization.RootCluster.Impl.Person" assembly="xml_serialization_test"> <name type="BaseNet.Kernel.Dotnet.Impl.String" assembly="ebcl">Moshe</name> <nickname void="true" /> <age type="System.Int32" assembly="mscorlib">20</age> <dad void="true" /> </dad> </Person> <item void="true" /> <Person type="XmlSerialization.RootCluster.Impl.Person" assembly="xml_serialization_test"> <name type="BaseNet.Kernel.Dotnet.Impl.String" assembly="ebcl">Jonathan</name> <nickname type="System.String" assembly="mscorlib">joni</nickname>

<age type="System.Int32" assembly="mscorlib">10</age> <dad type="XmlSerialization.RootCluster.Impl.Person" assembly="xml_serialization_test"> <name type="BaseNet.Kernel.Dotnet.Impl.String" assembly="ebcl">Moshe</name> <nickname void="true" /> <age type="System.Int32" assembly="mscorlib">20</age> <dad void="true" /> </dad> </Person> </nativeArray> </area> <objectComparison type="System.Boolean" assembly="mscorlib">False</objectComparison> </ArrayAny>

people2.txt
Same as people.txt

6.6 Source code


6.6.1 REFLECTOR Class
---- The comments in this class assumes that the reader have read the tutorial -- that accompanies this 4080 project. -class REFLECTOR inherit EXCEPTIONS creation make -- the features in this class can only be used by XML_OBJECT_WRITER and XML_OBJECT_READER feature {XML_OBJECT_WRITER, XML_OBJECT_READER} make is require do ensure end get_fields_names (obj: SYSTEM_OBJECT): NATIVE_ARRAY[SYSTEM_STRING] is -- This routine returns a NATIVE_ARRAY containing the names of all the fields -- of obj after removing the leading $$ from those names, and ignores the field -- name "____type" of type "EIFFEL_DERIVATION". --- The reason that the $$ are removed is because of the W3C XML standards. -- Elements' names in XML documents can not start with a $ sign!! require object_not_void: obj /= void; local type: TYPE; field_infos: NATIVE_ARRAY[FIELD_INFO]; field_info: FIELD_INFO; i: INTEGER; j: INTEGER; do -- getting the type of obj type := obj.get_type; -- getting information about all the fields of the objects, public and private field_infos := type.get_fields_binding_flags (feature {BINDING_FLAGS}.instance | feature {BINDING_FLAGS}.public | feature {BINDING_FLAGS}.non_public); if (field_infos.length = 1) then -- this is the case that the object has no defined fields except "____type" result := void else -- the length of the array is shorter by one because it doesn't include "____type" !!result.make (field_infos.length - 1); from i := 0; j := 0; until i = field_infos.length loop -- getting a fieldinfo from the type field_info := field_infos.item (i); if (not field_info.field_type.name.equals (("EIFFEL_DERIVATION").to_cil)) then -- if the field_info is not "____type" then add it to the result

result.put (j, field_info.name.substring (2)); j := j + 1; end i := i + 1; end -- end of loop end -- end of else ensure end get_field_type (obj: SYSTEM_OBJECT; name: SYSTEM_STRING): TYPE is -- This routine returns the type of a field of an object. It adds the leading $$ signs -- to the field's name before attempting to discover its type. require args_not_void: obj /= void and then name /= void local type: TYPE; tmp_st: SYSTEM_STRING; field_info: FIELD_INFO; do -- getting the type of the object type := obj.get_type; -- adding the $$ at the beginning of the field's name tmp_st := ("$$").to_cil; tmp_st := tmp_st.insert (2, name); -- getting the field_info object field_info := type.get_field_string_binding_flags (tmp_st, feature {BINDING_FLAGS}.instance | feature {BINDING_FLAGS}.public | feature {BINDING_FLAGS}.non_public); -- getting the field's type from field_info result := field_info.field_type; ensure end get_field_value (obj: SYSTEM_OBJECT; name: SYSTEM_STRING): SYSTEM_OBJECT is -- This routine returns the value of a field of an object. It adds the leading $$ signs -- to the field's name before attempting to discover its value. -- Because this routine returns a SYSTEM_OBJECT value, the callers to this routine should -- use an assignment attempt ( ?= ) or simply assign the result to a SYSTEM_OBJECT object. require args_not_void: obj /= void and then name /= void local type: TYPE; tmp_st: SYSTEM_STRING; field_info: FIELD_INFO; do -- getting the type of the object type := obj.get_type; -- adding the $$ at the beginning of the field's name tmp_st := ("$$").to_cil; tmp_st := tmp_st.insert (2, name); -- getting the field_info object field_info := type.get_field_string_binding_flags (tmp_st, feature {BINDING_FLAGS}.instance | feature {BINDING_FLAGS}.public | feature {BINDING_FLAGS}.non_public); -- getting the field's value from field_info (could possibly be void) result := field_info.get_value (obj); ensure end set_field_value (obj: SYSTEM_OBJECT; name: SYSTEM_STRING; value: SYSTEM_OBJECT) is -- This routine sets the value of a field in an object. -- If the type of value doesn't conform to the type of the field, an exception may be thrown. require args_not_void: obj /= void and then name /= void and then value /= void; local type: TYPE; tmp_st: SYSTEM_STRING; field_info: FIELD_INFO; do -- getting the type of the object type := obj.get_type;

-- adding the $$ at the beginning of the field's name tmp_st := ("$$").to_cil; tmp_st := tmp_st.insert (2, name); -- getting the field_info object field_info := type.get_field_string_binding_flags (tmp_st, feature {BINDING_FLAGS}.instance | feature {BINDING_FLAGS}.public | feature {BINDING_FLAGS}.non_public); -- setting the field's value from field_info (could possibly be void) field_info.set_value (obj, value); ensure end create_instance (type: TYPE): SYSTEM_OBJECT is -- This routine uses a type (that can be determined at runtime) and creates an instance of -- this type by calling its default, zero-arguments constructor. require type_not_void: type /= void local do result := feature {ACTIVATOR}.create_instance (type); ensure end find_type (assembly_name: SYSTEM_STRING; type_name: SYSTEM_STRING): TYPE is -- This routine finds a type using the specified assembly_name and type_name. -- It first tries to instantiate the type using the ACTIVATOR class, and if -- that fails (no constructor available) it tries to find the type using the -- TYPE class. -- The reason for the dual attempts is that the TYPE class has difficulties finding -- some types that come from Eiffel clusters. Eiffel classes become assemblies during -- their transition to CLR code, but not all the assemblies are saved to files. Some -- of those assemblies are stored in memory. The TYPE class can only find types in -- assemblies that were saved to files. require args_not_void: assembly_name /= void and type_name /= void; local try: INTEGER; do if (try = 0) then result := (feature {ACTIVATOR}.create_instance_string_string (assembly_name, type_name)).unwrap.get_type; else result := feature {TYPE}.get_type_string (type_name); end ensure rescue if (try = 0) then try := 1; retry; end end invariant invariant_clause: True -- Your invariant here end --

6.6.2 XML_OBJECT_WRITER Class


---- The comments in this class assumes that the reader have read the tutorial -- that accompanies this 4080 project. -class XML_OBJECT_WRITER creation make feature {ANY} make is require do ensure end write_object_to_file (obj: SYSTEM_OBJECT; file_name: STRING) is -- This routine serializes an object to a file using the default utf8 encoding. require object_not_void: obj /= void; file_name_not_void: file_name /= void; local do write_object_to_file_with_encoding (obj, file_name, feature {ENCODING}.utf8); ensure end write_object_to_stream (obj: SYSTEM_OBJECT; stream: SYSTEM_STREAM) is -- This routine serializes an object to an output stream using the default utf8 encoding. require object_not_void: obj /= void; stream_not_void: stream /= void; local do write_object_to_stream_with_encoding (obj, stream, feature {ENCODING}.utf8); ensure end write_object_to_text_writer (obj: SYSTEM_OBJECT; text_writer: TEXT_WRITER) is -- This routine serializes an object to a text writer. There is no need -- to specify an encoding because text writers already have an encoding set. require object_not_void: obj /= void; local x: XML_XML_TEXT_WRITER; do !!x.make_from_w (text_writer); do_write_object (obj, x); x.close; ensure end write_object_to_stdout (obj: SYSTEM_OBJECT) is -- This routine serializes an object to standard output. require object_not_void: obj /= void; local do write_object_to_text_writer (obj, feature {SYSTEM_CONSOLE}.out); ensure end write_object_to_file_with_encoding (obj: SYSTEM_OBJECT; file_name: STRING; encoding: ENCODING) is

-- This routine serializes an object to a file using the specified encoding. require object_not_void: obj /= void; file_name_not_void: file_name /= void; encoding_not_void: encoding /= void local x: XML_XML_TEXT_WRITER; do !!x.make_from_filename_and_encoding (file_name.to_cil, encoding); do_write_object (obj, x); x.close; ensure end write_object_to_stream_with_encoding (obj: SYSTEM_OBJECT; stream: SYSTEM_STREAM; encoding: ENCODING) is -- This routine serializes an object to an output stream using the specified encoding. require object_not_void: obj /= void; stream_not_void: stream /= void; encoding_not_void: encoding /= void local x: XML_XML_TEXT_WRITER; do !!x.make_from_w_and_encoding (stream, encoding); do_write_object (obj, x); x.close; ensure end feature {NONE} do_write_object (obj: SYSTEM_OBJECT; x: XML_XML_TEXT_WRITER) is -- This routine performs the initial setup of the xml writer, and writes -- the beginning and the end of the xml document. require object_not_void: obj /= void; x_not_void: x /= void; local type: TYPE; do -- setting the indentation to 3 spaces. x.set_formatting (feature {XML_FORMATTING}.Indented); x.set_indentation (3); -- writing the beginning of the document (xml declaration) x.write_start_document (); -- serializing the object write_object (obj, obj.get_type.name, x); -- this routine is required for the document to be valid, otherwise an Exception -- is thrown x.write_end_document (); -- adding a carriage return at the end of the file. x.write_raw (("%N").to_cil); x.flush; ensure end write_object (obj: SYSTEM_OBJECT; element_name: SYSTEM_STRING; x: XML_XML_TEXT_WRITER) is -- This routing is the "hub" of this class. It inspects an object to see if it is -- a primitive object, a string, a complex object or an array, and then calls the appropriate -- routine to serialize the object. require object_not_void: obj /= void; x_not_void: x /= void; local type: TYPE; do -- getting the type of the object type := obj.get_type; -- is the object primitive?

if (type.is_primitive) then write_primitive_object (obj, element_name, x); -- is the object an array? elseif (type.is_array) then write_array_object (obj, element_name, x); -- is the object a string? --- Although strings are complex objects, the nature of XML deals easily with string -- therefore I decided to make them a special case and save them as primitive objects -- (pure text) rather than saving their underlying array and other metadata information. elseif (type.full_name.equals (("BaseNet.Kernel.Dotnet.Impl.String").to_cil)) then write_string_object (obj, element_name, x); elseif (type.full_name.equals (("System.String").to_cil)) then write_string_object (obj, element_name, x); else -- The object is a complex object. write_complex_object (obj, element_name, x); end; ensure end write_primitive_object (obj: SYSTEM_OBJECT; element_name: SYSTEM_STRING; x: XML_XML_TEXT_WRITER) is -- This routine writes a primitive object as a single xml element. -- For example: <Int32 type="System.Int32" assembly="mscorlib">4</Int32> require args_not_void: obj /= void and then element_name /= void and then x /= void; type_primitive: obj.get_type.is_primitive; local do -- writing the start element. The name usually denotes the object's type, e.g. <Int32 x.write_start_element (element_name); -- writing the object's type and assembly. This information is required by the -- XML_OBJECT_READER class. e.g. <Int32 type="System.Int32" assembly="mscorlib"> x.write_attribute_string (("type").to_cil, obj.get_type.full_name); x.write_attribute_string (("assembly").to_cil, obj.get_type.assembly.get_name.name ); -- writing the textual data, e.g. "4" x.write_string (obj.to_string); -- writing the end element, e.g. </Int32> x.write_end_element (); ensure end write_string_object (obj: SYSTEM_OBJECT; element_name: SYSTEM_STRING; x: XML_XML_TEXT_WRITER) is -- This routine writes a string object as a single xml element. -- For example: <String type="System.String" assembly="mscorlib">hello</String> --- Although strings are complex objects, the nature of XML deals easily with string -- therefore I decided to make them a special case and save them as primitive objects -- (pure text) rather than saving their underlying array and other metadata information. require args_not_void: obj /= void and then element_name /= void and then x /= void; type_ok: obj.get_type.full_name.equals (("BaseNet.Kernel.Dotnet.Impl.String").to_cil) or else obj.get_type.full_name.equals (("System.String").to_cil); local do -- writing the start element. The name usually denotes the object's type, e.g. <String x.write_start_element (element_name); -- writing the object's type and assembly. This information is required by the -- XML_OBJECT_READER class. e.g. <String type="System.String" assembly="mscorlib" x.write_attribute_string (("type").to_cil, obj.get_type.full_name); x.write_attribute_string (("assembly").to_cil, obj.get_type.assembly.get_name.name );

-- writing the textual data, e.g "hello" x.write_string (obj.to_string); -- writing the end element, e.g. </String> x.write_end_element (); ensure end write_complex_object (obj: SYSTEM_OBJECT; element_name: SYSTEM_STRING; x: XML_XML_TEXT_WRITER) is -- This routine accepts a complex Eiffel object, writes its start element, recursively -- writes each of the object's fields and writes its end element require args_not_void: obj /= void and then element_name /= void and then x /= void; local r: REFLECTOR; fields_names: NATIVE_ARRAY[SYSTEM_STRING]; name: SYSTEM_STRING; type: TYPE; value: SYSTEM_OBJECT; i: INTEGER; do -- writing the start element. The name usually denotes the object's type, e.g. <Person x.write_start_element (element_name); -- writing the object's type and assembly. This information is required by the -- XML_OBJECT_READER class. -- e.g. <Person type="XmlSerialization.RootCluster.Impl.Person" assembly="xml_serialization_test" x.write_attribute_string (("type").to_cil, obj.get_type.full_name); x.write_attribute_string (("assembly").to_cil, obj.get_type.assembly.get_name.name); -- getting all the object's fields, and resursively writing them as well -- now we use the REFLECTOR class! !!r.make; -- getting all the fields' names - thanks to the reflector class we don't need to -- worry about the leading $$ characters. fields_names := r.get_fields_names (obj); if not (fields_names = void) then from i := 0; until i = fields_names.length loop -- recursively traversing through the fields name := fields_names.item (i); value := r.get_field_value (obj, name); if (not (value = void)) then -- only if the field is not void we recurse into it write_object (value, name, x); else -- value is void -- since the field is void, we write something like this: -- <nickname void="true" /> x.write_start_element (name); x.write_attribute_string (("void").to_cil, ("true").to_cil); x.write_end_element (); end; -- end of if-else not void i := i + 1; end end; -- writing the end element, e.g. </Person> x.write_end_element (); ensure end write_array_object (obj: SYSTEM_OBJECT; element_name: SYSTEM_STRING; x: XML_XML_TEXT_WRITER) is -- This routine accepts an array object, writes its start element, recursively -- writes each of the array's items and writes its end element. require

args_not_void: obj /= void and then element_name /= void and then x /= void; object_is_array: obj.get_type.is_array; local arr: SYSTEM_ARRAY; item: SYSTEM_OBJECT; i: INTEGER; do -- casting the object to an array arr ?= obj; -- writing the start element, e.g. <ArrayAny x.write_start_element (element_name); x.write_attribute_string (("type").to_cil, obj.get_type.full_name); x.write_attribute_string (("assembly").to_cil, obj.get_type.assembly.get_name.name); -- getting all the array's items, and writing them as objects as well from i := 0; until i = arr.length loop item := arr.get_value (i); if (item /= void) then -- only if the item is not void we recurse into it write_object (item, item.get_type.name, x); else -- item is void -- since the item is void, we write something like this: -- <item void="true" /> x.write_start_element (("item").to_cil); x.write_attribute_string (("void").to_cil, ("true").to_cil); x.write_end_element (); end; -- end of if not void i := i + 1; end -- end of loop -- writing the end element, e.g. </ArrayAny> x.write_end_element (); ensure end invariant end --

6.6.3 XML_OBJECT_READER Class


---- The comments in this class assumes that the reader have read the tutorial -- that accompanies this 4080 project. -class XML_OBJECT_READER creation make feature {ANY} make is require do ensure end read_object_from_file (file_name: STRING): SYSTEM_OBJECT is -- This routine deserializes an object from a file. require filename_not_void: file_name /= void; local x: XML_XML_TEXT_READER; node_type: XML_XML_NODE_TYPE; do !!x.make_from_url (file_name.to_cil); result := do_read_object (x); x.close; ensure end read_object_from_stream (stream: SYSTEM_STREAM): SYSTEM_OBJECT is -- This routine deserializes an object from a stream. require stream_not_void: stream /= void; local x: XML_XML_TEXT_READER; do result := read_object (x); x.close; ensure end read_object_from_text_reader (text_reader: TEXT_READER): SYSTEM_OBJECT is -- This routine deserializes an object from a text reader. require text_reader_not_void: text_reader /= void; local x: XML_XML_TEXT_READER; do result := read_object (x); x.close; ensure end feature {NONE} do_read_object (x: XML_XML_TEXT_READER): SYSTEM_OBJECT is -- This routine performs the initial setup of the xml reader, -- reads the object from the file and returns it to the caller. require x_not_void: x /= void; local node_type: XML_XML_NODE_TYPE;

do -- we don't want to deal with whitespaces. x.set_whitespace_handling (feature {XML_WHITESPACE_HANDLING}.none); node_type := x.move_to_content; result := read_object (x); ensure end read_object (x: XML_XML_TEXT_READER): SYSTEM_OBJECT is -- This routing is the "hub" of this class. It inspects the current XML Node to see if it -- represents a primitive object, a string, a complex object or an array, and then calls -- the appropriate routine to deserialize the object. require x_not_void: x /= void; x_on_element: x.node_type = feature {XML_XML_NODE_TYPE}.element; local r: REFLECTOR; assembly_name: SYSTEM_STRING; type_name: SYSTEM_STRING; type: TYPE; tmp_st: SYSTEM_STRING; is_void: BOOLEAN; do -- checking if the current XML Node represents a void field. tmp_st := x.get_attribute (("void").to_cil); if (tmp_st /= void) then is_void := feature {BOOLEAN}.parse (tmp_st) end; if (is_void) then -- if it is a void field, no parsing is necessary result := void; -- move the XML Reader to the next element x.read_start_element; else !!r.make; -- getting the assembly and type information for the current XML Node assembly_name := x.get_attribute (("assembly").to_cil); type_name := x.get_attribute (("type").to_cil); -- using this information with the Reflector to obtain the type of -- this object, and then inspect the type type := r.find_type (assembly_name, type_name); -- is the object primitive? if (type.is_primitive) then result := read_primitive_object (x, type); -- is the object an array? elseif (type.is_array) then result := read_array_object (x, type); -- is the object a string? elseif (type_name.equals (("BaseNet.Kernel.Dotnet.Impl.String").to_cil)) then result := read_string_object (x, type); elseif (type_name.equals (("System.String").to_cil)) then result := read_string_object (x, type); else -- The object is a complex object. result := read_complex_object (x, type); end; end -- end of if-else is_void ensure end read_primitive_object (x: XML_XML_TEXT_READER; object_type: TYPE): SYSTEM_OBJECT is -- This routine reads a primitive object from a single xml element. require x_not_void: x /= void; type_ok: object_type /= void and then object_type.is_primitive; x_on_element: x.node_type = feature {XML_XML_NODE_TYPE}.element; local type_name: SYSTEM_STRING; tmp_st: SYSTEM_STRING; do -- getting the string value of this object using the most useful

-- read_element_string routine. This routine reads the string -- value of the current element as well as advancing the XML Reader -- to the nexe element. tmp_st := x.read_element_string; type_name := object_type.full_name; -- according the type of this primitive object, we parse the string -- differently. if (type_name.equals(("System.Byte").to_cil)) then -- parsing the string to a Byte (C# byte). result := feature {INTEGER_8}.parse (tmp_st); elseif (type_name.equals(("System.Int16").to_cil)) then -- parsing the string to an Int16 (C# short). result := feature {INTEGER_16}.parse (tmp_st); elseif (type_name.equals(("System.Int32").to_cil)) then -- parsing the string to an Int32 (C# int). result := feature {INTEGER}.parse (tmp_st); elseif (type_name.equals(("System.Int64").to_cil)) then -- parsing the string to an Int64 (C# long). result := feature {INTEGER_64}.parse (tmp_st); elseif (type_name.equals(("System.Single").to_cil)) then -- parsing the string to a Float (C# float). result := feature {REAL}.parse (tmp_st); elseif (type_name.equals(("System.Double").to_cil)) then -- parsing the string to a Double (C# double). result := feature {DOUBLE}.parse (tmp_st); elseif (type_name.equals(("System.Boolean").to_cil)) then -- parsing the string to a Boolean (C# boolean). result := feature {BOOLEAN}.parse (tmp_st); elseif (type_name.equals(("System.Char").to_cil)) then -- parsing the string to a Char (C# char). result := feature {CHARACTER}.parse (tmp_st); end; ensure type_correct: result.get_type = object_type; end read_string_object (x: XML_XML_TEXT_READER; object_type: TYPE): SYSTEM_OBJECT is -- This routine reads a string object from a single xml element. require x_not_void: x /= void; type_ok: object_type /= void and then (object_type.full_name.equals (("BaseNet.Kernel.Dotnet.Impl.String").to_cil) or else object_type.full_name.equals (("System.String").to_cil)); x_on_element: x.node_type = feature {XML_XML_NODE_TYPE}.element; local type_name: SYSTEM_STRING; tmp_st: SYSTEM_STRING; result_st: STRING; do -- getting the string value of this object using the most useful -- read_element_string routine. This routine reads the string -- value of the current element as well as advancing the XML Reader -- to the nexe element. tmp_st := x.read_element_string; type_name := object_type.full_name; if (type_name.equals (("BaseNet.Kernel.Dotnet.Impl.String").to_cil)) then -- the object is an Eiffel String !!result_st.make_from_cil (tmp_st); result := result_st; elseif (type_name.equals (("System.String").to_cil)) then -- the object is a .NET String result := tmp_st; end ensure type_correct: result.get_type = object_type; end read_complex_object (x: XML_XML_TEXT_READER; object_type: TYPE): SYSTEM_OBJECT is -- This routine reads a complex element from an XML Node by reading the XML Node -- and its children recursively. require

x_not_void: x /= void; o_type_not_void: object_type /= void; o_type_ok: not (object_type.is_primitive or else object_type.is_array or else object_type.full_name.equals (("BaseNet.Kernel.Dotnet.Impl.String").to_cil) or else object_type.full_name.equals (("System.String").to_cil)); x_on_element: x.node_type = feature {XML_XML_NODE_TYPE}.element; local r: REFLECTOR; node_type: XML_XML_NODE_TYPE; name: SYSTEM_STRING; value: SYSTEM_OBJECT; do !!r.make; -- first, let's create an instance of the object that is desires and specified by the -- object_type parameter. result := r.create_instance (object_type); -- looping through all the children of the XML Node if (not x.is_empty_element) then from x.read_start_element; -- skipping unwanted nodes, such as comments. node_type:= x.move_to_content; until x.node_type = feature {XML_XML_NODE_TYPE}.end_element loop -- getting the name of the field name := x.name; -- getting the value of the field (this is an important recursive call) value :=read_object (x); if (value /= void) then -- if the value is not void then we use the reflector to assign the -- value to the right field. No need to deal with the $$ at the -- beginning of the field's name. r.set_field_value (result, name, value); end -- after the call to read_object, the Reader is position after the end -- of the previous element. It is now probably at the beginning of the -- next element, but still - we have to skip unwanted nodes, such as comments. node_type:= x.move_to_content; end -- end of loop x.read_end_element; else x.read_start_element; end ensure end read_array_object (x: XML_XML_TEXT_READER; object_type: TYPE): SYSTEM_OBJECT is -- This routine reads an array element from an XML Node by reading the XML Node -- and its children recursively. require x_not_void: x /= void; o_type_not_void: object_type /= void; o_type_ok: object_type.is_array; x_on_element: x.node_type = feature {XML_XML_NODE_TYPE}.element; local node_type: XML_XML_NODE_TYPE; value: SYSTEM_OBJECT; tmp_list: ARRAY_LIST; tmp_int: INTEGER; do -- creating an ARRAY_LIST because we don't know the size of the array. !!tmp_list.make; -- an array might be empty (of length zero). If the array is empty, its

---if

correspondin XML element will be empty as well - <element /> as opposed <element></element>/ So we need to check the XML element (not x.is_empty_element) then from x.read_start_element; -- skipping unwanted nodes, such as comments node_type:= x.move_to_content; until x.node_type = feature {XML_XML_NODE_TYPE}.end_element loop -- reading the value of the current array item value := read_object (x); -- adding it to the array list, even if it is void tmp_int := tmp_list.add (value); -- skipping unwanted nodes, such as comments node_type:= x.move_to_content; end -- end of loop x.read_end_element; else -- if the array was empty, we simply advance the XML Reader forward. x.read_start_element; end -- obtaining the resulting array from the array list. result := tmp_list.to_array_type (object_type.get_element_type); ensure type_correct: result.get_type = object_type; end invariant end

6.6.4 TEST_CLASS Class


---- The comments in this class assumes that the reader have read the tutorial -- that accompanies this 4080 project. -class TEST_CLASS creation make feature {ANY} io_st: STRING; make is local xw: XML_OBJECT_WRITER; xr: XML_OBJECT_READER; p: PERSON; p1: PERSON; p2: PERSON; people: ARRAY[PERSON]; people2: ARRAY[PERSON]; i: INTEGER; d: DOUBLE; b: BOOLEAN; c: CHARACTER; st: STRING; sys_st: SYSTEM_STRING; obj: SYSTEM_OBJECT; con: TEXT_WRITER; do con := feature {SYSTEM_CONSOLE}.out; !!xw.make; !!xr.make; -- serializing a primitive type INTEGER (System.Int32) i := 4; xw.write_object_to_file (i, "C:\temp\i.txt"); -- serializing a primitive type DOUBLE (System.Double) d := 4.5; xw.write_object_to_file (d, "C:\temp\d.txt"); -- serializing a primitive type BOOLEAN (System.Boolean) b := true; xw.write_object_to_file (b, "C:\temp\b.txt"); -- serializing a primitive type CHARACTER (System.Char) c := 'Y'; xw.write_object_to_file (c, "C:\temp\c.txt"); -- serializing a SYSTEM_STRING (System.String) sys_st := ("hello").to_cil; xw.write_object_to_file (sys_st, "C:\temp\sys_st.txt"); -- serializing an Eiffel String (BaseNet.Kernel.Dotnet.Impl.String) st := "hello"; xw.write_object_to_file (st, "C:\temp\st.txt"); -- serializing a complex Eiffel object Person (XmlSerialization.RootCluster.Impl.Person) !!p.make; p.set_name ("Jonathan");

p.set_nickname (("joni").to_cil); p.set_age (10); !!p1.make; p1.set_name ("Moshe"); p1.set_age (20); p.set_dad (p1); xw.write_object_to_file (p, "C:\temp\p.txt"); -- reading the serialized person and writing it to another file inorder to compare -- the files p2 ?= xr.read_object_from_file ("C:\temp\p.txt"); xw.write_object_to_file (p2, "C:\temp\p2.txt"); -- serializing a Eiffel ARRAY[PERSON] (BaseNet.Kernel.Dotnet.Impl.ArrayAny) !!people.make (0,3); people.put (p,0); people.put (p,1); people.put (p,2); people.put (p,3); xw.write_object_to_file (people, "C:\temp\people.txt"); -- reading the serialized array and writing it to another file inorder to compare -- the files people2 ?= xr.read_object_from_file ("C:\temp\people.txt"); xw.write_object_to_file (people2, "C:\temp\people2.txt"); io.put_string ("done."); io.new_line; io.read_line; end

--

end -- class TEST_CLASS

You might also like