--------------------------------------------------------------------------------------
CopyrightŠ 2001, Software Structures, Inc. All Rights
Reserved.
--------------------------------------------------------------------------------------
---------------------------------
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