using System;
using System.IO;
using System.Text;

namespace Common.Xml
{
    /// <summary>
    /// A StreamReader that excludes XML-illegal characters while reading.
    /// </summary>
    public class XmlSanitizingStream : StreamReader
    {
        /// <summary>
        /// The charactet that denotes the end of a file has been reached.
        /// </summary>
        private const int Eof = -1;

        /// <summary>Create an instance of XmlSanitizingStream.</summary>
        /// <param name="streamToSanitize">
        /// The stream to sanitize of illegal XML characters.
        /// </param>
        public XmlSanitizingStream(Stream streamToSanitize)
            : base(streamToSanitize, true)
        { }

        public XmlSanitizingStream(Stream streamToSanitize, Encoding encoding)
            : base(streamToSanitize, encoding)
        { }

        /// <summary>
        /// Get whether an integer represents a legal XML 1.0 or 1.1 character. See
        /// the specification at w3.org for these characters.
        /// </summary>
        /// <param name="xmlVersion">
        /// The version number as a string. Use "1.0" for XML 1.0 character
        /// validation, and use "1.1" for XML 1.1 character validation.
        /// </param>
        /// <param name="character"> </param>
        public static bool IsLegalXmlChar(string xmlVersion, int character)
        {
            switch (xmlVersion)
            {
                case "1.1": // http://www.w3.org/TR/xml11/#charsets
                    {
                        return
                        !(
                             character <= 0x8 ||
                             character == 0xB ||
                             character == 0xC ||
                            (character >= 0xE && character <= 0x1F) ||
                            (character >= 0x7F && character <= 0x84) ||
                            (character >= 0x86 && character <= 0x9F) ||
                             character > 0x10FFFF
                        );
                    }
                case "1.0": // http://www.w3.org/TR/REC-xml/#charsets
                    {
                        return
                        (
                             character == 0x9 /* == '\t' == 9   */          ||
                             character == 0xA /* == '\n' == 10  */          ||
                             character == 0xD /* == '\r' == 13  */          ||
                            (character >= 0x20 && character <= 0xD7FF) ||
                            (character >= 0xE000 && character <= 0xFFFD) ||
                            (character >= 0x10000 && character <= 0x10FFFF)
                        );
                    }
                default:
                    {
                        throw new ArgumentOutOfRangeException
                            ("xmlVersion", string.Format("'{0}' is not a valid XML version.", xmlVersion));
                    }
            }
        }

        /// <summary>
        /// Get whether an integer represents a legal XML 1.0 character. See the  
        /// specification at w3.org for these characters.
        /// </summary>
        public static bool IsLegalXmlChar(int character)
        {
            return IsLegalXmlChar("1.0", character);
        }

        public override int Read()
        {
            // Read each character, skipping over characters that XML has prohibited

            int nextCharacter;

            do
            {
                // Read a character

                if ((nextCharacter = base.Read()) == Eof)
                {
                    // If the character denotes the end of the file, stop reading

                    break;
                }
            }

            // Skip the character if it's prohibited, and try the next

            while (!IsLegalXmlChar(nextCharacter));

            return nextCharacter;
        }

        public override int Peek()
        {
            // Return the next legl XML character without reading it 

            int nextCharacter;

            do
            {
                // See what the next character is 

                nextCharacter = base.Peek();
            }
            while
            (
                // If it's prohibited XML, skip over the character in the stream
                // and try the next.

                !IsLegalXmlChar(nextCharacter) &&
                (nextCharacter = base.Read()) != Eof
            );

            return nextCharacter;

        } // method

        #region Read*() method overrides

        // The following methods are exact copies of the methods in TextReader, 
        // extracting by disassembling it in Refelctor

        public override int Read(char[] buffer, int index, int count)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException("buffer");
            }
            if (index < 0)
            {
                throw new ArgumentOutOfRangeException("index");
            }
            if (count < 0)
            {
                throw new ArgumentOutOfRangeException("count");
            }
            if ((buffer.Length - index) < count)
            {
                throw new ArgumentException();
            }
            int num = 0;
            do
            {
                int num2 = Read();
                if (num2 == -1)
                {
                    return num;
                }
                buffer[index + num++] = (char) num2;
            }
            while (num < count);
            return num;
        }

        public override int ReadBlock(char[] buffer, int index, int count)
        {
            int num;
            int num2 = 0;
            do
            {
                num2 += num = Read(buffer, index + num2, count - num2);
            }
            while ((num > 0) && (num2 < count));
            return num2;
        }

        public override string ReadLine()
        {
            StringBuilder builder = new StringBuilder();
            while (true)
            {
                int num = Read();
                switch (num)
                {
                    case -1:
                        if (builder.Length > 0)
                        {
                            return builder.ToString();
                        }
                        return null;

                    case 13:
                    case 10:
                        if ((num == 13) && (Peek() == 10))
                        {
                            Read();
                        }
                        return builder.ToString();
                }
                builder.Append((char) num);
            }
        }

        public override string ReadToEnd()
        {
            int num;
            char[] buffer = new char[0x1000];
            StringBuilder builder = new StringBuilder(0x1000);
            while ((num = Read(buffer, 0, buffer.Length)) != 0)
            {
                builder.Append(buffer, 0, num);
            }
            return builder.ToString();
        }

        #endregion

    } // class
}