2005-01-31 Zoltan Varga <vargaz@freemail.hu>
[mono.git] / mcs / class / System.XML / System.Xml / XmlInputStream.cs
index 1b23ffae588da99fdd347c3dc65062c7b9e5f4d6..e60951cd38e85961d6e16e68ad55e2230725ce9e 100644 (file)
@@ -7,19 +7,43 @@
 //
 //     (C)2003 Atsushi Enomoto
 //
-using System;\r
-using System.IO;\r
-using System.Text;\r
-using System.Xml;\r
-\r
-namespace Mono.Xml.Native\r
-{\r
+
+//
+// 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;
+using System.IO;
+using System.Text;
+using System.Xml;
+
+namespace System.Xml
+{
        #region XmlStreamReader
-       public class XmlStreamReader : StreamReader
+       internal class XmlStreamReader : StreamReader
        {
-               public XmlStreamReader (XmlInputStream input)
+               XmlInputStream input;
+
+               XmlStreamReader (XmlInputStream input)
                        : base (input, input.ActualEncoding != null ? input.ActualEncoding : Encoding.UTF8)
                {
+                       this.input = input;
                }
 
                public XmlStreamReader (Stream input)
@@ -27,265 +51,268 @@ namespace Mono.Xml.Native
                {
                }
 
-               public XmlStreamReader (string url)
-                       : this (new XmlInputStream (url))
+               public override void Close ()
+               {
+                       this.input.Close ();
+               }
+
+               protected override void Dispose (bool disposing)
                {
+                       base.Dispose (disposing);
+                       if (disposing) {
+                               Close ();
+                       }
                }
+
        }
        #endregion
-\r
-       public class XmlInputStream : Stream\r
-       {\r
-               Encoding enc;\r
-               Stream stream;\r
-               byte[] buffer = new byte[256];\r
-               int bufLength;\r
-               int bufPos;\r
-\r
-               static XmlException encodingException = new XmlException ("invalid encoding specification.");\r
-\r
-               public XmlInputStream (string url)\r
-               {\r
-#if NetworkEnabled\r
-                       try {\r
-                               Uri uri = new Uri (url);\r
-                               Initialize (new MemoryStream (new System.Net.WebClient ().DownloadData (url)));\r
-                       } catch (UriFormatException ex) {\r
-                               Initialize (new FileStream (url, FileMode.Open));\r
-                       }\r
-#else\r
-                       Initialize (new FileStream (url, FileMode.Open));\r
-#endif\r
-               }\r
-\r
-               public XmlInputStream (Stream stream)\r
-               {\r
-                       Initialize (stream);\r
-               }\r
-\r
-               private void Initialize (Stream stream)\r
-               {\r
-                       // FIXME: seems too waste...\r
-                       MemoryStream ms = new MemoryStream ();\r
-                       this.stream = stream;\r
-                       int c = stream.ReadByte ();\r
-                       switch (c) {\r
-                       case 0xFF:\r
-                               c = stream.ReadByte ();\r
-                               if (c == 0xFE) {\r
-                                       // BOM-ed little endian utf-16\r
-                                       enc = Encoding.Unicode;\r
-                               } else {\r
-                                       // It doesn't start from "<?xml" then its encoding is utf-8\r
-                                       enc = Encoding.UTF8;\r
-                                       ms.WriteByte ((byte)0xFF);\r
-                                       ms.WriteByte ((byte)c);\r
-                               }\r
-                               break;\r
-                       case 0xFE:\r
-                               c = stream.ReadByte ();\r
-                               if (c == 0xFF) {\r
-                                       // BOM-ed big endian utf-16\r
-                                       enc = Encoding.BigEndianUnicode;\r
-                                       return;\r
-                               } else {\r
-                                       // It doesn't start from "<?xml" then its encoding is utf-8\r
-                                       enc = Encoding.UTF8;\r
-                                       ms.WriteByte ((byte)0xFE);\r
-                                       ms.WriteByte ((byte)c);\r
-                               }\r
-                               break;\r
-                       case 0xEF:\r
-                               enc = Encoding.UTF8;\r
-                               c = ReadByte ();\r
-                               if (c == 0xBB) {\r
-                                       c = ReadByte ();\r
-                                       if (c != 0xBF) {\r
-                                               ms.WriteByte ((byte)0xEF);\r
-                                               ms.WriteByte ((byte)0xBB);\r
-                                               ms.WriteByte ((byte)c);\r
-                                       }\r
-                               } else {\r
-                                       ms.WriteByte ((byte)0xEF);\r
-                               }\r
-                               break;\r
-                       case '<':\r
-                               // try to get encoding name from XMLDecl.\r
-                               ms.WriteByte ((byte)'<');\r
-                               int size = stream.Read (buffer, 1, 4);\r
-                               ms.Write (buffer, 1, 4);\r
-                               if (Encoding.ASCII.GetString (buffer, 1, 4) == "?xml") {\r
-                                       int loop = 0;\r
-                                       c = SkipWhitespace (ms);\r
-                                       // version\r
-                                       if (c != 'v' || stream.ReadByte () != 'e')\r
-                                               throw new XmlException ("invalid xml declaration.");\r
-                                       ms.WriteByte ((byte)'v');\r
-                                       ms.WriteByte ((byte)'e');\r
-                                       while (loop++ >= 0) {\r
-                                               c = stream.ReadByte ();\r
-                                               ms.WriteByte ((byte)c);\r
-                                               if (c == '0') {\r
-                                                       ms.WriteByte ((byte)stream.ReadByte ());\r
-                                                       break;\r
-                                               }\r
-                                       }\r
-                                       c = SkipWhitespace (ms);\r
-                                       if (c == 'e') {\r
-                                               ms.WriteByte ((byte)'e');\r
-                                               size = stream.Read (buffer, 0, 7);\r
-                                               ms.Write (buffer, 0, 7);\r
-                                               if (Encoding.ASCII.GetString(buffer, 0, 7) == "ncoding") {\r
-                                                       c = this.SkipWhitespace(ms);\r
-                                                       if (c != '=')\r
-                                                               throw encodingException;\r
-                                                       ms.WriteByte ((byte)'=');\r
-                                                       c = this.SkipWhitespace (ms);\r
-                                                       int quoteChar = c;\r
-                                                       ms.WriteByte ((byte)c);\r
-                                                       int start = (int)ms.Position;\r
-                                                       while (loop++ >= 0) {\r
-                                                               c = stream.ReadByte ();\r
-                                                               if (c == quoteChar)\r
-                                                                       break;\r
-                                                               else if (c < 0)\r
-                                                                       throw encodingException;\r
-                                                               ms.WriteByte ((byte)c);\r
-                                                       }\r
-                                                       string encodingName = Encoding.UTF8.GetString (ms.GetBuffer (), start, (int)ms.Position - start);\r
-                                                       if (!XmlConstructs.IsValidIANAEncoding (encodingName))\r
-                                                               throw encodingException;\r
-                                                       ms.WriteByte ((byte)quoteChar);\r
-                                                       enc = Encoding.GetEncoding (encodingName);\r
-                                               }\r
-                                               else\r
-                                                       ms.Write (buffer, 0, size);\r
-                                       }\r
-                                       else\r
-                                               ms.WriteByte ((byte)c);\r
-                               }\r
-                               buffer = ms.ToArray ();\r
-                               bufLength = buffer.Length;\r
-                               bufPos = 0;\r
-                               break;\r
-                       default:\r
-                               buffer [0] = (byte)c;\r
-                               bufLength = 1;\r
-                               enc = Encoding.UTF8;\r
-                               break;\r
-                       }\r
-               }\r
-\r
-               // skips whitespace and returns misc char that was read from stream\r
-               private int SkipWhitespace (MemoryStream ms)    // ms may be null\r
-               {\r
-                       int loop = 0;\r
-                       int c;\r
-                       while (loop++ >= 0) { // defends infinite loop (expecting overflow)\r
-                               c = stream.ReadByte ();\r
-                               switch (c) {\r
-                               case '\r': goto case ' ';\r
-                               case '\n': goto case ' ';\r
-                               case '\t': goto case ' ';\r
-                               case ' ':\r
-                                       if (ms != null)\r
-                                               ms.WriteByte ((byte)c);\r
-                                       continue;\r
-                               default:\r
-                                       return c;\r
-                               }\r
-                       }\r
-                       throw new InvalidOperationException ();\r
-               }\r
-\r
-               public Encoding ActualEncoding {\r
-                       get { return enc; }\r
-               }\r
-\r
-               #region Public Overrides\r
-               public override bool CanRead {\r
-                       get { return stream.CanRead; }\r
-               }\r
-\r
-               public override bool CanSeek {\r
-                       get { return false; } //stream.CanSeek; }\r
-               }\r
-\r
-               public override bool CanWrite {\r
-                       get { return false; }\r
-               }\r
-\r
-               public override long Length {\r
-                       get {\r
-                               return stream.Length;\r
-                       }\r
-               }\r
-\r
-               public override long Position {\r
-                       get {\r
-                               return stream.Position + bufLength;\r
-                       }\r
-                       set {\r
-                               if(value < bufLength)\r
-                                       bufPos = (int)value;\r
-                               else\r
-                                       stream.Position = value - bufLength;\r
-                       }\r
-               }\r
-\r
-               public override void Flush()\r
-               {\r
-                       stream.Flush ();\r
-               }\r
-\r
-               public override int Read (byte[] buffer, int offset, int count)\r
-               {\r
-                       int ret;\r
-                       if (count <= bufLength - bufPos)        {       // all from buffer\r
-                               Array.Copy (this.buffer, bufPos, buffer, offset, count);\r
-                               bufPos += count;\r
-                               ret = count;\r
-                       } else {\r
-                               int bufRest = bufLength - bufPos;\r
-                               if (bufLength > bufPos) {\r
-                                       Array.Copy (this.buffer, bufPos, buffer, offset, bufRest);\r
-                                       bufPos += bufRest;\r
-                               }\r
-                               ret = bufRest +\r
-                                       stream.Read (buffer, offset + bufRest, count - bufRest);\r
-                       }\r
-                       return ret;\r
-               }\r
-\r
-               public override int ReadByte ()\r
-               {\r
-                       if (bufLength > bufPos) {\r
-                               return buffer [bufPos++];\r
-                       }\r
-                       return stream.ReadByte ();\r
-               }\r
-\r
-               public override long Seek (long offset, System.IO.SeekOrigin origin)\r
-               {\r
-                       int bufRest = bufLength - bufPos;\r
-                       if (origin == SeekOrigin.Current)\r
-                               if (offset < bufRest)\r
-                                       return buffer [bufPos + offset];\r
-                               else\r
-                                       return stream.Seek (offset - bufRest, origin);\r
-                       else\r
-                               return stream.Seek (offset, origin);\r
-               }\r
-\r
-               public override void SetLength (long value)\r
-               {\r
-                       stream.SetLength (value);\r
-               }\r
-\r
-               public override void Write (byte[] buffer, int offset, int count)\r
-               {\r
-                       throw new NotSupportedException ();\r
-               }\r
-               #endregion\r
-       }\r
-}\r
+
+       class XmlInputStream : Stream
+       {
+               Encoding enc;
+               Stream stream;
+               byte[] buffer;
+               int bufLength;
+               int bufPos;
+
+               static XmlException encodingException = new XmlException ("invalid encoding specification.");
+
+               public XmlInputStream (Stream stream)
+               {
+                       Initialize (stream);
+               }
+
+               private void Initialize (Stream stream)
+               {
+                       buffer = new byte [64];
+                       this.stream = stream;
+                       enc = Encoding.UTF8; // Default to UTF8 if we can't guess it
+                       bufLength = stream.Read (buffer, 0, buffer.Length);
+                       if (bufLength == -1 || bufLength == 0) {
+                               return;
+                       }
+
+                       int c = ReadByteSpecial ();
+                       switch (c) {
+                       case 0xFF:
+                               c = ReadByteSpecial ();
+                               if (c == 0xFE) {
+                                       // BOM-ed little endian utf-16
+                                       enc = Encoding.Unicode;
+                               } else {
+                                       // It doesn't start from "<?xml" then its encoding is utf-8
+                                       bufPos = 0;
+                               }
+                               break;
+                       case 0xFE:
+                               c = ReadByteSpecial ();
+                               if (c == 0xFF) {
+                                       // BOM-ed big endian utf-16
+                                       enc = Encoding.BigEndianUnicode;
+                                       return;
+                               } else {
+                                       // It doesn't start from "<?xml" then its encoding is utf-8
+                                       bufPos = 0;
+                               }
+                               break;
+                       case 0xEF:
+                               c = ReadByteSpecial ();
+                               if (c == 0xBB) {
+                                       c = ReadByteSpecial ();
+                                       if (c != 0xBF) {
+                                               bufPos = 0;
+                                       }
+                               } else {
+                                       buffer [--bufPos] = 0xEF;
+                               }
+                               break;
+                       case '<':
+                               // try to get encoding name from XMLDecl.
+                               if (bufLength >= 5 && Encoding.ASCII.GetString (buffer, 1, 4) == "?xml") {
+                                       bufPos += 4;
+                                       c = SkipWhitespace ();
+
+                                       // version. It is optional here.
+                                       if (c == 'v') {
+                                               while (c >= 0) {
+                                                       c = ReadByteSpecial ();
+                                                       if (c == '0') { // 0 of 1.0
+                                                               ReadByteSpecial ();
+                                                               break;
+                                                       }
+                                               }
+                                               c = SkipWhitespace ();
+                                       }
+
+                                       if (c == 'e') {
+                                               int remaining = bufLength - bufPos;
+                                               if (remaining >= 7 && Encoding.ASCII.GetString(buffer, bufPos, 7) == "ncoding") {
+                                                       bufPos += 7;
+                                                       c = SkipWhitespace();
+                                                       if (c != '=')
+                                                               throw encodingException;
+                                                       c = SkipWhitespace ();
+                                                       int quoteChar = c;
+                                                       StringBuilder sb = new StringBuilder ();
+                                                       while (true) {
+                                                               c = ReadByteSpecial ();
+                                                               if (c == quoteChar)
+                                                                       break;
+                                                               else if (c < 0)
+                                                                       throw encodingException;
+
+                                                               sb.Append ((char) c);
+                                                       }
+                                                       string encodingName = sb.ToString ();
+                                                       if (!XmlChar.IsValidIANAEncoding (encodingName))
+                                                               throw encodingException;
+                                                       enc = Encoding.GetEncoding (encodingName);
+                                               }
+                                       }
+                               }
+                               bufPos = 0;
+                               break;
+                       default:
+                               bufPos = 0;
+                               break;
+                       }
+               }
+
+               // Just like readbyte, but grows the buffer too.
+               int ReadByteSpecial ()
+               {
+                       if (bufLength > bufPos)
+                               return buffer [bufPos++];
+
+                       byte [] newbuf = new byte [buffer.Length * 2];
+                       Buffer.BlockCopy (buffer, 0, newbuf, 0, bufLength);
+                       int nbytes = stream.Read (newbuf, bufLength, buffer.Length);
+                       if (nbytes == -1 || nbytes == 0)
+                               return -1;
+                               
+                       bufLength += nbytes;
+                       buffer = newbuf;
+                       return buffer [bufPos++];
+               }
+
+               // skips whitespace and returns misc char that was read from stream
+               private int SkipWhitespace ()
+               {
+                       int c;
+                       while (true) {
+                               c = ReadByteSpecial ();
+                               switch ((char) c) {
+                               case '\r': goto case ' ';
+                               case '\n': goto case ' ';
+                               case '\t': goto case ' ';
+                               case ' ':
+                                       continue;
+                               default:
+                                       return c;
+                               }
+                       }
+                       throw new InvalidOperationException ();
+               }
+
+               public Encoding ActualEncoding {
+                       get { return enc; }
+               }
+
+               #region Public Overrides
+               public override bool CanRead {
+                       get {
+                               if (bufLength > bufPos)
+                                       return true;
+                               else
+                                       return stream.CanRead; 
+                       }
+               }
+
+               // FIXME: It should support base stream's CanSeek.
+               public override bool CanSeek {
+                       get { return false; } // stream.CanSeek; }
+               }
+
+               public override bool CanWrite {
+                       get { return false; }
+               }
+
+               public override long Length {
+                       get {
+                               return stream.Length;
+                       }
+               }
+
+               public override long Position {
+                       get {
+                               return stream.Position - bufLength + bufPos;
+                       }
+                       set {
+                               if(value < bufLength)
+                                       bufPos = (int)value;
+                               else
+                                       stream.Position = value - bufLength;
+                       }
+               }
+
+               public override void Close ()
+               {
+                       stream.Close ();
+               }
+
+               public override void Flush ()
+               {
+                       stream.Flush ();
+               }
+
+               public override int Read (byte[] buffer, int offset, int count)
+               {
+                       int ret;
+                       if (count <= bufLength - bufPos)        {       // all from buffer
+                               Buffer.BlockCopy (this.buffer, bufPos, buffer, offset, count);
+                               bufPos += count;
+                               ret = count;
+                       } else {
+                               int bufRest = bufLength - bufPos;
+                               if (bufLength > bufPos) {
+                                       Buffer.BlockCopy (this.buffer, bufPos, buffer, offset, bufRest);
+                                       bufPos += bufRest;
+                               }
+                               ret = bufRest +
+                                       stream.Read (buffer, offset + bufRest, count - bufRest);
+                       }
+                       return ret;
+               }
+
+               public override int ReadByte ()
+               {
+                       if (bufLength > bufPos) {
+                               return buffer [bufPos++];
+                       }
+                       return stream.ReadByte ();
+               }
+
+               public override long Seek (long offset, System.IO.SeekOrigin origin)
+               {
+                       int bufRest = bufLength - bufPos;
+                       if (origin == SeekOrigin.Current)
+                               if (offset < bufRest)
+                                       return buffer [bufPos + offset];
+                               else
+                                       return stream.Seek (offset - bufRest, origin);
+                       else
+                               return stream.Seek (offset, origin);
+               }
+
+               public override void SetLength (long value)
+               {
+                       stream.SetLength (value);
+               }
+
+               public override void Write (byte[] buffer, int offset, int count)
+               {
+                       throw new NotSupportedException ();
+               }
+               #endregion
+       }
+}