//
// XmlReader.cs
//
// Authors:
// 	Jason Diamond (jason@injektilo.org)
// 	Gonzalo Paniagua Javier (gonzalo@ximian.com)
//	Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
//
// (C) 2001, 2002 Jason Diamond  http://injektilo.org/
// (c) 2002 Ximian, Inc. (http://www.ximian.com)
// (C) 2003 Atsushi Enomoto
//

//
// 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.
//
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Text;
#if !NET_2_1
using System.Xml.Schema; // only required for NET_2_0 (SchemaInfo)
using System.Xml.Serialization; // only required for NET_2_0 (SchemaInfo)
using Mono.Xml.Schema; // only required for NET_2_0
#endif
using Mono.Xml; // only required for NET_2_0

namespace System.Xml
{
#if NET_2_0
	public abstract class XmlReader : IDisposable
#else
	public abstract class XmlReader
#endif
	{
		private StringBuilder readStringBuffer;
		private XmlReaderBinarySupport binary;
#if NET_2_0
		private XmlReaderSettings settings;
#endif

		#region Constructor

		protected XmlReader ()
		{
		}

		#endregion

		#region Properties

		public abstract int AttributeCount { get; }

		public abstract string BaseURI { get; }

		internal XmlReaderBinarySupport Binary {
			get { return binary; }
		}

		internal XmlReaderBinarySupport.CharGetter BinaryCharGetter {
			get { return binary != null ? binary.Getter : null; }
			set {
				if (binary == null)
					binary = new XmlReaderBinarySupport (this);
				binary.Getter = value;
			}
		}

#if NET_2_0
		// To enable it internally in sys.xml, just insert these
		// two lines into Read():
		//
		//	#if NET_2_0
		//	if (Binary != null)
		//		Binary.Reset ();
		//	#endif
		//
		public virtual bool CanReadBinaryContent {
			get { return false; }
		}

		public virtual bool CanReadValueChunk {
			get { return false; }
		}
#else
		internal virtual bool CanReadBinaryContent {
			get { return false; }
		}

		internal virtual bool CanReadValueChunk {
			get { return false; }
		}
#endif

		public virtual bool CanResolveEntity
		{
			get	{ return false; }
		}

		public abstract int Depth { get; }

		public abstract bool EOF { get; }

		public virtual bool HasAttributes
		{
			get { return AttributeCount > 0; }
		}

#if !NET_2_1
		public abstract bool HasValue { get; }
#endif

		public abstract bool IsEmptyElement { get; }

#if NET_2_0
		public virtual bool IsDefault {
			get { return false; }
		}

		public virtual string this [int i] {
			get { return GetAttribute (i); }
		}

		public virtual string this [string name] {
			get { return GetAttribute (name); }
		}

		public virtual string this [string name, string namespaceURI] {
			get { return GetAttribute (name, namespaceURI); }
		}
#else
		public abstract bool IsDefault { get; }

		public abstract string this [int i] { get; }

		public abstract string this [string name] { get; }

		public abstract string this [string localName, string namespaceName] { get; }
#endif

		public abstract string LocalName { get; }

#if NET_2_0
		public virtual string Name {
			get {
				return Prefix.Length > 0 ?
					String.Concat (Prefix, ":", LocalName) :
					LocalName;
			}
		}
#else
		public abstract string Name { get; }
#endif

		public abstract string NamespaceURI { get; }

		public abstract XmlNameTable NameTable { get; }

		public abstract XmlNodeType NodeType { get; }

		public abstract string Prefix { get; }

#if NET_2_0
		public virtual char QuoteChar {
			get { return '\"'; }
		}
#else
		public abstract char QuoteChar { get; }
#endif

		public abstract ReadState ReadState { get; }

#if NET_2_0
#if !NET_2_1
		public virtual IXmlSchemaInfo SchemaInfo {
			get { return null; }
		}
#endif

		public virtual XmlReaderSettings Settings {
			get { return settings; }
		}
#endif

		public abstract string Value { get; }

#if NET_2_0
		public virtual string XmlLang {
			get { return String.Empty; }
		}

		public virtual XmlSpace XmlSpace {
			get { return XmlSpace.None; }
		}
#else
		public abstract string XmlLang { get; }

		public abstract XmlSpace XmlSpace { get; }
#endif

		#endregion

		#region Methods

		public abstract void Close ();

#if NET_2_0
		private static XmlNameTable PopulateNameTable (
			XmlReaderSettings settings)
		{
			XmlNameTable nameTable = settings.NameTable;
			if (nameTable == null)
				nameTable = new NameTable ();
			return nameTable;
		}

		private static XmlParserContext PopulateParserContext (
			XmlReaderSettings settings, string baseUri)
		{
			XmlNameTable nt = PopulateNameTable (settings);
			return new XmlParserContext (nt,
				new XmlNamespaceManager (nt),
				null,
				null,
				null,
				null,
				baseUri,
				null,
				XmlSpace.None,
				null);
		}

		private static XmlNodeType GetNodeType (
			XmlReaderSettings settings)
		{
			ConformanceLevel level = settings != null ? settings.ConformanceLevel : ConformanceLevel.Auto;
			return
				level == ConformanceLevel.Fragment ?
				XmlNodeType.Element :
				XmlNodeType.Document;
		}

		public static XmlReader Create (Stream stream)
		{
			return Create (stream, null);
		}

		public static XmlReader Create (string url)
		{
			return Create (url, null);
		}

		public static XmlReader Create (TextReader reader)
		{
			return Create (reader, null);
		}

		public static XmlReader Create (string url, XmlReaderSettings settings)
		{
			return Create (url, settings, null);
		}

		public static XmlReader Create (Stream stream, XmlReaderSettings settings)
		{
			return Create (stream, settings, String.Empty);
		}

		public static XmlReader Create (TextReader reader, XmlReaderSettings settings)
		{
			return Create (reader, settings, String.Empty);
		}

		static XmlReaderSettings PopulateSettings (XmlReaderSettings src)
		{
			if (src == null)
				return new XmlReaderSettings ();
			else
				return src.Clone ();
		}

		public static XmlReader Create (Stream stream, XmlReaderSettings settings, string baseUri)
		{
			settings = PopulateSettings (settings);
			return Create (stream, settings,
				PopulateParserContext (settings, baseUri));
		}

		public static XmlReader Create (TextReader reader, XmlReaderSettings settings, string baseUri)
		{
			settings = PopulateSettings (settings);
			return Create (reader, settings,
				PopulateParserContext (settings, baseUri));
		}

		public static XmlReader Create (XmlReader reader, XmlReaderSettings settings)
		{
			settings = PopulateSettings (settings);
			XmlReader r = CreateFilteredXmlReader (reader, settings);
			r.settings = settings;
			return r;
		}

		public static XmlReader Create (string url, XmlReaderSettings settings, XmlParserContext context)
		{
			settings = PopulateSettings (settings);
			bool closeInputBak = settings.CloseInput;
			try {
				settings.CloseInput = true; // forced. See XmlReaderCommonTests.CreateFromUrlClose().
				if (context == null)
					context = PopulateParserContext (settings, url);
				XmlTextReader xtr = new XmlTextReader (false, settings.XmlResolver, url, GetNodeType (settings), context);
				XmlReader ret = CreateCustomizedTextReader (xtr, settings);
				return ret;
			} finally {
				settings.CloseInput = closeInputBak;
			}
		}

		public static XmlReader Create (Stream stream, XmlReaderSettings settings, XmlParserContext context)
		{
			settings = PopulateSettings (settings);
			if (context == null)
				context = PopulateParserContext (settings, String.Empty);
			return CreateCustomizedTextReader (new XmlTextReader (stream, GetNodeType (settings), context), settings);
		}

		public static XmlReader Create (TextReader reader, XmlReaderSettings settings, XmlParserContext context)
		{
			settings = PopulateSettings (settings);
			if (context == null)
				context = PopulateParserContext (settings, String.Empty);
			return CreateCustomizedTextReader (new XmlTextReader (context.BaseURI, reader, GetNodeType (settings), context), settings);
		}

		private static XmlReader CreateCustomizedTextReader (XmlTextReader reader, XmlReaderSettings settings)
		{
			reader.XmlResolver = settings.XmlResolver;
			// Normalization is set true by default.
			reader.Normalization = true;
			reader.EntityHandling = EntityHandling.ExpandEntities;

			if (settings.ProhibitDtd)
				reader.ProhibitDtd = true;

			if (!settings.CheckCharacters)
				reader.CharacterChecking = false;

			// I guess it might be changed in 2.0 RTM to set true
			// as default, or just disappear. It goes against
			// XmlTextReader's default usage and users will have 
			// to close input manually (that's annoying). Moreover,
			// MS XmlTextReader consumes text input more than 
			// actually read and users can acquire those extra
			// consumption by GetRemainder() that returns different
			// TextReader.
			reader.CloseInput = settings.CloseInput;

			// I would like to support it in detail later;
			// MSDN description looks source of confusion. We don't
			// need examples, but precise list of how it works.
			reader.Conformance = settings.ConformanceLevel;

			reader.AdjustLineInfoOffset (settings.LineNumberOffset,
				settings.LinePositionOffset);

			if (settings.NameTable != null)
				reader.SetNameTable (settings.NameTable);

			XmlReader r = CreateFilteredXmlReader (reader, settings);
			r.settings = settings;
			return r;
		}

		private static XmlReader CreateFilteredXmlReader (XmlReader reader, XmlReaderSettings settings)
		{
			ConformanceLevel conf = ConformanceLevel.Auto;
			if (reader is XmlTextReader)
				conf = ((XmlTextReader) reader).Conformance;
			else if (reader.Settings != null)
				conf = reader.Settings.ConformanceLevel;
			else
				conf = settings.ConformanceLevel;
			if (settings.ConformanceLevel != ConformanceLevel.Auto &&
				conf != settings.ConformanceLevel)
				throw new InvalidOperationException (String.Format ("ConformanceLevel cannot be overwritten by a wrapping XmlReader. The source reader has {0}, while {1} is specified.", conf, settings.ConformanceLevel));
			settings.ConformanceLevel = conf;

			reader = CreateValidatingXmlReader (reader, settings);

			if ( settings.IgnoreComments ||
			     settings.IgnoreProcessingInstructions ||
			     settings.IgnoreWhitespace)
				return new XmlFilterReader (reader, settings);
			else {
				reader.settings = settings;
				return reader;
			}
		}

		private static XmlReader CreateValidatingXmlReader (XmlReader reader, XmlReaderSettings settings)
		{
#if NET_2_1
			return reader;
#else
			XmlValidatingReader xvr = null;
			switch (settings.ValidationType) {
			// Auto and XDR are obsoleted in 2.0 and therefore ignored.
			default:
				return reader;
			case ValidationType.DTD:
				xvr = new XmlValidatingReader (reader);
				xvr.XmlResolver = settings.XmlResolver;
				xvr.ValidationType = ValidationType.DTD;
				break;
			case ValidationType.Schema:
				return new XmlSchemaValidatingReader (reader, settings);
			}

			// Actually I don't think they are treated in DTD validation though...
			if ((settings.ValidationFlags & XmlSchemaValidationFlags.ProcessIdentityConstraints) == 0)
				throw new NotImplementedException ();
			//if ((settings.ValidationFlags & XmlSchemaValidationFlags.ProcessInlineSchema) != 0)
			//	throw new NotImplementedException ();
			//if ((settings.ValidationFlags & XmlSchemaValidationFlags.ProcessSchemaLocation) != 0)
			//	throw new NotImplementedException ();
			//if ((settings.ValidationFlags & XmlSchemaValidationFlags.ReportValidationWarnings) == 0)
			//	throw new NotImplementedException ();

			return xvr != null ? xvr : reader;
#endif
		}

		void IDisposable.Dispose ()
		{
			Dispose (false);
		}

		protected virtual void Dispose (bool disposing)
		{
			if (ReadState != ReadState.Closed)
				Close ();
		}
#endif

		public abstract string GetAttribute (int i);

		public abstract string GetAttribute (string name);

		public abstract string GetAttribute (
			string localName,
			string namespaceName);

		public static bool IsName (string s)
		{
			return s != null && XmlChar.IsName (s);
		}

		public static bool IsNameToken (string s)
		{
			return s != null && XmlChar.IsNmToken (s);
		}

		public virtual bool IsStartElement ()
		{
			return (MoveToContent () == XmlNodeType.Element);
		}

		public virtual bool IsStartElement (string name)
		{
			if (!IsStartElement ())
				return false;

			return (Name == name);
		}

		public virtual bool IsStartElement (string localName, string namespaceName)
		{
			if (!IsStartElement ())
				return false;

			return (LocalName == localName && NamespaceURI == namespaceName);
		}

		public abstract string LookupNamespace (string prefix);

#if NET_2_0
		public virtual void MoveToAttribute (int i)
		{
			if (i >= AttributeCount)
				throw new ArgumentOutOfRangeException ();
			MoveToFirstAttribute ();
			for (int a = 0; a < i; a++)
				MoveToNextAttribute ();
		}
#else
		public abstract void MoveToAttribute (int i);
#endif

		public abstract bool MoveToAttribute (string name);

		public abstract bool MoveToAttribute (
			string localName,
			string namespaceName);

		private bool IsContent (XmlNodeType nodeType)
		{
			/* MS doc says:
			 * (non-white space text, CDATA, Element, EndElement, EntityReference, or EndEntity)
			 */
			switch (nodeType) {
			case XmlNodeType.Text:
				return true;
			case XmlNodeType.CDATA:
				return true;
			case XmlNodeType.Element:
				return true;
			case XmlNodeType.EndElement:
				return true;
			case XmlNodeType.EntityReference:
				return true;
			case XmlNodeType.EndEntity:
				return true;
			}

			return false;
		}

		public virtual XmlNodeType MoveToContent ()
		{
			if (NodeType == XmlNodeType.Attribute)
				MoveToElement ();

			do {
				if (IsContent (NodeType))
					return NodeType;
				Read ();
			} while (!EOF);
			return XmlNodeType.None;
		}

		public abstract bool MoveToElement ();

		public abstract bool MoveToFirstAttribute ();

		public abstract bool MoveToNextAttribute ();

		public abstract bool Read ();

#if !NET_2_1
		public abstract bool ReadAttributeValue ();
#endif

		public virtual string ReadElementString ()
		{
			if (MoveToContent () != XmlNodeType.Element) {
				string error = String.Format ("'{0}' is an invalid node type.",
							      NodeType.ToString ());
				throw XmlError (error);
			}

			string result = String.Empty;
			if (!IsEmptyElement) {
				Read ();
				result = ReadString ();
				if (NodeType != XmlNodeType.EndElement) {
					string error = String.Format ("'{0}' is an invalid node type.",
								      NodeType.ToString ());
					throw XmlError (error);
				}
			}

			Read ();
			return result;
		}

		public virtual string ReadElementString (string name)
		{
			if (MoveToContent () != XmlNodeType.Element) {
				string error = String.Format ("'{0}' is an invalid node type.",
							      NodeType.ToString ());
				throw XmlError (error);
			}

			if (name != Name) {
				string error = String.Format ("The {0} tag from namespace {1} is expected.",
							      Name, NamespaceURI);
				throw XmlError (error);
			}

			string result = String.Empty;
			if (!IsEmptyElement) {
				Read ();
				result = ReadString ();
				if (NodeType != XmlNodeType.EndElement) {
					string error = String.Format ("'{0}' is an invalid node type.",
								      NodeType.ToString ());
					throw XmlError (error);
				}
			}

			Read ();
			return result;
		}

		public virtual string ReadElementString (string localName, string namespaceName)
		{
			if (MoveToContent () != XmlNodeType.Element) {
				string error = String.Format ("'{0}' is an invalid node type.",
							      NodeType.ToString ());
				throw XmlError (error);
			}

			if (localName != LocalName || NamespaceURI != namespaceName) {
				string error = String.Format ("The {0} tag from namespace {1} is expected.",
							      LocalName, NamespaceURI);
				throw XmlError (error);
			}

			string result = String.Empty;
			if (!IsEmptyElement) {
				Read ();
				result = ReadString ();
				if (NodeType != XmlNodeType.EndElement) {
					string error = String.Format ("'{0}' is an invalid node type.",
								      NodeType.ToString ());
					throw XmlError (error);
				}
			}

			Read ();
			return result;
		}

		public virtual void ReadEndElement ()
		{
			if (MoveToContent () != XmlNodeType.EndElement) {
				string error = String.Format ("'{0}' is an invalid node type.",
							      NodeType.ToString ());
				throw XmlError (error);
			}

			Read ();
		}

		public virtual string ReadInnerXml ()
		{
			if (ReadState != ReadState.Interactive || NodeType == XmlNodeType.EndElement)
				return String.Empty;

			if (IsEmptyElement) {
				Read ();
				return String.Empty;
			}
			StringWriter sw = new StringWriter ();
			XmlTextWriter xtw = new XmlTextWriter (sw);
			if (NodeType == XmlNodeType.Element) {
				int startDepth = Depth;
				Read ();
				while (startDepth < Depth) {
					if (ReadState != ReadState.Interactive)
						throw XmlError ("Unexpected end of the XML reader.");
					xtw.WriteNode (this, false);
				}
				// reader is now end element, then proceed once more.
				Read ();
			}
			else
				xtw.WriteNode (this, false);

			return sw.ToString ();
		}

		public virtual string ReadOuterXml ()
		{
			if (ReadState != ReadState.Interactive || NodeType == XmlNodeType.EndElement)
				return String.Empty;

			switch (NodeType) {
			case XmlNodeType.Element:
			case XmlNodeType.Attribute:
				StringWriter sw = new StringWriter ();
				XmlTextWriter xtw = new XmlTextWriter (sw);
				xtw.WriteNode (this, false);
				return sw.ToString ();
			default:
				Skip ();
				return String.Empty;
			}
		}

		public virtual void ReadStartElement ()
		{
			if (MoveToContent () != XmlNodeType.Element) {
				string error = String.Format ("'{0}' is an invalid node type.",
							      NodeType.ToString ());
				throw XmlError (error);
			}

			Read ();
		}

		public virtual void ReadStartElement (string name)
		{
			if (MoveToContent () != XmlNodeType.Element) {
				string error = String.Format ("'{0}' is an invalid node type.",
							      NodeType.ToString ());
				throw XmlError (error);
			}

			if (name != Name) {
				string error = String.Format ("The {0} tag from namespace {1} is expected.",
							      Name, NamespaceURI);
				throw XmlError (error);
			}

			Read ();
		}

		public virtual void ReadStartElement (string localName, string namespaceName)
		{
			if (MoveToContent () != XmlNodeType.Element) {
				string error = String.Format ("'{0}' is an invalid node type.",
							      NodeType.ToString ());
				throw XmlError (error);
			}

			if (localName != LocalName || NamespaceURI != namespaceName) {
				string error = String.Format ("Expecting {0} tag from namespace {1}, got {2} and {3} instead",
							      localName, namespaceName,
							      LocalName, NamespaceURI);
				throw XmlError (error);
			}

			Read ();
		}

		public virtual string ReadString ()
		{
			if (readStringBuffer == null)
				readStringBuffer = new StringBuilder ();
			readStringBuffer.Length = 0;

			MoveToElement ();

			switch (NodeType) {
			default:
				return String.Empty;
			case XmlNodeType.Element:
				if (IsEmptyElement)
					return String.Empty;
				do {
					Read ();
					switch (NodeType) {
					case XmlNodeType.Text:
					case XmlNodeType.CDATA:
					case XmlNodeType.Whitespace:
					case XmlNodeType.SignificantWhitespace:
						readStringBuffer.Append (Value);
						continue;
					}
					break;
				} while (true);
				break;
			case XmlNodeType.Text:
			case XmlNodeType.CDATA:
			case XmlNodeType.Whitespace:
			case XmlNodeType.SignificantWhitespace:
				do {
					switch (NodeType) {
					case XmlNodeType.Text:
					case XmlNodeType.CDATA:
					case XmlNodeType.Whitespace:
					case XmlNodeType.SignificantWhitespace:
						readStringBuffer.Append (Value);
						Read ();
						continue;
					}
					break;
				} while (true);
				break;
			}
			string ret = readStringBuffer.ToString ();
			readStringBuffer.Length = 0;
			return ret;
		}

#if NET_2_0
		public virtual Type ValueType {
			get { return typeof (string); }
		}

		public virtual bool ReadToDescendant (string name)
		{
			if (ReadState == ReadState.Initial) {
				MoveToContent ();
				if (IsStartElement (name))
					return true;
			}
			if (NodeType != XmlNodeType.Element || IsEmptyElement)
				return false;
			int depth = Depth;
			for (Read (); depth < Depth; Read ())
				if (NodeType == XmlNodeType.Element && name == Name)
					return true;
			return false;
		}

		public virtual bool ReadToDescendant (string localName, string namespaceURI)
		{
			if (ReadState == ReadState.Initial) {
				MoveToContent ();
				if (IsStartElement (localName, namespaceURI))
					return true;
			}
			if (NodeType != XmlNodeType.Element || IsEmptyElement)
				return false;
			int depth = Depth;
			for (Read (); depth < Depth; Read ())
				if (NodeType == XmlNodeType.Element && localName == LocalName && namespaceURI == NamespaceURI)
					return true;
			return false;
		}

		public virtual bool ReadToFollowing (string name)
		{
			while (Read ())
				if (NodeType == XmlNodeType.Element && name == Name)
					return true;
			return false;
		}

		public virtual bool ReadToFollowing (string localName, string namespaceURI)
		{
			while (Read ())
				if (NodeType == XmlNodeType.Element && localName == Name && namespaceURI == NamespaceURI)
					return true;
			return false;
		}

		public virtual bool ReadToNextSibling (string name)
		{
			if (ReadState != ReadState.Interactive)
				return false;
			int depth = Depth;
			Skip ();
			for (; !EOF && depth <= Depth; Skip ())
				if (NodeType == XmlNodeType.Element && name == Name)
					return true;
			return false;
		}

		public virtual bool ReadToNextSibling (string localName, string namespaceURI)
		{
			if (ReadState != ReadState.Interactive)
				return false;
			int depth = Depth;
			Skip ();
			for (; !EOF && depth <= Depth; Skip ())
				if (NodeType == XmlNodeType.Element && localName == LocalName && namespaceURI == NamespaceURI)
					return true;
			return false;
		}

		public virtual XmlReader ReadSubtree ()
		{
			return new SubtreeXmlReader (this);
		}

		private string ReadContentString ()
		{
			if (NodeType == XmlNodeType.Attribute)
				return Value;
			return ReadContentString (true);
		}

		private string ReadContentString (bool isText)
		{
			if (isText) {
				switch (NodeType) {
				case XmlNodeType.Text:
				case XmlNodeType.SignificantWhitespace:
				case XmlNodeType.Whitespace:
				case XmlNodeType.CDATA:
					break;
				default:
					throw new InvalidOperationException (String.Format ("Node type {0} is not supported in this operation.{1}", NodeType, GetLocation ()));
				}
			}

			string value = String.Empty;
			do {
				switch (NodeType) {
				case XmlNodeType.Element:
					if (isText)
						return value;
					throw XmlError ("Child element is not expected in this operation.");
				case XmlNodeType.EndElement:
					return value;
				case XmlNodeType.Text:
				case XmlNodeType.CDATA:
				case XmlNodeType.SignificantWhitespace:
				case XmlNodeType.Whitespace:
					value += Value;
					break;
				}
			} while (Read ());
			throw XmlError ("Unexpected end of document.");
		}

		string GetLocation ()
		{
			IXmlLineInfo li = this as IXmlLineInfo;
			return li != null && li.HasLineInfo () ?
				String.Format (" {0} (line {1}, column {2})", BaseURI, li.LineNumber, li.LinePosition) : String.Empty;
		}

		[MonoTODO]
		public virtual object ReadElementContentAsObject ()
		{
			return ReadElementContentAs (ValueType, null);
		}

		[MonoTODO]
		public virtual object ReadElementContentAsObject (string localName, string namespaceURI)
		{
			return ReadElementContentAs (ValueType, null, localName, namespaceURI);
		}

		[MonoTODO]
		public virtual object ReadContentAsObject ()
		{
			return ReadContentAs (ValueType, null);
		}

		public virtual object ReadElementContentAs (Type type, IXmlNamespaceResolver resolver)
		{
			bool isEmpty = IsEmptyElement;
			ReadStartElement ();
			object obj = ValueAs (isEmpty ? String.Empty : ReadContentString (false), type, resolver);
			if (!isEmpty)
				ReadEndElement ();
			return obj;
		}

		public virtual object ReadElementContentAs (Type type, IXmlNamespaceResolver resolver, string localName, string namespaceURI)
		{
			ReadStartElement (localName, namespaceURI);
			object obj = ReadContentAs (type, resolver);
			ReadEndElement ();
			return obj;
		}

		public virtual object ReadContentAs (Type type, IXmlNamespaceResolver resolver)
		{
			return ValueAs (ReadContentString (), type, resolver);
		}

		private object ValueAs (string text, Type type, IXmlNamespaceResolver resolver)
		{
			try {
				if (type == typeof (object))
					return text;
				if (type == typeof (XmlQualifiedName)) {
					if (resolver != null)
						return XmlQualifiedName.Parse (text, resolver);
					else
						return XmlQualifiedName.Parse (text, this);
				}

				switch (Type.GetTypeCode (type)) {
				case TypeCode.Boolean:
					return XQueryConvert.StringToBoolean (text);
				case TypeCode.DateTime:
					return XQueryConvert.StringToDateTime (text);
				case TypeCode.Decimal:
					return XQueryConvert.StringToDecimal (text);
				case TypeCode.Double:
					return XQueryConvert.StringToDouble (text);
				case TypeCode.Int32:
					return XQueryConvert.StringToInt (text);
				case TypeCode.Int64:
					return XQueryConvert.StringToInteger (text);
				case TypeCode.Single:
					return XQueryConvert.StringToFloat (text);
				case TypeCode.String:
					return text;
				}
			} catch (Exception ex) {
				throw XmlError (String.Format ("Current text value '{0}' is not acceptable for specified type '{1}'. {2}", text, type, ex != null ? ex.Message : String.Empty), ex);
			}
			throw new ArgumentException (String.Format ("Specified type '{0}' is not supported.", type));
		}

		public virtual bool ReadElementContentAsBoolean ()
		{
			try {
				return XQueryConvert.StringToBoolean (ReadElementContentAsString ());
			} catch (FormatException ex) {
				throw XmlError ("Typed value is invalid.", ex);
			}
		}

		public virtual DateTime ReadElementContentAsDateTime ()
		{
			try {
				return XQueryConvert.StringToDateTime (ReadElementContentAsString ());
			} catch (FormatException ex) {
				throw XmlError ("Typed value is invalid.", ex);
			}
		}

		public virtual decimal ReadElementContentAsDecimal ()
		{
			try {
				return XQueryConvert.StringToDecimal (ReadElementContentAsString ());
			} catch (FormatException ex) {
				throw XmlError ("Typed value is invalid.", ex);
			}
		}

		public virtual double ReadElementContentAsDouble ()
		{
			try {
				return XQueryConvert.StringToDouble (ReadElementContentAsString ());
			} catch (FormatException ex) {
				throw XmlError ("Typed value is invalid.", ex);
			}
		}

		public virtual float ReadElementContentAsFloat ()
		{
			try {
				return XQueryConvert.StringToFloat (ReadElementContentAsString ());
			} catch (FormatException ex) {
				throw XmlError ("Typed value is invalid.", ex);
			}
		}

		public virtual int ReadElementContentAsInt ()
		{
			try {
				return XQueryConvert.StringToInt (ReadElementContentAsString ());
			} catch (FormatException ex) {
				throw XmlError ("Typed value is invalid.", ex);
			}
		}

		public virtual long ReadElementContentAsLong ()
		{
			try {
				return XQueryConvert.StringToInteger (ReadElementContentAsString ());
			} catch (FormatException ex) {
				throw XmlError ("Typed value is invalid.", ex);
			}
		}

		public virtual string ReadElementContentAsString ()
		{
			bool isEmpty = IsEmptyElement;
			ReadStartElement ();
			if (isEmpty)
				return String.Empty;
			string s = ReadContentString (false);
			ReadEndElement ();
			return s;
		}

		public virtual bool ReadElementContentAsBoolean (string localName, string namespaceURI)
		{
			try {
				return XQueryConvert.StringToBoolean (ReadElementContentAsString (localName, namespaceURI));
			} catch (FormatException ex) {
				throw XmlError ("Typed value is invalid.", ex);
			}
		}

		public virtual DateTime ReadElementContentAsDateTime (string localName, string namespaceURI)
		{
			try {
				return XQueryConvert.StringToDateTime (ReadElementContentAsString (localName, namespaceURI));
			} catch (FormatException ex) {
				throw XmlError ("Typed value is invalid.", ex);
			}
		}

		public virtual decimal ReadElementContentAsDecimal (string localName, string namespaceURI)
		{
			try {
				return XQueryConvert.StringToDecimal (ReadElementContentAsString (localName, namespaceURI));
			} catch (FormatException ex) {
				throw XmlError ("Typed value is invalid.", ex);
			}
		}

		public virtual double ReadElementContentAsDouble (string localName, string namespaceURI)
		{
			try {
				return XQueryConvert.StringToDouble (ReadElementContentAsString (localName, namespaceURI));
			} catch (FormatException ex) {
				throw XmlError ("Typed value is invalid.", ex);
			}
		}

		public virtual float ReadElementContentAsFloat (string localName, string namespaceURI)
		{
			try {
				return XQueryConvert.StringToFloat (ReadElementContentAsString (localName, namespaceURI));
			} catch (FormatException ex) {
				throw XmlError ("Typed value is invalid.", ex);
			}
		}

		public virtual int ReadElementContentAsInt (string localName, string namespaceURI)
		{
			try {
				return XQueryConvert.StringToInt (ReadElementContentAsString (localName, namespaceURI));
			} catch (FormatException ex) {
				throw XmlError ("Typed value is invalid.", ex);
			}
		}

		public virtual long ReadElementContentAsLong (string localName, string namespaceURI)
		{
			try {
				return XQueryConvert.StringToInteger (ReadElementContentAsString (localName, namespaceURI));
			} catch (FormatException ex) {
				throw XmlError ("Typed value is invalid.", ex);
			}
		}

		public virtual string ReadElementContentAsString (string localName, string namespaceURI)
		{
			bool isEmpty = IsEmptyElement;
			ReadStartElement (localName, namespaceURI);
			if (isEmpty)
				return String.Empty;
			string s = ReadContentString (false);
			ReadEndElement ();
			return s;
		}

		public virtual bool ReadContentAsBoolean ()
		{
			try {
				return XQueryConvert.StringToBoolean (ReadContentString ());
			} catch (FormatException ex) {
				throw XmlError ("Typed value is invalid.", ex);
			}
		}

		public virtual DateTime ReadContentAsDateTime ()
		{
			try {
				return XQueryConvert.StringToDateTime (ReadContentString ());
			} catch (FormatException ex) {
				throw XmlError ("Typed value is invalid.", ex);
			}
		}

		public virtual decimal ReadContentAsDecimal ()
		{
			try {
				return XQueryConvert.StringToDecimal (ReadContentString ());
			} catch (FormatException ex) {
				throw XmlError ("Typed value is invalid.", ex);
			}
		}

		public virtual double ReadContentAsDouble ()
		{
			try {
				return XQueryConvert.StringToDouble (ReadContentString ());
			} catch (FormatException ex) {
				throw XmlError ("Typed value is invalid.", ex);
			}
		}

		public virtual float ReadContentAsFloat ()
		{
			try {
				return XQueryConvert.StringToFloat (ReadContentString ());
			} catch (FormatException ex) {
				throw XmlError ("Typed value is invalid.", ex);
			}
		}

		public virtual int ReadContentAsInt ()
		{
			try {
				return XQueryConvert.StringToInt (ReadContentString ());
			} catch (FormatException ex) {
				throw XmlError ("Typed value is invalid.", ex);
			}
		}

		public virtual long ReadContentAsLong ()
		{
			try {
				return XQueryConvert.StringToInteger (ReadContentString ());
			} catch (FormatException ex) {
				throw XmlError ("Typed value is invalid.", ex);
			}
		}

		public virtual string ReadContentAsString ()
		{
			return ReadContentString ();
		}

		public virtual int ReadContentAsBase64 (
			byte [] buffer, int offset, int length)
		{
			CheckSupport ();
			return binary.ReadContentAsBase64 (
				buffer, offset, length);
		}

		public virtual int ReadContentAsBinHex (
			byte [] buffer, int offset, int length)
		{
			CheckSupport ();
			return binary.ReadContentAsBinHex (
				buffer, offset, length);
		}

		public virtual int ReadElementContentAsBase64 (
			byte [] buffer, int offset, int length)
		{
			CheckSupport ();
			return binary.ReadElementContentAsBase64 (
				buffer, offset, length);
		}

		public virtual int ReadElementContentAsBinHex (
			byte [] buffer, int offset, int length)
		{
			CheckSupport ();
			return binary.ReadElementContentAsBinHex (
				buffer, offset, length);
		}
#endif

#if NET_2_0
		public virtual int ReadValueChunk (
			char [] buffer, int offset, int length)
#else
		internal virtual int ReadValueChunk (
			char [] buffer, int offset, int length)
#endif
		{
			if (!CanReadValueChunk)
				throw new NotSupportedException ();
			if (binary == null)
				binary = new XmlReaderBinarySupport (this);
			return binary.ReadValueChunk (buffer, offset, length);
		}

		private void CheckSupport ()
		{
			// Default implementation expects both.
			if (!CanReadBinaryContent || !CanReadValueChunk)
				throw new NotSupportedException ();
			if (binary == null)
				binary = new XmlReaderBinarySupport (this);
		}

#if !NET_2_1
		public abstract void ResolveEntity ();
#endif

		public virtual void Skip ()
		{
			if (ReadState != ReadState.Interactive)
				return;

			MoveToElement ();
			if (NodeType != XmlNodeType.Element || IsEmptyElement) {
				Read ();
				return;
			}
				
			int depth = Depth;
			while (Read () && depth < Depth)
				;
			if (NodeType == XmlNodeType.EndElement)
				Read ();
		}

		private XmlException XmlError (string message)
		{
			return new XmlException (this as IXmlLineInfo, BaseURI, message);
		}

		private XmlException XmlError (string message, Exception innerException)
		{
			return new XmlException (this as IXmlLineInfo, BaseURI, message);
		}

		#endregion
	}
}
