Microsoft KB Archive/816221

= BUG: The XmlValidatingReader class does not close the stream when the XML document references a DTD that has errors =

Article ID: 816221

Article Last Modified on 1/11/2007

-

APPLIES TO


 * Microsoft .NET Framework 1.0
 * Microsoft .NET Framework 1.1
 * Microsoft .NET Framework Class Libraries 1.0
 * Microsoft .NET Framework Class Libraries 1.1
 * Microsoft Visual C# .NET 2002 Standard Edition
 * Microsoft Visual C# .NET 2003 Standard Edition
 * Microsoft Visual Basic .NET 2002 Standard Edition
 * Microsoft Visual Basic .NET 2003 Standard Edition

-



SYMPTOMS
When you validate your XML document by using the XmlValidatingReader class with respect to an external document type definition (DTD), and the DTD has an error, the stream that is opened by XmlValidatingReader to load the DTD is not closed. Therefore, when you try to open the DTD with write permissions, you receive the following error message:

An unhandled exception of type 'System.IO.IOException' occurred in mscorlib.dll

Additional information: The process cannot access the file &quot;DTDFileName&quot; because it is being used by another process.



CAUSE
When you use XmlValidatingReader to validate an XML document with respect to DTD, the .NET Framework internally opens a stream to read the DTD. When the error occurs while validating the XML document, the stream that is used to read the DTD is not closed, and you receive the error message that is listed in the &quot;Symptoms&quot; section.



WORKAROUND
To work around this problem, use the garbage collector (GarbageCollector.exe) to reclaim the unused memory and to close the stream. To do this, add the following code in the beginning of the method where the DTD is being corrected.

Visual C# .NET Code
// This method call triggers the garbage collector // to collect the unreferenced memory. GC.Collect; // Wait for the GC's Finalize thread to finish // executing all queued Finalize methods. GC.WaitForPendingFinalizers;

Visual Basic .NET Code
' This method call triggers the garbage collector ' to collect the unreferenced memory. GC.Collect ' Wait for the GC's Finalize thread to finish ' executing all queued Finalize methods. GC.WaitForPendingFinalizers For more information about the code to add to the RemoveTheErrorInDTD method, see the &quot;More Information&quot; section.



STATUS
Microsoft has confirmed that this is a problem in the Microsoft products that are listed at the beginning of this article.



MORE INFORMATION
Steps to Reproduce the Behavior   Save the following XML document in a text file as C:\Xmlfile.xml :  <!DOCTYPE CompanyEmployees SYSTEM &quot;C:/XMLDTD.dtd&quot;>   10001  John Sam</Middle> <Last>Desouza</Last> </Name> <Address> <Address1>408, Lake Apartments</Address1> <Address2>Rock Island</Address2> <City>Rock City</City> <Pin>10101</Pin> </Address> <Phone> <Residence>+1-435-89734</Residence> <Work>+1-436-32879</Work> <Mobile>+1-4358972922</Mobile> <Fax>+1-435-89729</Fax> </Phone> </Employee> </CompanyEmployees> </li> <li> Save the following DTD in a text file as C:\Xmldtd.dtd : <!ELEMENT CompanyEmployees (Employee)+ > <!ELEMENT Employee (EmpId,Name, Address, Phone)> <!ELEMENT EmpId (#PCDATA)> <!ELEMENT Name (First, Middle*, Last)+> <!ELEMENT Middle (#PCDATA)> <!ELEMENT Last (#PCDATA)> <!ELEMENT Address (Address1, Address2*, City, Pin)> <!ELEMENT Address1 (#PCDATA)> <!ELEMENT Address2 (#PCDATA)> <!ELEMENT City (#PCDATA)> <!ELEMENT Pin (#PCDATA)@ <!ELEMENT Phone (Residence, Work, Mobile*, Fax*)> <!ELEMENT Residence (#PCDATA)> <!ELEMENT Work (#PCDATA)> <!ELEMENT Mobile (#PCDATA)> <!ELEMENT Fax (#PCDATA)> Note An error in this DTD at line 11, column 24 (the &quot;@&quot; character) causes a compilation error and the symptoms that are mentioned in this article. </li> <li>Start Microsoft Visual Studio .NET.</li> <li>On the File menu, point to New and then click Project.</li> <li>Under Project Types, select either Visual C# .NET or Visual Basic .NET. Under Templates, select Console Application.</li> <li>Name the project MyConsoleApplication, and then click OK.</li> <li> Replace the existing code with the following code (either Visual C# .NET or Visual Basic .NET, as appropriate):

Visual C# .NET Code
using System; using System.Xml; using System.IO;

namespace MyConsoleApplication {  class MyTestClass {     private string dtdFile;  // Absolute path of the DTD file private string xmlFile; // Absolute path of the XML Document

// No default constructor. public MyTestClass(string dtdFile, string xmlFile) {        this.dtdFile = dtdFile; this.xmlFile = xmlFile; }     // This method reads the DTD and replaces the invalid // &quot;@&quot; character with the &quot;>&quot; character. public void RemoveTheErrorInDTD {        StreamReader sr = File.OpenText(dtdFile); string data = sr.ReadToEnd; sr.Close;

char[] charData = new Char[data.Length]; int index = data.IndexOf('@'); charData = data.ToCharArray; charData[index] = '>';

StreamWriter sw = File.CreateText(dtdFile); sw.Write(new String (charData)); sw.Close; }     // Validating the XML with respect to the external DTD // that was given in the XML document. public bool ValidateTheXMLDocWithDTD(bool validate) {        XmlTextReader tReader = null; XmlValidatingReader vReader = null; try {            FileStream stream = new FileStream(xmlFile, FileMode.Open,FileAccess.Read); tReader = new XmlTextReader (stream); tReader.WhitespaceHandling = WhitespaceHandling.Significant; vReader = new XmlValidatingReader(tReader); vReader.EntityHandling = EntityHandling.ExpandCharEntities;

if (!validate) vReader.ValidationType = ValidationType.None; else vReader.ValidationType = ValidationType.XDR;

while (vReader.Read) {              // Reading the text nodes and displaying on the console if ( vReader.NodeType == XmlNodeType.Text ) Console.WriteLine(vReader.Value+&quot;\n&quot;); }           return true; }         catch (Exception e)          { Console.WriteLine(&quot;XML Error: &quot;+e.Message); return false; }         finally {            // Finally block. Here XmlValidatingReader and // XmlTextReader will get closed. if (vReader != null) {               vReader.Close; }           if (tReader != null) {               tReader.Close; }           Console.Read; }

}

// Main Method. static void Main(string[] args) {        // Creating the object of MyTestClass. MyTestClass testObj = new MyTestClass(@&quot;C:\XMLDTD.dtd&quot;, @&quot;C:\XMLFile1.xml&quot;);

// Validating the XML document. bool x = testObj.ValidateTheXMLDocWithDTD(true); if ( x == false ) // If validation is not successful {           testObj.RemoveTheErrorInDTD; // correcting the error bool y = testObj.ValidateTheXMLDocWithDTD(true); // Validating it again }

Console.Read; }  } }

Visual Basic .NET Code
Imports System Imports System.Xml Imports System.IO

Public Class MyTestClass Private dtdFile As String 'Absolute path if the DTD File Private xmlFile As String 'Absolute path if the XML Document

'No default constructor. Public Sub New(ByVal dtdFile As String, ByVal xmlFile As String) Me.dtdFile = dtdFile Me.xmlFile = xmlFile End Sub

'     This method reads the DTD and replaces the invalid '     &quot;@&quot; character with the &quot;>&quot; character. Public Sub RemoveTheErrorInDTD Dim sr As StreamReader Dim data As String sr = File.OpenText(dtdFile) data = sr.ReadToEnd sr.Close Dim index As Long index = data.IndexOf(&quot;@&quot;) Dim charArray As Char charArray = data.ToCharArray charArray(index) = &quot;>&quot;

Dim sw As StreamWriter sw = File.CreateText(dtdFile) sw.Write(New String(charArray)) sw.Close End Sub

' Validating the XML with respect to the external DTD ' that was given in the XML document. Public Function ValidateTheXMLDocWithDTD(ByVal validate As Boolean) As Boolean Dim tReader As XmlTextReader Dim vReader As XmlValidatingReader Try Dim stream As New FileStream(xmlFile, FileMode.Open, FileAccess.Read) tReader = New XmlTextReader(Stream) tReader.WhitespaceHandling = WhitespaceHandling.Significant vReader = New XmlValidatingReader(tReader) vReader.EntityHandling = EntityHandling.ExpandCharEntities If validate = False Then vReader.ValidationType = ValidationType.None Else vReader.ValidationType = ValidationType.XDR End If        While (vReader.Read) If vReader.NodeType = XmlNodeType.Text Then Console.WriteLine(vReader.Value + &quot;\n&quot;) End If        End While Return True Catch excep As Exception Console.WriteLine(&quot;XML Error: &quot; & excep.Message) Finally If Not vReader Is Nothing Then vReader.Close End If        If Not tReader Is Nothing Then tReader.Close End If        Console.Read End Try

End Function End Class

Module Module1 Sub Main ' Creating the object of MyTestClass. Dim testObj As New MyTestClass(&quot;C:\XMLDTD.dtd&quot;, &quot;C:\XMLFile1.xml&quot;) Dim validated As Boolean validated = False While validated = False ' Validating the XML Document; validation continues until validated as True. validated = testObj.ValidateTheXMLDocWithDTD(True) If validated = False Then 'If validation is not successfull testObj.RemoveTheErrorInDTD ' correcting the error End If     End While Console.Read End Sub

End Module </li> <li>On the Debug menu, click Start.

During the correction of the error in the Xmldtd.dtd file, you receive the exception that is mentioned in the &quot;Symptoms&quot; section.</li></ol>

<div class="references_section">