--------------------------------------------------------------------------------------

CopyrightŠ 2001, Software Structures, Inc. All Rights Reserved.

--------------------------------------------------------------------------------------

Serialize into a C Sharp class Data from an Arbitrary XML Node

 

 

---------------------------------

Note: This material assumes that on your machine you have installed Windows 2000, Visual Studio 6 (with Service Pack 4), Visual Studio .NET 7 Beta 1, .NET Framework SDK Beta 1 and SQL Server 2000.

---------------------------------

 

 

Summary

 

     We extend Microsoft's .NET Framework SDK "QuickStart | How do I...? | XML Serialization | Read and Write objects into XML?" procedure for reading from XML data files.  Our technique will serialize into a C# class instance any one record from a multi-record XML stream irrespective of the record's XML node depth.

 

 

 

The Scenario.

 

Our goal is to serialize the XML representation of a data record into an instance of a C# (or VB) class.  As outlined by the QuickStart Tutorials that come with Microsoft's .NET Framework SDK, the following ADO.NET based method may be used.

 

1.   We start from a SQL Server 2000 table creatable by the SQL statement:

 

     CREATE TABLE [dbo].[TestTable] (

        [m_int] [int] NULL

        ) ON [PRIMARY]

     GO

 

     With 3 records:     Column m_int

                            111

                            222

                            333

 

 

2.   From a C# executable use ADO.NET class SQLConnection to open a connection to the SQL Server database.

 

3.   Using an instance of ADO.NET class SQLDataSetCommand read the TestTable records into an instance of ADO.NET class DataSet.

 

4.   Use DataSet.XmlSchema to extract as string the TestTable XSD schema and write it to file TestTable.xsd.  The generated schema is:

 

<xsd:schema id="NewDataSet" targetNamespace="" xmlns="" xmlns:xsd="http://www.w3.org/1999/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">

  <xsd:element name="TestTable">

    <xsd:complexType content="elementOnly">

      <xsd:all>

        <xsd:element name="m_int" minOccurs="0" type="xsd:int"/>

      </xsd:all>

    </xsd:complexType>

  </xsd:element>

  <xsd:element name="NewDataSet" msdata:IsDataSet="True">

    <xsd:complexType>

      <xsd:choice maxOccurs="unbounded">

        <xsd:element ref="TestTable"/>

      </xsd:choice>

    </xsd:complexType>

  </xsd:element>

</xsd:schema>

 

 

5.   Use DataSet.WriteXmlData() to generate file TestTable.xml, an XML representation of the TestTable data records.  That XML file is:

 

<?xml version="1.0" standalone="yes"?>

<NewDataSet>

  <TestTable>

    <m_int>111</m_int>

  </TestTable>

  <TestTable>

    <m_int>222</m_int>

  </TestTable>

  <TestTable>

    <m_int>333</m_int>

  </TestTable>

</NewDataSet>

 

 

6.   Using the .NET tool Xsd.exe generate a C# class representation of a TestTable record.  The Xsd.exe command is:

 

     xsd.exe -classes -language:c# -namespace:  -outputdir:. TestTable.xsd

 

     The generated TestTable.cs is:

 

using System.Xml.Serialization;

[System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)]

public class TestTable {

    [System.Xml.Serialization.XmlElementAttribute("m_int", Form=System.Xml.Serialization.XmlForm.Unqualified, IsNullable=false)]

    public int M_int;

    [System.Xml.Serialization.XmlIgnoreAttribute()]

    public bool M_intSpecified;

         }

 

 

7a.  The C# QuickStart code to do the actual serialization into an instance of class TestTable (Item 6 above) is:

 

     XmlSerializer serializer= new XmlSerializer(typeof(TestTable));

     TextReader reader= new StreamReader("TestTable.xml");

     TestTable refTestTable= (TestTable)serializer.Deserialize(reader); // error

     reader.Close();

 

 

The Problem.

 

Under .NET Beta 1 an exception is thrown at the error line in Item 7a above.  The exception message complains about the "unexpected" <NewDataSet> tag in TestTable.xml (Item 5 above).  If one removes or comments the <NewDataSet> tag and re-executes the Item 7a code, the error does not occur and the TestTable instance is correctly initialized with data from the first XML record (111).  Note, however, that removal of the <NewDataSet> tag renders TestTable.xml an invalid XML file since it lacks a single <Root> node.

 

 

A Solution.

 

7b.  We modify QuickStart's Item 7a code as follows:

 

XmlSerializer serializer= new XmlSerializer(typeof(TestTable));

XmlElementExtractStream refMyFileStream= new XmlElementExtractStream(

   "TestTable.xml"  // string XmlFileToRead

   ,"TestTable"     // string NameOfElementToExtractToStream

   ,0               // int OccurrenceNdxOfElementToExtract

   );

TextReader reader= new StreamReader(refMyFileStream);

TestTable refTestTable= (TestTable)serializer.Deserialize(reader);  // ok

reader.Close();

 

 

8.   Class XmlElementExtractStream subclasses .NET's System.IO.FileStream and supports serialization into a suitable (auto-generated) C# class instance of the data record name-tagged with the value of the NameOfElementToExtractToStream parameter. The OccurrenceNdxOfElementToExtract parameter determines which one record is selected from a multi-record XML stream.

 

9.   The following code implements our C# class XmlElementExtractStream:

 

// XmlElementExtractStream.cs begin

 

namespace Sx1NetXMLSupportLib0CSharp

{

    using System;

    using System.Diagnostics;

 

    public class XmlElementExtractStream : System.IO.FileStream

    {

      private string m_XmlFileToRead;

      private string m_NameOfElementToExtractToStream;

      private int m_OccurrenceNdxOfElementToExtract;

 

      private int m_FileSize;

      private byte[] m_FileByteImage;

      private bool m_bDidReadEntireFile;

 

      private bool m_bDidLocateElementOccurrence;

      private int m_nFileByteOffsetOfElementOccurrence;

      private int m_nFileByteCountSizeOfEntireElementSubstring;

 

      private int m_nOnReadOffsetOfNextFileByteToReturn;

      private int m_nOnReadByteCountRemaining;

 

      public XmlElementExtractStream(

          string XmlFileToRead

          ,string NameOfElementToExtractToStream

          ,int OccurrenceNdxOfElementToExtract 

          ) : base(

                  XmlFileToRead

                  ,System.IO.FileMode.Open

                  ,System.IO.FileAccess.Read

                  ,System.IO.FileShare.ReadWrite

                  )

      {

        m_XmlFileToRead = XmlFileToRead;

        m_NameOfElementToExtractToStream = NameOfElementToExtractToStream;

        m_OccurrenceNdxOfElementToExtract = OccurrenceNdxOfElementToExtract;

 

        m_FileSize = (int)base.Length;

        Debug.Assert(0 < m_FileSize);

        m_bDidReadEntireFile = false;

 

        m_bDidLocateElementOccurrence = false;

        m_nFileByteOffsetOfElementOccurrence = -1;

        m_nFileByteCountSizeOfEntireElementSubstring = 0;

       

        m_nOnReadOffsetOfNextFileByteToReturn = -1;

        m_nOnReadByteCountRemaining = 0;

 

      }  // end XmlElementExtractStream()

       

      override public int Read(

          byte[] buffer

          ,int offsetInBufferAtWhichToWrite

          ,int nMaxCountBytesToWrite

          )

      {

        int nRetVal = 0;  // default init

       

        Debug.Assert(0 < nMaxCountBytesToWrite);

        Debug.Assert(0 <= offsetInBufferAtWhichToWrite);

 

        if(!m_bDidReadEntireFile)

        {

          m_bDidReadEntireFile = true;  // => in this 'if' block only once

         

          m_FileByteImage = new byte[m_FileSize];

          int nCountRead= base.Read(m_FileByteImage, 0, m_FileSize);

          Debug.Assert(nCountRead == m_FileSize);

        }

 

        if(!m_bDidLocateElementOccurrence)

        {

          m_bDidLocateElementOccurrence = true;  // => in this 'if' only once

         

          //locate element occurrence

           m_nFileByteOffsetOfElementOccurrence =

              FindFileByteOffsetOfElementOccurrence(

                 ref m_FileByteImage

                 ,m_NameOfElementToExtractToStream

                 ,m_OccurrenceNdxOfElementToExtract

                 ,ref m_nFileByteCountSizeOfEntireElementSubstring

                 );

         

          m_nOnReadOffsetOfNextFileByteToReturn =

              m_nFileByteOffsetOfElementOccurrence;

          m_nOnReadByteCountRemaining =

              m_nFileByteCountSizeOfEntireElementSubstring;

        }

 

        if(0 <= m_nFileByteOffsetOfElementOccurrence &&

            0 < m_nFileByteCountSizeOfEntireElementSubstring)

        {

          //respond to this current Read() request

          int nByteCountToReturn= Math.Min(

              nMaxCountBytesToWrite, m_nOnReadByteCountRemaining);

          if(0 > nByteCountToReturn)

          {

            nByteCountToReturn = 0;

          }

 

          if(0 < nByteCountToReturn)

          {

            for(int jjj= 0 ; jjj < nByteCountToReturn ; ++jjj)

            {

              buffer[offsetInBufferAtWhichToWrite + jjj] =

                  m_FileByteImage[

                      jjj + m_nOnReadOffsetOfNextFileByteToReturn];

            }

            nRetVal = nByteCountToReturn;

            m_nOnReadOffsetOfNextFileByteToReturn +=

                nByteCountToReturn;

            m_nOnReadByteCountRemaining -=

                nByteCountToReturn;

          }

        }

 

        return nRetVal;

      }  // end Read(byte[], int, int)

 

      private int FindFileByteOffsetOfElementOccurrence(

          ref byte[] refFileByteImage

          ,string ElementName

           ,int nNdxOffsetOfElementOccurrence

           ,ref int refnByteCountSizeOfEntireElementSubstring

           )

      {

        int nRetVal= -1;  // default init

      

        refnByteCountSizeOfEntireElementSubstring = -1;  // default init

 

        Debug.Assert(0 <= nNdxOffsetOfElementOccurrence);

 

        int tempFileByteOffsetOfElementOccurrence= 0;

        int cumulativeNdxOffsetOfElementOccurrence= -1;

 

        string FileStringImage;

      

        unsafe

        {

          fixed(byte* pFileByteImage = refFileByteImage)

          {

            // to see the value with the debugger

            fixed(sbyte* pFileSByteImage= (sbyte*)pFileByteImage)

            {

              Debug.Assert(null != pFileSByteImage && 0 != *pFileSByteImage);

              FileStringImage = new string(pFileSByteImage);

            }

          }

        }

 

        string SubStrToFind= "<";

        SubStrToFind += ElementName;

        SubStrToFind += ">";                 

               

        int nPos0, nPos1;

        nPos0 = 0;

        while(true)

        {

          nPos0 = 1 + FileStringImage.IndexOf(SubStrToFind, nPos0);

          if(nPos0 <= 0)

          {

            // here, => still looking and didnt find it

            goto LblReturnToCaller;

          }

 

          tempFileByteOffsetOfElementOccurrence = nPos0 - 1;

          cumulativeNdxOffsetOfElementOccurrence += 1;

          if(cumulativeNdxOffsetOfElementOccurrence ==

              nNdxOffsetOfElementOccurrence)

          {

            break;  // while()

          }

        }

 

        if(cumulativeNdxOffsetOfElementOccurrence ==

            nNdxOffsetOfElementOccurrence)

        {

          // found it, now need length

          SubStrToFind= "</";

          SubStrToFind += ElementName;

          SubStrToFind += ">";                 

 

          nPos1 = 1 + FileStringImage.IndexOf(SubStrToFind, nPos0);

          if(nPos1 <= 0)

          {

            goto LblReturnToCaller;

          }

   

          nRetVal = tempFileByteOffsetOfElementOccurrence;

          refnByteCountSizeOfEntireElementSubstring =

              nPos1 - nPos0 + 1 + SubStrToFind.Length - 1;

         

          // debugging

          //string str11;

          //str11 = FileStringImage.Substring(

          //            tempFileByteOffsetOfElementOccurrence,

          //            refnByteCountSizeOfEntireElementSubstring

          //            );

          //int dummy=0;

        }

  

      LblReturnToCaller:    

        return nRetVal;

      }  // end FindFileByteOffsetOfElementOccurrence()

     

    }

}

// XmlElementExtractStream.cs end

 

 

 

Done.

 

Rafael Pena

rpenaphd@worldnet.att.net

4/01/2001

 

Keywords: HowTo, How To