فهرست منبع

2006-02-17 Atsushi Enomoto <[email protected]>

	* DTDValidatingReader2.cs, EntityResolvingXmlReader.cs :
	  new files for refactoring DTD validating reader. As the first stage,
	  it splits entity handling process from DTD validation step.

	  (DTDValidatingReader.cs will be removed at some stage).

	* System.Xml.dll.sources : removed DTDValidatingReader and added
	  DTDValidatingReader2.cs and EntityResolvingXmlReader.cs.


svn path=/trunk/mcs/; revision=56967
Atsushi Eno 20 سال پیش
والد
کامیت
29ce6906d7

+ 5 - 0
mcs/class/System.XML/ChangeLog

@@ -1,3 +1,8 @@
+2006-02-17  Atsushi Enomoto  <[email protected]>
+
+	* System.Xml.dll.sources : removed DTDValidatingReader and added
+	  DTDValidatingReader2.cs and EntityResolvingXmlReader.cs.
+
 2006-02-16  Atsushi Enomoto  <[email protected]>
 
 	* System.Xml.dll.sources : added IHasXmlChildNode.cs.

+ 2 - 1
mcs/class/System.XML/System.Xml.dll.sources

@@ -79,7 +79,8 @@ System.Xml/ConformanceLevel.cs
 System.Xml/DTDAutomata.cs
 System.Xml/DTDObjectModel.cs
 System.Xml/DTDReader.cs
-System.Xml/DTDValidatingReader.cs
+System.Xml/DTDValidatingReader2.cs
+System.Xml/EntityResolvingXmlReader.cs
 System.Xml/EntityHandling.cs
 System.Xml/Formatting.cs
 System.Xml/IHasXmlNode.cs

+ 8 - 0
mcs/class/System.XML/System.Xml/ChangeLog

@@ -1,3 +1,11 @@
+2006-02-17  Atsushi Enomoto <[email protected]>
+
+	* DTDValidatingReader2.cs, EntityResolvingXmlReader.cs :
+	  new files for refactoring DTD validating reader. As the first stage,
+	  it splits entity handling process from DTD validation step.
+
+	  (DTDValidatingReader.cs will be removed at some stage).
+
 2006-02-17  Atsushi Enomoto <[email protected]>
 
 	* DTDObjectModel.cs : entity resolution method will be used in

+ 1218 - 0
mcs/class/System.XML/System.Xml/DTDValidatingReader2.cs

@@ -0,0 +1,1218 @@
+//
+// DTDValidatingReader2.cs
+//
+// Author:
+//   Atsushi Enomoto  [email protected]
+//
+// (C)2003 Atsushi Enomoto
+// (C)2004-2006 Novell Inc.
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+/*
+
+Some notes:
+
+	DTDValidatingReader requires somewhat different ResolveEntity()
+	implementation because unlike other readers (XmlTextReaderImpl and
+	XmlNodeReaderImpl), DTDValidatingReader manages validation state
+	and it must not be held in each entity reader.
+	
+	Say, if there are such element and entity definitions:
+
+		<!ELEMENT root (child)>
+		<!ELEMENT child EMPTY>
+		<!ENTITY foo "<child />">
+
+	and an instance
+
+		<root>&foo;</root>
+
+	When the container XmlReader encounters "&foo;", it creates another
+	XmlReader for resolved entity "<child/>". However, the generated
+	reader must not be another standalone DTDValidatingReader since
+	<child/> must be a participant of the container's validation.
+
+	Thus, this reader handles validation, and it holds an
+	EntityResolvingXmlReader as its validation source XmlReader.
+
+*/
+
+using System;
+using System.Collections;
+#if NET_2_0
+using System.Collections.Generic;
+#endif
+using System.IO;
+using System.Text;
+using System.Xml;
+using System.Xml.Schema;
+
+#if NET_2_0
+using XmlTextReaderImpl = Mono.Xml2.XmlTextReader;
+#else
+using XmlTextReaderImpl = System.Xml.XmlTextReader;
+#endif
+
+
+namespace Mono.Xml
+{
+	internal class DTDValidatingReader : XmlReader, IXmlLineInfo, 
+#if NET_2_0
+		IXmlNamespaceResolver,
+#endif
+		IHasXmlParserContext, IHasXmlSchemaInfo
+	{
+		public DTDValidatingReader (XmlReader reader)
+			: this (reader, null)
+		{
+		}
+
+		internal DTDValidatingReader (XmlReader reader,
+			XmlValidatingReader validatingReader)
+		{
+			IHasXmlParserContext container = reader as IHasXmlParserContext;
+			this.reader = new EntityResolvingXmlReader (reader,
+				container.ParserContext);
+			this.sourceTextReader = reader as XmlTextReader;
+			elementStack = new Stack ();
+			automataStack = new Stack ();
+			attributes = new ArrayList ();
+			attributeValues = new Hashtable ();
+			attributeLocalNames = new Hashtable ();
+			attributeNamespaces = new Hashtable ();
+			attributePrefixes = new Hashtable ();
+			nsmgr = new XmlNamespaceManager (reader.NameTable);
+			this.validatingReader = validatingReader;
+			valueBuilder = new StringBuilder ();
+			idList = new ArrayList ();
+			missingIDReferences = new ArrayList ();
+			XmlTextReader xtReader = reader as XmlTextReader;
+			if (xtReader != null) {
+				resolver = xtReader.Resolver;
+			}
+			else
+				resolver = new XmlUrlResolver ();
+		}
+
+		EntityResolvingXmlReader reader;
+		XmlTextReader sourceTextReader;
+		DTDObjectModel dtd;
+		Stack elementStack;
+		Stack automataStack;
+		string currentElement;
+		string currentAttribute;
+		string currentTextValue;
+		string constructingTextValue;
+		bool shouldResetCurrentTextValue;
+		bool consumedAttribute;
+		bool insideContent;
+		DTDAutomata currentAutomata;
+		DTDAutomata previousAutomata;
+		bool isStandalone;
+		ArrayList attributes;
+		Hashtable attributeValues;
+		Hashtable attributeLocalNames;
+		Hashtable attributeNamespaces;
+		Hashtable attributePrefixes;
+		XmlNamespaceManager nsmgr;
+		StringBuilder valueBuilder;
+		ArrayList idList;
+		ArrayList missingIDReferences;
+		XmlResolver resolver;
+		bool isSignificantWhitespace;
+		bool isWhitespace;
+		bool isText;
+		bool dontResetTextType;
+		bool popScope;
+
+		// This field is used to get properties and to raise events.
+		XmlValidatingReader validatingReader;
+
+		public DTDObjectModel DTD {
+			get { return dtd; }
+		}
+
+		public EntityHandling EntityHandling {
+			get { return reader.EntityHandling; }
+			set { reader.EntityHandling = value; }
+		}
+
+		public override void Close ()
+		{
+			reader.Close ();
+		}
+
+		// We had already done attribute validation, so can ignore name.
+		public override string GetAttribute (int i)
+		{
+			if (currentTextValue != null)
+				throw new IndexOutOfRangeException ("Specified index is out of range: " + i);
+
+			if (dtd == null)
+				return reader.GetAttribute (i);
+
+			if (attributes.Count <= i)
+				throw new IndexOutOfRangeException ("Specified index is out of range: " + i);
+
+			string attrName = (string) attributes [i];
+			return FilterNormalization (attrName, (string) attributeValues [attrName]);
+		}
+
+		public override string GetAttribute (string name)
+		{
+			if (currentTextValue != null)
+				return null;
+
+			if (dtd == null)
+				return reader.GetAttribute (name);
+
+			return FilterNormalization (name, (string) attributeValues [name]);
+		}
+
+		public override string GetAttribute (string name, string ns)
+		{
+			if (currentTextValue != null)
+				return null;
+
+			if (dtd == null)
+				return reader.GetAttribute (name, ns);
+
+			return reader.GetAttribute ((string) attributeLocalNames [name], ns);
+		}
+
+#if NET_2_0
+		IDictionary<string, string> IXmlNamespaceResolver.GetNamespacesInScope (XmlNamespaceScope scope)
+		{
+			IXmlNamespaceResolver res = reader as IXmlNamespaceResolver;
+			return res != null ? res.GetNamespacesInScope (scope) : new Dictionary<string, string> ();
+		}
+#endif
+
+		bool IXmlLineInfo.HasLineInfo ()
+		{
+			IXmlLineInfo ixli = reader as IXmlLineInfo;
+			if (ixli != null)
+				return ixli.HasLineInfo ();
+			else
+				return false;
+		}
+
+		public override string LookupNamespace (string prefix)
+		{
+			string s = nsmgr.LookupNamespace (NameTable.Get (prefix));
+			return s == String.Empty ? null : s;
+		}
+
+#if NET_2_0
+		string IXmlNamespaceResolver.LookupPrefix (string ns)
+		{
+			IXmlNamespaceResolver res = reader as IXmlNamespaceResolver;
+			return res != null ? res.LookupPrefix (ns) : null;
+		}
+#endif
+
+		public override void MoveToAttribute (int i)
+		{
+			if (currentTextValue != null)
+				throw new IndexOutOfRangeException ("The index is out of range.");
+
+			if (dtd == null) {
+				reader.MoveToAttribute (i);
+				currentAttribute = reader.Name;
+				consumedAttribute = false;
+				return;
+			}
+
+			if (currentElement == null)
+				throw new IndexOutOfRangeException ("The index is out of range.");
+
+			if (attributes.Count > i) {
+				if (reader.AttributeCount > i)
+					reader.MoveToAttribute (i);
+				currentAttribute = (string) attributes [i];
+				consumedAttribute = false;
+				return;
+			} else
+				throw new IndexOutOfRangeException ("The index is out of range.");
+		}
+
+		public override bool MoveToAttribute (string name)
+		{
+			if (currentTextValue != null)
+				return false;
+
+			if (dtd == null) {
+				bool b = reader.MoveToAttribute (name);
+				if (b) {
+					currentAttribute = reader.Name;
+					consumedAttribute = false;
+				}
+				return b;
+			}
+
+			if (currentElement == null)
+				return false;
+
+			int idx = attributes.IndexOf (name);
+			if (idx >= 0) {
+				currentAttribute = name;
+				consumedAttribute = false;
+				return true;
+			}
+			return false;
+		}
+
+		public override bool MoveToAttribute (string name, string ns)
+		{
+			if (currentTextValue != null)
+				return false;
+
+			if (dtd == null) {
+				bool b = reader.MoveToAttribute (name, ns);
+				if (b) {
+					currentAttribute = reader.Name;
+					consumedAttribute = false;
+				}
+				return b;
+			}
+
+			if (reader.MoveToAttribute (name, ns)) {
+				currentAttribute = reader.Name;
+				consumedAttribute = false;
+				return true;
+			}
+
+			for (int i = 0; i < attributes.Count; i++) {
+				string iter = (string) attributes [i];
+				if ((string) attributeLocalNames [iter] == name)
+					return MoveToAttribute (iter);
+			}
+			return false;
+		}
+
+		public override bool MoveToElement ()
+		{
+			if (currentTextValue != null)
+				return false;
+
+			bool b = reader.MoveToElement ();
+			if (!b && !IsDefault)
+				return false;
+			currentAttribute = null;
+			consumedAttribute = false;
+			return true;
+		}
+
+		public override bool MoveToFirstAttribute ()
+		{
+			if (currentTextValue != null)
+				return false;
+
+			if (dtd == null) {
+				bool b = reader.MoveToFirstAttribute ();
+				if (b) {
+					currentAttribute = reader.Name;
+					consumedAttribute = false;
+				}
+				return b;
+			}
+
+			if (attributes.Count == 0)
+				return false;
+			currentAttribute = (string) attributes [0];
+			reader.MoveToAttribute (currentAttribute);
+			consumedAttribute = false;
+			return true;
+		}
+
+		public override bool MoveToNextAttribute ()
+		{
+			if (currentTextValue != null)
+				return false;
+
+			if (dtd == null) {
+				bool b = reader.MoveToNextAttribute ();
+				if (b) {
+					currentAttribute = reader.Name;
+					consumedAttribute = false;
+				}
+				return b;
+			}
+
+			if (currentAttribute == null)
+				return MoveToFirstAttribute ();
+
+			int idx = attributes.IndexOf (currentAttribute);
+			if (idx + 1 < attributes.Count) {
+				currentAttribute = (string) attributes [idx + 1];
+				reader.MoveToAttribute (currentAttribute);
+				consumedAttribute = false;
+				return true;
+			} else
+				return false;
+		}
+
+		/*
+		private void OnValidationEvent (object o, ValidationEventArgs e)
+		{
+			this.HandleError (e.Exception, e.Severity);
+		}
+		*/
+
+		public override bool Read ()
+		{
+			if (currentTextValue != null)
+				shouldResetCurrentTextValue = true;
+
+			MoveToElement ();
+
+			currentElement = null;
+			currentAttribute = null;
+			consumedAttribute = false;
+			attributes.Clear ();
+			attributeLocalNames.Clear ();
+			attributeValues.Clear ();
+			attributeNamespaces.Clear ();
+			attributePrefixes.Clear ();
+			isWhitespace = false;
+			isSignificantWhitespace = false;
+			isText = false;
+			dontResetTextType = false;
+
+			bool b = ReadContent () || currentTextValue != null;
+			if (!b && this.missingIDReferences.Count > 0) {
+				this.HandleError ("Missing ID reference was found: " +
+					String.Join (",", missingIDReferences.ToArray (typeof (string)) as string []),
+					XmlSeverityType.Error);
+				// Don't output the same errors so many times.
+				this.missingIDReferences.Clear ();
+			}
+			if (validatingReader != null)
+				EntityHandling = validatingReader.EntityHandling;
+			return b;
+		}
+
+		private bool ReadContent ()
+		{
+			if (reader.EOF)
+				return false;
+			if (popScope) {
+				nsmgr.PopScope ();
+				popScope = false;
+			}
+
+			bool b = !reader.EOF;
+			if (shouldResetCurrentTextValue) {
+				currentTextValue = null;
+				shouldResetCurrentTextValue = false;
+			}
+			else
+				b = reader.Read ();
+
+			if (!insideContent && reader.NodeType == XmlNodeType.Element) {
+				insideContent = true;
+				if (dtd == null)
+					currentAutomata = null;
+				else
+					currentAutomata = dtd.RootAutomata;
+			}
+
+			if (!b) {
+				if (elementStack.Count != 0)
+					throw new InvalidOperationException ("Unexpected end of XmlReader.");
+				return false;
+			}
+
+			DTDElementDeclaration elem = null;
+
+			switch (reader.NodeType) {
+			case XmlNodeType.XmlDeclaration:
+				if (GetAttribute ("standalone") == "yes")
+					isStandalone = true;
+				ValidateAttributes (null, false);
+				break;
+
+			case XmlNodeType.DocumentType:
+//				XmlTextReader xmlTextReader = reader as XmlTextReader;
+				IHasXmlParserContext ctx = reader as IHasXmlParserContext;
+				if (ctx != null)
+					dtd = ctx.ParserContext.Dtd;
+				if (dtd == null) {
+					XmlTextReaderImpl xmlTextReader = new XmlTextReaderImpl ("", XmlNodeType.Document, null);
+					xmlTextReader.XmlResolver = resolver;
+					xmlTextReader.GenerateDTDObjectModel (reader.Name,
+						reader ["PUBLIC"], reader ["SYSTEM"], reader.Value);
+					dtd = xmlTextReader.DTD;
+				}
+
+				// Validity Constraints Check.
+				if (DTD.Errors.Length > 0)
+					for (int i = 0; i < DTD.Errors.Length; i++)
+						HandleError (DTD.Errors [i].Message, XmlSeverityType.Error);
+
+				// NData target exists.
+				foreach (DTDEntityDeclaration ent in dtd.EntityDecls.Values)
+					if (ent.NotationName != null && dtd.NotationDecls [ent.NotationName] == null)
+						this.HandleError ("Target notation was not found for NData in entity declaration " + ent.Name + ".",
+							XmlSeverityType.Error);
+				// NOTATION exists for attribute default values
+				foreach (DTDAttListDeclaration attListIter in dtd.AttListDecls.Values)
+					foreach (DTDAttributeDefinition def in attListIter.Definitions)
+						if (def.Datatype.TokenizedType == XmlTokenizedType.NOTATION) {
+							foreach (string notation in def.EnumeratedNotations)
+								if (dtd.NotationDecls [notation] == null)
+									this.HandleError ("Target notation was not found for NOTATION typed attribute default " + def.Name + ".",
+										XmlSeverityType.Error);
+						}
+
+				break;
+
+			case XmlNodeType.Element:
+				nsmgr.PushScope ();
+				popScope = reader.IsEmptyElement;
+				if (constructingTextValue != null) {
+					currentTextValue = constructingTextValue;
+					constructingTextValue = null;
+					if (isWhitespace)
+						ValidateWhitespaceNode ();
+					return true;
+				}
+				elementStack.Push (reader.Name);
+				// startElementDeriv
+				// If no schema specification, then skip validation.
+				if (currentAutomata == null) {
+					ValidateAttributes (null, false);
+					if (reader.IsEmptyElement)
+						goto case XmlNodeType.EndElement;
+					break;
+				}
+
+				previousAutomata = currentAutomata;
+				currentAutomata = currentAutomata.TryStartElement (reader.Name);
+				if (currentAutomata == DTD.Invalid) {
+					HandleError (String.Format ("Invalid start element found: {0}", reader.Name),
+						XmlSeverityType.Error);
+					currentAutomata = previousAutomata;
+				}
+				elem = DTD.ElementDecls [reader.Name];
+				if (elem == null) {
+					HandleError (String.Format ("Element {0} is not declared.", reader.Name),
+						XmlSeverityType.Error);
+					currentAutomata = previousAutomata;
+				}
+
+				currentElement = Name;
+				automataStack.Push (currentAutomata);
+				if (elem != null)	// i.e. not invalid
+					currentAutomata = elem.ContentModel.GetAutomata ();
+
+				DTDAttListDeclaration attList = dtd.AttListDecls [currentElement];
+				if (attList != null) {
+					// check attributes
+					ValidateAttributes (attList, true);
+					currentAttribute = null;
+				} else {
+					if (reader.HasAttributes) {
+						HandleError (String.Format (
+							"Attributes are found on element {0} while it has no attribute definitions.", currentElement),
+							XmlSeverityType.Error);
+					}
+					// SetupValidityIgnorantAttributes ();
+					ValidateAttributes (null, false);
+				}
+				// If it is empty element then directly check end element.
+				if (reader.IsEmptyElement)
+					goto case XmlNodeType.EndElement;
+				break;
+
+			case XmlNodeType.EndElement:
+				if (constructingTextValue != null) {
+					currentTextValue = constructingTextValue;
+					constructingTextValue = null;
+					return true;
+				}
+				popScope = true;
+				elementStack.Pop ();
+				// endElementDeriv
+				// If no schema specification, then skip validation.
+				if (currentAutomata == null)
+					break;
+
+				elem = DTD.ElementDecls [reader.Name];
+				if (elem == null) {
+					HandleError (String.Format ("Element {0} is not declared.", reader.Name),
+						XmlSeverityType.Error);
+				}
+
+				previousAutomata = currentAutomata;
+				// Don't let currentAutomata
+				DTDAutomata tmpAutomata = currentAutomata.TryEndElement ();
+				if (tmpAutomata == DTD.Invalid) {
+					HandleError (String.Format ("Invalid end element found: {0}", reader.Name),
+						XmlSeverityType.Error);
+					currentAutomata = previousAutomata;
+				}
+
+				currentAutomata = automataStack.Pop () as DTDAutomata;
+				break;
+
+			case XmlNodeType.CDATA:
+				isSignificantWhitespace = isWhitespace = false;
+				isText = true;
+
+				ValidateText ();
+
+				if (currentTextValue != null) {
+					currentTextValue = constructingTextValue;
+					constructingTextValue = null;
+					return true;
+				}
+				break;
+			case XmlNodeType.SignificantWhitespace:
+				if (!isText)
+					isSignificantWhitespace = true;
+				isWhitespace = false;
+				dontResetTextType = true;
+				goto case XmlNodeType.DocumentFragment;
+			case XmlNodeType.Text:
+				isWhitespace = isSignificantWhitespace = false;
+				isText = true;
+				goto case XmlNodeType.DocumentFragment;
+			case XmlNodeType.DocumentFragment:
+				// it should not happen, but in case if
+				// XmlReader really returns it, just ignore.
+				if (reader.NodeType == XmlNodeType.DocumentFragment)
+					break;
+
+				ValidateText ();
+
+				break;
+			case XmlNodeType.Whitespace:
+				if (!isText && !isSignificantWhitespace)
+					isWhitespace = true;
+				goto case XmlNodeType.DocumentFragment;
+			}
+			if (isWhitespace)
+				ValidateWhitespaceNode ();
+			currentTextValue = constructingTextValue;
+			constructingTextValue = null;
+			MoveToElement ();
+			return true;
+		}
+
+		private void ValidateText ()
+		{
+			if (currentAutomata == null)
+				return;
+
+			DTDElementDeclaration elem = null;
+			if (elementStack.Count > 0)
+				elem = dtd.ElementDecls [elementStack.Peek () as string];
+			// Here element should have been already validated, so
+			// if no matching declaration is found, simply ignore.
+			if (elem != null && !elem.IsMixedContent && !elem.IsAny && !isWhitespace) {
+				HandleError (String.Format ("Current element {0} does not allow character data content.", elementStack.Peek () as string),
+					XmlSeverityType.Error);
+				currentAutomata = previousAutomata;
+			}
+		}
+
+		private void ValidateWhitespaceNode ()
+		{
+			// VC Standalone Document Declaration (2.9)
+			if (this.isStandalone && DTD != null && elementStack.Count > 0) {
+				DTDElementDeclaration elem = DTD.ElementDecls [elementStack.Peek () as string];
+				if (elem != null && !elem.IsInternalSubset && !elem.IsMixedContent && !elem.IsAny && !elem.IsEmpty)
+					HandleError ("In standalone document, whitespace cannot appear in an element whose declaration explicitly contains child content model, not Mixed content.", XmlSeverityType.Error);
+			}
+		}
+
+		private XmlException NotWFError (string message)
+		{
+			return new XmlException (this as IXmlLineInfo, BaseURI, message);
+		}
+
+		private void HandleError (string message, XmlSeverityType severity)
+		{
+			if (validatingReader != null &&
+				validatingReader.ValidationType == ValidationType.None)
+				return;
+
+			IXmlLineInfo info = this as IXmlLineInfo;
+			bool hasLine = info.HasLineInfo ();
+			XmlSchemaException ex = new XmlSchemaException (
+				message,
+				hasLine ? info.LineNumber : 0,
+				hasLine ? info.LinePosition : 0, 
+				null,
+				BaseURI, 
+				null);
+			HandleError (ex, severity);
+		}
+
+		private void HandleError (XmlSchemaException ex, XmlSeverityType severity)
+		{
+			if (validatingReader != null &&
+				validatingReader.ValidationType == ValidationType.None)
+				return;
+
+			if (validatingReader != null)
+				this.validatingReader.OnValidationEvent (this,
+					new ValidationEventArgs (ex, ex.Message, severity));
+			else if (severity == XmlSeverityType.Error)
+				throw ex;
+		}
+
+		Stack attributeValueEntityStack = new Stack ();
+
+		private void ValidateAttributes (DTDAttListDeclaration decl, bool validate)
+		{
+			DtdValidateAttributes (decl, validate);
+
+			foreach (string attr in attributes)
+				if (attr == "xmlns" ||
+					String.CompareOrdinal (attr, 0, "xmlns:", 0, 6) == 0)
+					nsmgr.AddNamespace (
+						attr == "xmlns" ? String.Empty : (string) attributeLocalNames [attr],
+						(string) attributeValues [attr]);
+
+			foreach (string attr in attributes) {
+				string prefix = attr == "xmlns" ? "xmlns" : attributePrefixes [attr] as string;
+				if (prefix == String.Empty)
+					attributeNamespaces.Add (attr, String.Empty);
+				else
+					attributeNamespaces.Add (attr, LookupNamespace (prefix));
+			}
+		}
+
+		private void DtdValidateAttributes (DTDAttListDeclaration decl, bool validate)
+		{
+			while (reader.MoveToNextAttribute ()) {
+				string attrName = reader.Name;
+				this.currentAttribute = attrName;
+				attributes.Add (attrName);
+				attributeLocalNames.Add (attrName, reader.LocalName);
+				attributePrefixes.Add (attrName, reader.Prefix);
+				XmlReader targetReader = reader;
+				string attrValue = null;
+				// It always resolves entity references on attributes (documented as such).
+//				if (currentEntityHandling == EntityHandling.ExpandCharEntities)
+//					attrValue = reader.Value;
+//				else
+				{
+					while (attributeValueEntityStack.Count >= 0) {
+						if (!targetReader.ReadAttributeValue ()) {
+							if (attributeValueEntityStack.Count > 0) {
+								targetReader = attributeValueEntityStack.Pop () as XmlReader;
+								continue;
+							} else
+								break;
+						}
+						switch (targetReader.NodeType) {
+						case XmlNodeType.EntityReference:
+							DTDEntityDeclaration edecl = DTD.EntityDecls [targetReader.Name];
+							if (edecl == null) {
+								HandleError (String.Format ("Referenced entity {0} is not declared.", targetReader.Name),
+									XmlSeverityType.Error);
+							} else {
+								XmlTextReader etr = new XmlTextReader (edecl.EntityValue, XmlNodeType.Attribute, ParserContext);
+								attributeValueEntityStack.Push (targetReader);
+								targetReader = etr;
+								continue;
+							}
+							break;
+						case XmlNodeType.EndEntity:
+							break;
+						default:
+							if (attrValue != null) {
+								valueBuilder.Append (attrValue);
+								attrValue = null;
+							}
+							
+							if (valueBuilder.Length != 0)
+								valueBuilder.Append (targetReader.Value);
+							else
+								attrValue = targetReader.Value;
+							
+							break;
+						}
+					}
+					
+					if (attrValue == null) {
+						attrValue = valueBuilder.ToString ();
+						valueBuilder.Length = 0;
+					}
+				}
+				reader.MoveToElement ();
+				reader.MoveToAttribute (attrName);
+				attributeValues.Add (attrName, attrValue);
+
+				if (!validate)
+					continue;
+
+				// Validation
+
+				DTDAttributeDefinition def = decl [reader.Name];
+				if (def == null) {
+					HandleError (String.Format ("Attribute {0} is not declared.", reader.Name),
+						XmlSeverityType.Error);
+					continue;
+				}
+
+				// check enumeration constraint
+				if (def.EnumeratedAttributeDeclaration.Count > 0)
+					if (!def.EnumeratedAttributeDeclaration.Contains (
+						FilterNormalization (reader.Name, attrValue)))
+						HandleError (String.Format ("Attribute enumeration constraint error in attribute {0}, value {1}.",
+							reader.Name, attrValue), XmlSeverityType.Error);
+				if (def.EnumeratedNotations.Count > 0)
+					if (!def.EnumeratedNotations.Contains (
+						FilterNormalization (reader.Name, attrValue)))
+						HandleError (String.Format ("Attribute notation enumeration constraint error in attribute {0}, value {1}.",
+							reader.Name, attrValue), XmlSeverityType.Error);
+
+				// check type constraint
+				string normalized = null;
+				if (def.Datatype != null)
+					normalized = FilterNormalization (def.Name, attrValue);
+				else
+					normalized = attrValue;
+				DTDEntityDeclaration ent;
+
+				// Common process to get list value
+				string [] list = null;
+				switch (def.Datatype.TokenizedType) {
+				case XmlTokenizedType.IDREFS:
+				case XmlTokenizedType.ENTITIES:
+				case XmlTokenizedType.NMTOKENS:
+					try {
+						list = def.Datatype.ParseValue (normalized, NameTable, null) as string [];
+					} catch (Exception) {
+						HandleError ("Attribute value is invalid against its data type.", XmlSeverityType.Error);
+						list = new string [0];
+					}
+					break;
+				default:
+					try {
+						def.Datatype.ParseValue (normalized, NameTable, null);
+					} catch (Exception ex) {
+						HandleError (String.Format ("Attribute value is invalid against its data type '{0}'. {1}", def.Datatype, ex.Message), XmlSeverityType.Error);
+					}
+					break;
+				}
+
+				switch (def.Datatype.TokenizedType) {
+				case XmlTokenizedType.ID:
+					if (this.idList.Contains (normalized)) {
+						HandleError (String.Format ("Node with ID {0} was already appeared.", attrValue),
+							XmlSeverityType.Error);
+					} else {
+						if (missingIDReferences.Contains (normalized))
+							missingIDReferences.Remove (normalized);
+						idList.Add (normalized);
+					}
+					break;
+				case XmlTokenizedType.IDREF:
+					if (!idList.Contains (normalized))
+						missingIDReferences.Add (normalized);
+					break;
+				case XmlTokenizedType.IDREFS:
+					for (int i = 0; i < list.Length; i++) {
+						string idref = list [i];
+						if (!idList.Contains (idref))
+							missingIDReferences.Add (idref);
+					}
+					break;
+				case XmlTokenizedType.ENTITY:
+					ent = dtd.EntityDecls [normalized];
+					if (ent == null)
+						HandleError ("Reference to undeclared entity was found in attribute: " + reader.Name + ".", XmlSeverityType.Error);
+					else if (ent.NotationName == null)
+						HandleError ("The entity specified by entity type value must be an unparsed entity. The entity definition has no NDATA in attribute: " + reader.Name + ".", XmlSeverityType.Error);
+					break;
+				case XmlTokenizedType.ENTITIES:
+					for (int i = 0; i < list.Length; i++) {
+						string entref = list [i];
+						ent = dtd.EntityDecls [FilterNormalization (reader.Name, entref)];
+						if (ent == null)
+							HandleError ("Reference to undeclared entity was found in attribute: " + reader.Name + ".", XmlSeverityType.Error);
+						else if (ent.NotationName == null)
+							HandleError ("The entity specified by ENTITIES type value must be an unparsed entity. The entity definition has no NDATA in attribute: " + reader.Name + ".", XmlSeverityType.Error);
+					}
+					break;
+//				case XmlTokenizedType.NMTOKEN: nothing to do
+//				case XmlTokenizedType.NMTOKENS: nothing to do
+				}
+
+				if (isStandalone && !def.IsInternalSubset && 
+					attrValue != normalized)
+					HandleError ("In standalone document, attribute value characters must not be checked against external definition.", XmlSeverityType.Error);
+
+				if (def.OccurenceType == 
+					DTDAttributeOccurenceType.Fixed &&
+					attrValue != def.DefaultValue)
+					HandleError (String.Format ("Fixed attribute {0} in element {1} has invalid value {2}.",
+						def.Name, decl.Name, attrValue),
+						XmlSeverityType.Error);
+			}
+
+			if (validate)
+				VerifyDeclaredAttributes (decl);
+
+			MoveToElement ();
+		}
+
+		private void VerifyDeclaredAttributes (DTDAttListDeclaration decl)
+		{
+			// Check if all required attributes exist, and/or
+			// if there is default values, then add them.
+			for (int i = 0; i < decl.Definitions.Count; i++) {
+				DTDAttributeDefinition def = (DTDAttributeDefinition) decl.Definitions [i];
+				if (attributes.Contains (def.Name))
+					continue;
+
+				if (def.OccurenceType == DTDAttributeOccurenceType.Required) {
+					HandleError (String.Format ("Required attribute {0} in element {1} not found .",
+						def.Name, decl.Name),
+						XmlSeverityType.Error);
+					continue;
+				}
+
+				else if (def.DefaultValue == null)
+					continue;
+
+				if (this.isStandalone && !def.IsInternalSubset)
+					HandleError ("In standalone document, external default value definition must not be applied.", XmlSeverityType.Error);
+
+				switch (validatingReader.ValidationType) {
+				case ValidationType.Auto:
+					if (validatingReader.Schemas.Count == 0)
+						goto case ValidationType.DTD;
+					break;
+				case ValidationType.DTD:
+				case ValidationType.None:
+					// Other than them, ignore DTD defaults.
+					attributes.Add (def.Name);
+					int colonAt = def.Name.IndexOf (':');
+					attributeLocalNames.Add (def.Name,
+						colonAt < 0 ? def.Name :
+						def.Name.Substring (colonAt + 1));
+					string prefix = colonAt < 0 ?
+						String.Empty :
+						def.Name.Substring (0, colonAt);
+					attributePrefixes.Add (def.Name, prefix);
+					attributeValues.Add (def.Name, def.DefaultValue);
+					break;
+				}
+			}
+		}
+
+		public override bool ReadAttributeValue ()
+		{
+			if (consumedAttribute)
+				return false;
+			if (NodeType == XmlNodeType.Attribute &&
+			    EntityHandling == EntityHandling.ExpandEntities) {
+				consumedAttribute = true;
+				return true;
+			}
+			else if (IsDefault) {
+				consumedAttribute = true;
+				return true;
+			}
+			else
+				return reader.ReadAttributeValue ();
+		}
+
+		public override void ResolveEntity ()
+		{
+			reader.ResolveEntity ();
+		}
+
+		public override int AttributeCount {
+			get {
+				if (currentTextValue != null)
+					return 0;
+
+				if (dtd == null || !insideContent)
+					return reader.AttributeCount;
+
+				return attributes.Count;
+			}
+		}
+
+		public override string BaseURI {
+			get {
+				return reader.BaseURI;
+			}
+		}
+
+		public override bool CanResolveEntity {
+			get { return true; }
+		}
+
+		public override int Depth {
+			get {
+				int baseNum = reader.Depth;
+				if (currentTextValue != null && reader.NodeType == XmlNodeType.EndElement)
+					baseNum++;
+
+				return IsDefault ? baseNum + 1 : baseNum;
+			}
+		}
+
+		public override bool EOF {
+			get { return reader.EOF; }
+		}
+
+		public override bool HasValue {
+			get {
+				return IsDefault ? true :
+					currentTextValue != null ? true :
+					reader.HasValue;
+			}
+		}
+
+		public override bool IsDefault {
+			get {
+				if (currentTextValue != null)
+					return false;
+				if (currentAttribute == null)
+					return false;
+				return reader.GetAttribute (currentAttribute) == null;
+			}
+		}
+
+		public override bool IsEmptyElement {
+			get {
+				if (currentTextValue != null)
+					return false;
+				return reader.IsEmptyElement;
+			}
+		}
+
+		public override string this [int i] {
+			get { return GetAttribute (i); }
+		}
+
+		public override string this [string name] {
+			get { return GetAttribute (name); }
+		}
+
+		public override string this [string name, string ns] {
+			get { return GetAttribute (name, ns); }
+		}
+
+		public int LineNumber {
+			get {
+				IXmlLineInfo info = reader as IXmlLineInfo;
+				return (info != null) ? info.LineNumber : 0;
+			}
+		}
+
+		public int LinePosition {
+			get {
+				IXmlLineInfo info = reader as IXmlLineInfo;
+				return (info != null) ? info.LinePosition : 0;
+			}
+		}
+
+		public override string LocalName {
+			get {
+				if (currentTextValue != null || consumedAttribute)
+					return String.Empty;
+				else if (NodeType == XmlNodeType.Attribute)
+					return (string) attributeLocalNames [currentAttribute];
+				else
+					return reader.LocalName;
+			}
+		}
+
+		public override string Name {
+			get {
+				if (currentTextValue != null || consumedAttribute)
+					return String.Empty;
+				else if (NodeType == XmlNodeType.Attribute)
+					return currentAttribute;
+				else
+					return reader.Name;
+			}
+		}
+
+		public override string NamespaceURI {
+			get {
+				if (currentTextValue != null || consumedAttribute)
+					return String.Empty;
+				switch (NodeType) {
+				case XmlNodeType.Attribute:
+					return (string) attributeNamespaces [currentAttribute];
+				case XmlNodeType.Element:
+				case XmlNodeType.EndElement:
+					return nsmgr.LookupNamespace (Prefix);
+				default:
+					return String.Empty;
+				}
+			}
+		}
+
+		public override XmlNameTable NameTable {
+			get { return reader.NameTable; }
+		}
+
+		public override XmlNodeType NodeType {
+			get {
+				if (currentTextValue != null)
+					return isSignificantWhitespace ? XmlNodeType.SignificantWhitespace :
+						isWhitespace ? XmlNodeType.Whitespace :
+						XmlNodeType.Text;
+
+				// If consumedAttribute is true, then entities must be resolved.
+				return consumedAttribute ? XmlNodeType.Text :
+					IsDefault ? XmlNodeType.Attribute :
+					reader.NodeType;
+			}
+		}
+
+		public XmlParserContext ParserContext {
+			get { return XmlSchemaUtil.GetParserContext (reader); }
+		}
+
+		public override string Prefix {
+			get {
+				if (currentTextValue != null || consumedAttribute)
+					return String.Empty;
+				else if (NodeType == XmlNodeType.Attribute)
+					return (string) attributePrefixes [currentAttribute];
+				else
+					return reader.Prefix;
+			}
+		}
+		
+		public override char QuoteChar {
+			get {
+				// If it is not actually on an attribute, then it returns
+				// undefined value or '"'.
+				return reader.QuoteChar;
+			}
+		}
+
+		public override ReadState ReadState {
+			get {
+				if (reader.ReadState == ReadState.EndOfFile && currentTextValue != null)
+					return ReadState.Interactive;
+				return reader.ReadState;
+			}
+		}
+
+		public object SchemaType {
+			get {
+				if (currentElement == null)
+					return null;
+				DTDAttListDeclaration decl =
+					DTD.AttListDecls [currentElement];
+				DTDAttributeDefinition def =
+					decl != null ? decl [currentAttribute] : null;
+				return def != null ? def.Datatype : null;
+			}
+		}
+
+		char [] whitespaceChars = new char [] {' '};
+		private string FilterNormalization (string attrName, string rawValue)
+		{
+			if (DTD == null || NodeType != XmlNodeType.Attribute ||
+				sourceTextReader == null ||
+				!sourceTextReader.Normalization)
+				return rawValue;
+
+			DTDAttributeDefinition def = 
+				dtd.AttListDecls [currentElement].Get (attrName);
+			valueBuilder.Append (rawValue);
+			valueBuilder.Replace ('\r', ' ');
+			valueBuilder.Replace ('\n', ' ');
+			valueBuilder.Replace ('\t', ' ');
+			try {
+				if (def.Datatype.TokenizedType == XmlTokenizedType.CDATA)
+					return valueBuilder.ToString ();
+				
+				for (int i=0; i < valueBuilder.Length; i++) {
+					if (valueBuilder [i] != ' ')
+						continue;
+					while (++i < valueBuilder.Length && valueBuilder [i] == ' ')
+						valueBuilder.Remove (i, 1);
+				}
+				return valueBuilder.ToString ().Trim (whitespaceChars);
+			} finally {
+				valueBuilder.Length = 0;
+			}
+		}
+
+		public override string Value {
+			get {
+				if (currentTextValue != null)
+					return currentTextValue;
+				// As to this property, MS.NET seems ignorant of EntityHandling...
+				else if (NodeType == XmlNodeType.Attribute
+					// It also covers default attribute text.
+ 					|| consumedAttribute)
+					return FilterNormalization (Name, (string) attributeValues [currentAttribute]);
+				else
+					return FilterNormalization (Name, reader.Value);
+			}
+		}
+
+		public override string XmlLang {
+			get {
+				string val = this ["xml:lang"];
+				return val != null ? val : reader.XmlLang;
+			}
+		}
+
+		internal XmlResolver Resolver {
+			get { return resolver; }
+		}
+
+		public XmlResolver XmlResolver {
+			set {
+				if (dtd != null)
+					dtd.XmlResolver = value;
+				resolver = value;
+			}
+		}
+
+		public override XmlSpace XmlSpace {
+			get {
+				string val = this ["xml:space"];
+				switch (val) {
+				case "preserve":
+					return XmlSpace.Preserve;
+				case "default":
+					return XmlSpace.Default;
+				default:
+					return reader.XmlSpace;
+				}
+			}
+		}
+
+	}
+}

+ 460 - 0
mcs/class/System.XML/System.Xml/EntityResolvingXmlReader.cs

@@ -0,0 +1,460 @@
+//
+// EntityResolvingXmlReader.cs - XmlReader that handles entity resolution
+//
+// Author:
+//   Atsushi Enomoto  ([email protected])
+//
+// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#if NET_2_0
+using System.Collections.Generic;
+#endif
+using System;
+using System.Globalization;
+using System.IO;
+using System.Security.Permissions;
+using System.Text;
+using System.Xml.Schema;
+using System.Xml;
+
+namespace Mono.Xml
+{
+	[PermissionSet (SecurityAction.InheritanceDemand, Unrestricted = true)]
+	internal class EntityResolvingXmlReader : XmlReader,
+#if NET_2_0
+		IXmlNamespaceResolver,
+#endif
+		IXmlLineInfo, IHasXmlParserContext
+	{
+		EntityResolvingXmlReader entity;
+		XmlReader source;
+		XmlParserContext context;
+		XmlResolver resolver;
+		EntityHandling entity_handling;
+		bool entity_inside_attr;
+		bool inside_attr;
+		bool do_resolve;
+
+		public EntityResolvingXmlReader (XmlReader source, XmlParserContext context)
+		{
+			this.source = source;
+			this.context = context;
+		}
+
+		EntityResolvingXmlReader (XmlReader entityContainer,
+			bool inside_attr)
+		{
+			source = entityContainer;
+			this.entity_inside_attr = inside_attr;
+		}
+
+		#region Properties
+
+		private XmlReader Current {
+			get { return entity != null && entity.ReadState != ReadState.Initial ? (XmlReader) entity : source; }
+		}
+
+#if NET_2_0
+#else
+		public override string this [int i] {
+			get { return GetAttribute (i); }
+		}
+
+		public override string this [string name] {
+			get { return GetAttribute (name); }
+		}
+
+		public override string this [string localName, string namespaceName] {
+			get { return GetAttribute (localName, namespaceName); }
+		}
+#endif
+
+		public override int AttributeCount {
+			get { return Current.AttributeCount; }
+		}
+
+		public override string BaseURI {
+			get { return Current.BaseURI; }
+		}
+
+		public override bool CanResolveEntity {
+			get { return true; }
+		}
+
+		public override int Depth {
+			get {
+				// On EndEntity, depth is the same as that 
+				// of EntityReference.
+				if (entity != null && entity.ReadState == ReadState.Interactive)
+					return source.Depth + entity.Depth + 1;
+				else
+					return source.Depth;
+			}
+		}
+
+		public override bool EOF {
+			get { return source.EOF; }
+		}
+
+		public override bool HasValue {
+			get { return Current.HasValue; }
+		}
+
+		public override bool IsDefault {
+			get { return Current.IsDefault; }
+		}
+
+		public override bool IsEmptyElement {
+			get { return Current.IsEmptyElement; }
+		}
+
+		public override string LocalName {
+			get { return Current.LocalName; }
+		}
+
+		public override string Name {
+			get { return Current.Name; }
+		}
+
+		public override string NamespaceURI {
+			get { return Current.NamespaceURI; }
+		}
+
+		public override XmlNameTable NameTable {
+			get { return Current.NameTable; }
+		}
+
+		public override XmlNodeType NodeType {
+			get {
+				if (Current == entity)
+					return entity.EOF ? XmlNodeType.EndEntity : entity.NodeType;
+				else
+					return source.NodeType;
+			}
+		}
+
+		internal XmlParserContext ParserContext {
+			get { return context; }
+		}
+
+		XmlParserContext IHasXmlParserContext.ParserContext {
+			get { return context; }
+		}
+
+		public override string Prefix {
+			get { return Current.Prefix; }
+		}
+
+		public override char QuoteChar {
+			get { return Current.QuoteChar; }
+		}
+
+		public override ReadState ReadState {
+			get { return entity != null ? ReadState.Interactive : source.ReadState; }
+		}
+
+		public override string Value {
+			get { return Current.Value; }
+		}
+
+		public override string XmlLang {
+			get { return Current.XmlLang; }
+		}
+
+		public override XmlSpace XmlSpace {
+			get { return Current.XmlSpace; }
+		}
+
+		// non-overrides
+
+		private void CopyProperties (EntityResolvingXmlReader other)
+		{
+			context = other.context;
+			resolver = other.resolver;
+			entity_handling = other.entity_handling;
+		}
+
+		// public members
+
+		public EntityHandling EntityHandling {
+			get { return entity_handling; }
+			set {
+				if (entity != null)
+					entity.EntityHandling = value;
+				entity_handling = value;
+			}
+		}
+
+		public int LineNumber {
+			get {
+				IXmlLineInfo li = Current as IXmlLineInfo;
+				return li == null ? 0 : li.LineNumber;
+			}
+		}
+
+		public int LinePosition {
+			get {
+				IXmlLineInfo li = Current as IXmlLineInfo;
+				return li == null ? 0 : li.LinePosition;
+			}
+		}
+
+		public XmlResolver XmlResolver {
+			set {
+				if (entity != null)
+					entity.XmlResolver = value;
+				resolver = value;
+			}
+		}
+
+		#endregion
+
+		#region Methods
+
+		// overrides
+
+		public override void Close ()
+		{
+			if (entity != null)
+				entity.Close ();
+			source.Close ();
+		}
+
+		public override string GetAttribute (int i)
+		{
+			return Current.GetAttribute (i);
+		}
+
+		// MS.NET 1.0 msdn says that this method returns String.Empty
+		// for absent attribute, but in fact it returns null.
+		// This description is corrected in MS.NET 1.1 msdn.
+		public override string GetAttribute (string name)
+		{
+			return Current.GetAttribute (name);
+		}
+
+		public override string GetAttribute (string localName, string namespaceURI)
+		{
+			return Current.GetAttribute (localName, namespaceURI);
+		}
+
+#if NET_2_0
+		public IDictionary<string, string> GetNamespacesInScope (XmlNamespaceScope scope)
+		{
+			return ((IXmlNamespaceResolver) Current).GetNamespacesInScope (scope);
+		}
+
+		IDictionary<string, string> IXmlNamespaceResolver.GetNamespacesInScope (XmlNamespaceScope scope)
+		{
+			return GetNamespacesInScope (scope);
+		}
+
+		string IXmlNamespaceResolver.LookupPrefix (string ns)
+		{
+			return ((IXmlNamespaceResolver) Current).LookupPrefix (ns);
+		}
+#endif
+
+		public override string LookupNamespace (string prefix)
+		{
+			return Current.LookupNamespace (prefix);
+		}
+
+		public override void MoveToAttribute (int i)
+		{
+			if (entity != null && entity_inside_attr) {
+				entity.Close ();
+				entity = null;
+			}
+			Current.MoveToAttribute (i);
+			inside_attr = true;
+		}
+
+		public override bool MoveToAttribute (string name)
+		{
+			if (entity != null && !entity_inside_attr)
+				return entity.MoveToAttribute (name);
+			if (!source.MoveToAttribute (name))
+				return false;
+			if (entity != null && entity_inside_attr) {
+				entity.Close ();
+				entity = null;
+			}
+			inside_attr = true;
+			return true;
+		}
+
+		public override bool MoveToAttribute (string localName, string namespaceName)
+		{
+			if (entity != null && !entity_inside_attr)
+				return entity.MoveToAttribute (localName, namespaceName);
+			if (!source.MoveToAttribute (localName, namespaceName))
+				return false;
+			if (entity != null && entity_inside_attr) {
+				entity.Close ();
+				entity = null;
+			}
+			inside_attr = true;
+			return true;
+		}
+
+		public override bool MoveToElement ()
+		{
+			if (entity != null && entity_inside_attr) {
+				entity.Close ();
+				entity = null;
+			}
+			if (!Current.MoveToElement ())
+				return false;
+			inside_attr = false;
+			return true;
+		}
+
+		public override bool MoveToFirstAttribute ()
+		{
+			if (entity != null && !entity_inside_attr)
+				return entity.MoveToFirstAttribute ();
+			if (!source.MoveToFirstAttribute ())
+				return false;
+			if (entity != null && entity_inside_attr) {
+				entity.Close ();
+				entity = null;
+			}
+			inside_attr = true;
+			return true;
+		}
+
+		public override bool MoveToNextAttribute ()
+		{
+			if (entity != null && !entity_inside_attr)
+				return entity.MoveToNextAttribute ();
+			if (!source.MoveToNextAttribute ())
+				return false;
+			if (entity != null && entity_inside_attr) {
+				entity.Close ();
+				entity = null;
+			}
+			inside_attr = true;
+			return true;
+		}
+
+		public override bool Read ()
+		{
+			if (do_resolve) {
+				DoResolveEntity ();
+				do_resolve = false;
+			}
+
+			inside_attr = false;
+
+			if (entity != null && (entity_inside_attr || entity.EOF)) {
+				entity.Close ();
+				entity = null;
+			}
+			if (entity != null) {
+				if (entity.Read ())
+					return true;
+				if (EntityHandling == EntityHandling.ExpandEntities) {
+					// EndEntity must be skipped
+					entity.Close ();
+					entity = null;
+					return Read ();
+				}
+				else
+					return true; // either success or EndEntity
+			}
+			else {
+				if (!source.Read ())
+					return false;
+				if (EntityHandling == EntityHandling.ExpandEntities
+					&& source.NodeType == XmlNodeType.EntityReference) {
+					ResolveEntity ();
+					return Read ();
+				}
+				return true;
+			}
+		}
+
+		public override bool ReadAttributeValue ()
+		{
+			if (entity != null && entity_inside_attr) {
+				if (entity.EOF) {
+					entity.Close ();
+					entity = null;
+				}
+				else {
+					entity.Read ();
+					return true; // either success or EndEntity
+				}
+			}
+			return Current.ReadAttributeValue ();
+		}
+
+		public override string ReadString ()
+		{
+			return base.ReadString ();
+		}
+
+		public override void ResolveEntity ()
+		{
+#if NET_2_0
+			DoResolveEntity ();
+#else
+			do_resolve = true;
+#endif
+		}
+
+		void DoResolveEntity ()
+		{
+			if (entity != null)
+				entity.ResolveEntity ();
+			else {
+				if (source.NodeType != XmlNodeType.EntityReference)
+					throw new InvalidOperationException ("The current node is not an Entity Reference");
+				if (ParserContext.Dtd == null)
+					throw new XmlException (this as IXmlLineInfo, this.BaseURI, String.Format ("Cannot resolve entity without DTD: '{0}'", source.Name));
+				XmlReader entReader = ParserContext.Dtd.GenerateEntityContentReader (
+					source.Name, ParserContext);
+				if (entReader == null)
+					throw new XmlException (this as IXmlLineInfo, this.BaseURI, String.Format ("Reference to undeclared entity '{0}'.", source.Name));
+
+				entity = new EntityResolvingXmlReader (
+					entReader, inside_attr);
+				entity.CopyProperties (this);
+			}
+		}
+
+		public override void Skip ()
+		{
+			base.Skip ();
+		}
+
+		public bool HasLineInfo ()
+		{
+			IXmlLineInfo li = Current as IXmlLineInfo;
+			return li == null ? false : li.HasLineInfo ();
+		}
+
+		#endregion
+	}
+}