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

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

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

Serialize From a C Sharp class Data to Replace 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.

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

 

 

Summary

 

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

 

 

 

The Scenario.

 

1.   We begin from an XML data file TestTable.xml:

 

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

 

 

2.   For TestTable.xml we have a XSD Schema file TestTable.xsd:

 

<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>

 

 

3.   For TestTable.xml we also have a C# class TestTable in TestTable.cs:

 

// TestTable.cs begin

 

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;

         }

 

// TestTable.cs end

 

 

4.   Refer to Serialize into a C Sharp class Data from an Arbitrary XML Node for information on how to generate these files starting from a SQL Server table and its SQL Server schema.

 

5a.  The C# QuickStart code to serialize from a class instance to a XML file is:

 

     TestTable refTestTable= new TestTable();  // an instance of class TestTable

 

     // initialize the TestTable instance

     refTestTable.M_int = 444;

     refTestTable.M_intSpecified = true;

 

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

     TextWriter writer= new StreamWriter("NewTestTableRecord.xml");

    serializer.Serialize(writer, refTestTable);

     writer.Close();

 

 

6.   The QuickStart code of Item 5a. generates a new XML file NewTestTableRecord.xml:

 

<?xml version="1.0"?>

<TestTable xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema">

  <m_int>444</m_int>

</TestTable>

 

 

Our Extension.

 

Generally one desires not a new XML data file but rather a flexible ability to manipulate data in an existing XML data file.

 

 

5b.  Our modification of Item 5a. code is the following:

 

     TestTable refTestTable= new TestTable();  // an instance of class TestTable

     // initialize the TestTable instance

     refTestTable.M_int = 444;

     refTestTable.M_intSpecified = true;

 

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

     XmlElementReplaceStream refMyFileStream= new XmlElementReplaceStream(

   "TestTable.xml"      // string XmlFileToModify

   ,"TestTable"         // string NameOfElementToReplaceToStream

   ,1  /* 2nd record */ // int OccurrenceNdxOfElementToReplace

   );

    

     TextWriter writer = new StreamWriter(refMyFileStream);

     serializer.Serialize(writer, refTestTable);

     writer.Close();

 

 

7.   Class XmlElementReplaceStream subclasses .NET's System.IO.FileStream and supports serialization from a suitable (auto-generated) C# class instance replacing the XML data record name-tagged with the value of the NameOfElementToReplaceToStream parameter. The OccurrenceNdxOfElementToReplace parameter determines which one record is replaced in a multi-record XML stream (file).

 

8.   The following code implements our C# class XmlElementReplaceStream:

 

// XmlElementReplaceStream.cs begin

 

namespace Sx1NetXMLSupportLib0CSharp

{

    using System;

    using System.Diagnostics;

    using System.IO;

 

    public class XmlElementReplaceStream : System.IO.FileStream

    {

      private string m_XmlFileToModify;

      private string m_NameOfElementToReplaceToStream;

      private int m_OccurrenceNdxOfElementToReplace;

 

      private int m_FileToModifyInitialSize;

      private byte[] m_FileToModifyInitialByteImage;

      private bool m_bDidReadEntireFileToModify;

 

      private bool m_bDidLocateElementOccurrence;

      private int m_nFileInitialByteOffsetOfElementOccurrence;

      private int m_nFileInitialByteCountSizeOfEntireElementSubstring;

 

      private bool m_bDidStripReplacementPrefix;

      private int  m_nLengthOfReplacementString;

 

      public XmlElementReplaceStream(

          string XmlFileToModify

          ,string NameOfElementToReplaceToStream

          ,int OccurrenceNdxOfElementToReplace 

          ) : base(

                  XmlFileToModify

                  ,System.IO.FileMode.Open

                  ,System.IO.FileAccess.ReadWrite

                  ,System.IO.FileShare.ReadWrite

                  )

      {

        m_XmlFileToModify = XmlFileToModify;

        m_NameOfElementToReplaceToStream =

            NameOfElementToReplaceToStream;

        m_OccurrenceNdxOfElementToReplace =

            OccurrenceNdxOfElementToReplace;

 

        m_FileToModifyInitialSize = (int)base.Length;

        Debug.Assert(0 < m_FileToModifyInitialSize);

        m_bDidReadEntireFileToModify = false;

 

        m_bDidLocateElementOccurrence = false;

        m_nFileInitialByteOffsetOfElementOccurrence = -1;

        m_nFileInitialByteCountSizeOfEntireElementSubstring = 0;

       

        m_bDidStripReplacementPrefix = false;

        m_nLengthOfReplacementString = 0;

      }  // end XmlElementReplaceStream()

       

      override public int Write(

          byte[] BufferWithReplacement

          ,int offsetInBufferWithReplacementAtWhichToRead

          ,int nMaxCountBufferWithReplacementBytesToRead

          )

      {

        int nRetVal = 0;  // default init

       

        Debug.Assert(0 < nMaxCountBufferWithReplacementBytesToRead);

        Debug.Assert(0 <= offsetInBufferWithReplacementAtWhichToRead);

 

        if(!m_bDidReadEntireFileToModify)

        {

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

         

          m_FileToModifyInitialByteImage = new byte[m_FileToModifyInitialSize];

         int nCountRead= base.Read(m_FileToModifyInitialByteImage, 0,

            m_FileToModifyInitialSize);

          Debug.Assert(nCountRead == m_FileToModifyInitialSize);

        }

 

        if(!m_bDidLocateElementOccurrence)

        {

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

         

          //locate element occurrence

          int nNotUsedHere;

          m_nFileInitialByteOffsetOfElementOccurrence =

              FindFileByteOffsetOfElementOccurrence(

                  ref m_FileToModifyInitialByteImage

                  ,m_NameOfElementToReplaceToStream

                  ,m_OccurrenceNdxOfElementToReplace

                  ,out nNotUsedHere

                  ,out m_nFileInitialByteCountSizeOfEntireElementSubstring);

         

          // here, => a copy of the initial file for output is in ram

          // and we know the offset and size of the byte-sub-array to

          // replace.

         

          Debug.Assert(this.CanSeek);

          long lNewPos= base.Seek(

              m_nFileInitialByteOffsetOfElementOccurrence

              ,SeekOrigin.Begin

              );

          Debug.Assert(lNewPos == m_nFileInitialByteOffsetOfElementOccurrence);

 

        }

 

        if(0 <= m_nFileInitialByteOffsetOfElementOccurrence &&

            0 < m_nFileInitialByteCountSizeOfEntireElementSubstring)

        {

     

          int nNdxOffsetOf1stByteAfterElementTag=

              offsetInBufferWithReplacementAtWhichToRead;  // default init

          if(!m_bDidStripReplacementPrefix)

          {

            m_bDidStripReplacementPrefix = true;  // here only once

           

            int nNotUsedHere;

            int nNdxRetVal= FindFileByteOffsetOfElementOccurrence(

                ref BufferWithReplacement

                ,m_NameOfElementToReplaceToStream

                ,0

                ,out nNdxOffsetOf1stByteAfterElementTag

                ,out nNotUsedHere

                );

            Debug.Assert(0 <= nNdxRetVal);

 

            {

              // Write the name of element tag

              string SubStrToWrite= "<";

              SubStrToWrite += m_NameOfElementToReplaceToStream;

              SubStrToWrite += ">";                 

              for(int iii = 0 ; iii < SubStrToWrite.Length ; ++iii) {

                byte byteiii= (byte)SubStrToWrite[iii];

                base.WriteByte(byteiii);

                }

              m_nLengthOfReplacementString += SubStrToWrite.Length;

            }

         

          }  // if(!m_bDidStripReplacementPrefix) end

     

          //respond to this current Write() request

          nRetVal = base.Write(

              BufferWithReplacement

              ,nNdxOffsetOf1stByteAfterElementTag

              ,nMaxCountBufferWithReplacementBytesToRead -

                  nNdxOffsetOf1stByteAfterElementTag +

                  offsetInBufferWithReplacementAtWhichToRead

              );

          m_nLengthOfReplacementString += nRetVal;

        }

 

        return nRetVal;

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

 

      private int FindFileByteOffsetOfElementOccurrence(

          ref byte[] refXmlByteArrayImage

          ,string ElementName

          ,int nNdxOffsetOfElementOccurrence

          ,out int nNdxOffsetOf1stByteAfterElementTag

          ,out int refnByteCountSizeOfEntireElementSubstring)

      {

        int nRetVal= -1;  // default init

       

        nNdxOffsetOf1stByteAfterElementTag = -1;  // default init

        refnByteCountSizeOfEntireElementSubstring = -1;  // default init

 

        Debug.Assert(0 <= nNdxOffsetOfElementOccurrence);

 

        int tempFileByteOffsetOfElementOccurrence= 0;

        int cumulativeNdxOffsetOfElementOccurrence= -1;

 

        string FileStringImage;

      

        unsafe

        {

          fixed(byte* pFileByteImage = refXmlByteArrayImage)

          {

            // to see the value with the debugger

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

            {

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

              FileStringImage = new string(pFileSByteImage);

            }

          }

        }

 

        string SubStrToFind0= "<";

        SubStrToFind0 += ElementName;

               

        int nPos0= 0;

        while(true)

        {

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

 

          char c1stCharAfter=FileStringImage[

              nPos0 - 1 + SubStrToFind0.Length];

 

           // maybe also check for other whitespace ?  

          Debug.Assert(' ' == c1stCharAfter || '>' == c1stCharAfter);

             

          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

 

          string SubStrToFind1= "</";

          SubStrToFind1 += ElementName;

          SubStrToFind1 += ">";                 

 

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

          if(nPos1 <= 0)

          {

            goto LblReturnToCaller;

          }

 

          // now for nNdxOffsetOf1stByteAfterElementTag

          int nOffset2;

          string SubStrToFind2= ">";

          nOffset2 = FileStringImage.IndexOf(SubStrToFind2, nPos0);

          if(nOffset2 < 0)

          {

            goto LblReturnToCaller;  // error

          }

          nNdxOffsetOf1stByteAfterElementTag = nOffset2 + 1;

   

          nRetVal = tempFileByteOffsetOfElementOccurrence;

          refnByteCountSizeOfEntireElementSubstring =

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

         

          // debugging

          // string str11;

          // str11 = FileStringImage.Substring(

          //             tempFileByteOffsetOfElementOccurrence,

          //             refnByteCountSizeOfEntireElementSubstring

          //             );

          // int dummy=0;

        }

  

      LblReturnToCaller:    

        return nRetVal;

      }  // end FindFileByteOffsetOfElementOccurrence()

 

      public override void Close()

      {

        // append trailing piece of original file

        int nOffsetFromWhichToAppend=

            m_nFileInitialByteOffsetOfElementOccurrence +

            m_nFileInitialByteCountSizeOfEntireElementSubstring;    

        int nCountToAppend=

            m_FileToModifyInitialSize - nOffsetFromWhichToAppend;

        if(0 < nCountToAppend)

        {

          base.Write(m_FileToModifyInitialByteImage

              ,nOffsetFromWhichToAppend, nCountToAppend

              );

          base.SetLength(nOffsetFromWhichToAppend +

              nCountToAppend -

              m_nFileInitialByteCountSizeOfEntireElementSubstring +

              m_nLengthOfReplacementString

              );

        }

        base.Close();

      }

 

      override public int GetHandle()

      {

        Debug.Assert(false);  // do not allow direct access to handle

        return 0;

      }

     

    }

}

// XmlElementReplaceStream.cs end

 

 

9.   Our Item 5b. code outputs a modified TestTable.xml (compare to Item 1):

 

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

<NewDataSet>

  <TestTable>

    <m_int>111</m_int>

  </TestTable>

  <TestTable>

    <m_int>444</m_int> 

  </TestTable>

  <TestTable>

    <m_int>333</m_int>

  </TestTable>

</NewDataSet>

 

 

 

 

Done.

 

Rafael Pena

rpenaphd@worldnet.att.net

4/03/2001

 

Keywords: HowTo, How To