This commit was manufactured by cvs2svn to create branch 'mono-1-0'.
[mono.git] / mcs / class / System.XML / System.Xml / XmlInputStream.cs
1 //
2 // System.Xml.XmlInputStream 
3 //      encoding-specification-wise XML input stream and reader
4 //
5 // Author:
6 //      Atsushi Enomoto (ginga@kit.hi-ho.ne.jp)
7 //
8 //      (C)2003 Atsushi Enomoto
9 //
10
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31 using System;
32 using System.IO;
33 using System.Text;
34 using System.Xml;
35
36 namespace System.Xml
37 {
38         #region XmlStreamReader
39         internal class XmlStreamReader : StreamReader
40         {
41                 XmlInputStream input;
42
43                 XmlStreamReader (XmlInputStream input)
44                         : base (input, input.ActualEncoding != null ? input.ActualEncoding : Encoding.UTF8)
45                 {
46                         this.input = input;
47                 }
48
49                 public XmlStreamReader (Stream input)
50                         : this (new XmlInputStream (input))
51                 {
52                 }
53
54                 public override void Close ()
55                 {
56                         this.input.Close ();
57                 }
58
59                 protected override void Dispose (bool disposing)
60                 {
61                         base.Dispose (disposing);
62                         if (disposing) {
63                                 Close ();
64                         }
65                 }
66
67         }
68         #endregion
69
70         class XmlInputStream : Stream
71         {
72                 Encoding enc;
73                 Stream stream;
74                 byte[] buffer;
75                 int bufLength;
76                 int bufPos;
77
78                 static XmlException encodingException = new XmlException ("invalid encoding specification.");
79
80                 public XmlInputStream (Stream stream)
81                 {
82                         Initialize (stream);
83                 }
84
85                 private void Initialize (Stream stream)
86                 {
87                         buffer = new byte [1024];
88                         this.stream = stream;
89                         enc = Encoding.UTF8; // Default to UTF8 if we can't guess it
90                         bufLength = stream.Read (buffer, 0, buffer.Length);
91                         if (bufLength == -1 || bufLength == 0) {
92                                 return;
93                         }
94
95                         int c = ReadByteSpecial ();
96                         switch (c) {
97                         case 0xFF:
98                                 c = ReadByteSpecial ();
99                                 if (c == 0xFE) {
100                                         // BOM-ed little endian utf-16
101                                         enc = Encoding.Unicode;
102                                 } else {
103                                         // It doesn't start from "<?xml" then its encoding is utf-8
104                                         bufPos = 0;
105                                 }
106                                 break;
107                         case 0xFE:
108                                 c = ReadByteSpecial ();
109                                 if (c == 0xFF) {
110                                         // BOM-ed big endian utf-16
111                                         enc = Encoding.BigEndianUnicode;
112                                         return;
113                                 } else {
114                                         // It doesn't start from "<?xml" then its encoding is utf-8
115                                         bufPos = 0;
116                                 }
117                                 break;
118                         case 0xEF:
119                                 c = ReadByteSpecial ();
120                                 if (c == 0xBB) {
121                                         c = ReadByteSpecial ();
122                                         if (c != 0xBF) {
123                                                 bufPos = 0;
124                                         }
125                                 } else {
126                                         buffer [--bufPos] = 0xEF;
127                                 }
128                                 break;
129                         case '<':
130                                 // try to get encoding name from XMLDecl.
131                                 if (bufLength >= 5 && Encoding.ASCII.GetString (buffer, 1, 4) == "?xml") {
132                                         bufPos += 4;
133                                         c = SkipWhitespace ();
134
135                                         // version. It is optional here.
136                                         if (c == 'v') {
137                                                 while (c >= 0) {
138                                                         c = ReadByteSpecial ();
139                                                         if (c == '0') { // 0 of 1.0
140                                                                 ReadByteSpecial ();
141                                                                 break;
142                                                         }
143                                                 }
144                                                 c = SkipWhitespace ();
145                                         }
146
147                                         if (c == 'e') {
148                                                 int remaining = bufLength - bufPos;
149                                                 if (remaining >= 7 && Encoding.ASCII.GetString(buffer, bufPos, 7) == "ncoding") {
150                                                         bufPos += 7;
151                                                         c = SkipWhitespace();
152                                                         if (c != '=')
153                                                                 throw encodingException;
154                                                         c = SkipWhitespace ();
155                                                         int quoteChar = c;
156                                                         StringBuilder sb = new StringBuilder ();
157                                                         while (true) {
158                                                                 c = ReadByteSpecial ();
159                                                                 if (c == quoteChar)
160                                                                         break;
161                                                                 else if (c < 0)
162                                                                         throw encodingException;
163
164                                                                 sb.Append ((char) c);
165                                                         }
166                                                         string encodingName = sb.ToString ();
167                                                         if (!XmlChar.IsValidIANAEncoding (encodingName))
168                                                                 throw encodingException;
169                                                         enc = Encoding.GetEncoding (encodingName);
170                                                 }
171                                         }
172                                 }
173                                 bufPos = 0;
174                                 break;
175                         default:
176                                 bufPos = 0;
177                                 break;
178                         }
179                 }
180
181                 // Just like readbyte, but grows the buffer too.
182                 int ReadByteSpecial ()
183                 {
184                         if (bufLength > bufPos)
185                                 return buffer [bufPos++];
186
187                         byte [] newbuf = new byte [buffer.Length * 2];
188                         Buffer.BlockCopy (buffer, 0, newbuf, 0, bufLength);
189                         int nbytes = stream.Read (newbuf, bufLength, buffer.Length);
190                         if (nbytes == -1 || nbytes == 0)
191                                 return -1;
192                                 
193                         bufLength += nbytes;
194                         buffer = newbuf;
195                         return buffer [bufPos++];
196                 }
197
198                 // skips whitespace and returns misc char that was read from stream
199                 private int SkipWhitespace ()
200                 {
201                         int c;
202                         while (true) {
203                                 c = ReadByteSpecial ();
204                                 switch (c) {
205                                 case '\r': goto case ' ';
206                                 case '\n': goto case ' ';
207                                 case '\t': goto case ' ';
208                                 case ' ':
209                                         continue;
210                                 default:
211                                         return c;
212                                 }
213                         }
214                         throw new InvalidOperationException ();
215                 }
216
217                 public Encoding ActualEncoding {
218                         get { return enc; }
219                 }
220
221                 #region Public Overrides
222                 public override bool CanRead {
223                         get {
224                                 if (bufLength > bufPos)
225                                         return true;
226                                 else
227                                         return stream.CanRead; 
228                         }
229                 }
230
231                 // FIXME: It should support base stream's CanSeek.
232                 public override bool CanSeek {
233                         get { return false; } // stream.CanSeek; }
234                 }
235
236                 public override bool CanWrite {
237                         get { return false; }
238                 }
239
240                 public override long Length {
241                         get {
242                                 return stream.Length;
243                         }
244                 }
245
246                 public override long Position {
247                         get {
248                                 return stream.Position - bufLength + bufPos;
249                         }
250                         set {
251                                 if(value < bufLength)
252                                         bufPos = (int)value;
253                                 else
254                                         stream.Position = value - bufLength;
255                         }
256                 }
257
258                 public override void Close ()
259                 {
260                         stream.Close ();
261                 }
262
263                 public override void Flush ()
264                 {
265                         stream.Flush ();
266                 }
267
268                 public override int Read (byte[] buffer, int offset, int count)
269                 {
270                         int ret;
271                         if (count <= bufLength - bufPos)        {       // all from buffer
272                                 Array.Copy (this.buffer, bufPos, buffer, offset, count);
273                                 bufPos += count;
274                                 ret = count;
275                         } else {
276                                 int bufRest = bufLength - bufPos;
277                                 if (bufLength > bufPos) {
278                                         Array.Copy (this.buffer, bufPos, buffer, offset, bufRest);
279                                         bufPos += bufRest;
280                                 }
281                                 ret = bufRest +
282                                         stream.Read (buffer, offset + bufRest, count - bufRest);
283                         }
284                         return ret;
285                 }
286
287                 public override int ReadByte ()
288                 {
289                         if (bufLength > bufPos) {
290                                 return buffer [bufPos++];
291                         }
292                         return stream.ReadByte ();
293                 }
294
295                 public override long Seek (long offset, System.IO.SeekOrigin origin)
296                 {
297                         int bufRest = bufLength - bufPos;
298                         if (origin == SeekOrigin.Current)
299                                 if (offset < bufRest)
300                                         return buffer [bufPos + offset];
301                                 else
302                                         return stream.Seek (offset - bufRest, origin);
303                         else
304                                 return stream.Seek (offset, origin);
305                 }
306
307                 public override void SetLength (long value)
308                 {
309                         stream.SetLength (value);
310                 }
311
312                 public override void Write (byte[] buffer, int offset, int count)
313                 {
314                         throw new NotSupportedException ();
315                 }
316                 #endregion
317         }
318 }