DotNet XML Tutorial
DotNet XML Tutorial
NET Applications
Student:
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
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.
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.
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
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.
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:
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.
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 (:
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
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
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.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#"
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
The second way is to use the static methods of the class System.Console.
feature {SYSTEM_CONSOLE}.write_line_string (sys_st);
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.
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.
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.
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.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 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>
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
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
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
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.
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.
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.
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 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.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
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 --
-- 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 --
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
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
--