Implement several ToString() methods.
[mono.git] / mcs / class / System.ServiceModel.Web / System.Runtime.Serialization.Json / JsonReader.cs
1 //
2 // JsonWriter.cs
3 //
4 // Author:
5 //      Atsushi Enomoto  <atsushi@ximian.com>
6 //
7 // Copyright (C) 2007 Novell, Inc (http://www.novell.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28 using System;
29 using System.Collections.Generic;
30 using System.Globalization;
31 using System.IO;
32 using System.Text;
33 using System.Xml;
34
35 namespace System.Runtime.Serialization.Json
36 {
37         class PushbackReader : StreamReader
38         {
39                 Stack<int> pushback;
40
41                 public PushbackReader (Stream stream, Encoding encoding) : base (stream, encoding)
42                 {
43                         pushback = new Stack<int>();
44                 }
45
46                 public PushbackReader (Stream stream) : base (stream, true)
47                 {
48                         pushback = new Stack<int>();
49                 }
50
51                 public override void Close ()
52                 {
53                         pushback.Clear ();
54                 }
55
56                 public override int Peek ()
57                 {
58                         if (pushback.Count > 0) {
59                                 return pushback.Peek ();
60                         }
61                         else {
62                                 return base.Peek ();
63                         }
64                 }
65
66                 public override int Read ()
67                 {
68                         if (pushback.Count > 0) {
69                                 return pushback.Pop ();
70                         }
71                         else {
72                                 return base.Read ();
73                         }
74                 }
75
76                 public void Pushback (int ch)
77                 {
78                         pushback.Push (ch);
79                 }
80         }
81
82         // FIXME: quotas check
83         class JsonReader : XmlDictionaryReader, IXmlJsonReaderInitializer, IXmlLineInfo
84         {
85                 class ElementInfo
86                 {
87                         public readonly string Name;
88                         public readonly string Type;
89                         public bool HasContent;
90
91                         public ElementInfo (string name, string type)
92                         {
93                                 this.Name = name;
94                                 this.Type = type;
95                         }
96                 }
97
98                 enum AttributeState
99                 {
100                         None,
101                         Type,
102                         TypeValue,
103                         RuntimeType,
104                         RuntimeTypeValue
105                 }
106
107                 PushbackReader reader;
108                 XmlDictionaryReaderQuotas quotas;
109                 OnXmlDictionaryReaderClose on_close;
110                 XmlNameTable name_table = new NameTable ();
111
112                 XmlNodeType current_node;
113                 AttributeState attr_state;
114                 string simple_value;
115                 string next_element;
116                 string current_runtime_type, next_object_content_name;
117                 ReadState read_state = ReadState.Initial;
118                 bool content_stored;
119                 bool finished;
120                 Stack<ElementInfo> elements = new Stack<ElementInfo> ();
121
122                 int line = 1, column = 0;
123
124                 // Constructors
125
126                 public JsonReader (byte [] buffer, int offset, int count, Encoding encoding, XmlDictionaryReaderQuotas quotas, OnXmlDictionaryReaderClose onClose)
127                 {
128                         SetInput (buffer, offset, count, encoding, quotas, onClose);
129                 }
130
131                 public JsonReader (Stream stream, Encoding encoding, XmlDictionaryReaderQuotas quotas, OnXmlDictionaryReaderClose onClose)
132                 {
133                         SetInput (stream, encoding, quotas, onClose);
134                 }
135
136                 internal bool LameSilverlightLiteralParser { get; set; }
137
138                 // IXmlLineInfo
139
140                 public bool HasLineInfo ()
141                 {
142                         return true;
143                 }
144
145                 public int LineNumber {
146                         get { return line; }
147                 }
148
149                 public int LinePosition {
150                         get { return column; }
151                 }
152
153                 // IXmlJsonReaderInitializer
154
155                 public void SetInput (byte [] buffer, int offset, int count, Encoding encoding, XmlDictionaryReaderQuotas quotas, OnXmlDictionaryReaderClose onClose)
156                 {
157                         SetInput (new MemoryStream (buffer, offset, count), encoding, quotas, onClose);
158                 }
159
160                 public void SetInput (Stream stream, Encoding encoding, XmlDictionaryReaderQuotas quotas, OnXmlDictionaryReaderClose onClose)
161                 {
162                         if (encoding != null)
163                                 reader = new PushbackReader (stream, encoding);
164                         else
165                                 reader = new PushbackReader (stream);
166                         if (quotas == null)
167                                 throw new ArgumentNullException ("quotas");
168                         this.quotas = quotas;
169                         this.on_close = onClose;
170                 }
171
172                 // XmlDictionaryReader
173
174                 public override int AttributeCount {
175                         get { return current_node != XmlNodeType.Element ? 0 : current_runtime_type != null ? 2 : 1; }
176                 }
177
178                 public override string BaseURI {
179                         get { return String.Empty; }
180                 }
181
182                 public override int Depth {
183                         get {
184                                 int mod = 0;
185                                 switch (attr_state) {
186                                 case AttributeState.Type:
187                                 case AttributeState.RuntimeType:
188                                         mod++;
189                                         break;
190                                 case AttributeState.TypeValue:
191                                 case AttributeState.RuntimeTypeValue:
192                                         mod += 2;
193                                         break;
194                                 case AttributeState.None:
195                                         if (NodeType == XmlNodeType.Text)
196                                                 mod++;
197                                         break;
198                                 }
199                                 return read_state != ReadState.Interactive ? 0 : elements.Count - 1 + mod;
200                         }
201                 }
202
203                 public override bool EOF {
204                         get {
205                                 switch (read_state) {
206                                 case ReadState.Closed:
207                                 case ReadState.EndOfFile:
208                                         return true;
209                                 default:
210                                         return false;
211                                 }
212                         }
213                 }
214
215                 public override bool HasValue {
216                         get {
217                                 switch (NodeType) {
218                                 case XmlNodeType.Attribute:
219                                 case XmlNodeType.Text:
220                                         return true;
221                                 default:
222                                         return false;
223                                 }
224                         }
225                 }
226
227                 public override bool IsEmptyElement {
228                         get { return false; }
229                 }
230
231                 public override string LocalName {
232                         get {
233                                 switch (attr_state) {
234                                 case AttributeState.Type:
235                                         return "type";
236                                 case AttributeState.RuntimeType:
237                                         return "__type";
238                                 }
239                                 switch (NodeType) {
240                                 case XmlNodeType.Element:
241                                 case XmlNodeType.EndElement:
242                                         return elements.Peek ().Name;
243                                 default:
244                                         return String.Empty;
245                                 }
246                         }
247                 }
248
249                 public override string NamespaceURI {
250                         get { return String.Empty; }
251                 }
252
253                 public override XmlNameTable NameTable {
254                         get { return name_table; }
255                 }
256
257                 public override XmlNodeType NodeType {
258                         get {
259                                 switch (attr_state) {
260                                 case AttributeState.Type:
261                                 case AttributeState.RuntimeType:
262                                         return XmlNodeType.Attribute;
263                                 case AttributeState.TypeValue:
264                                 case AttributeState.RuntimeTypeValue:
265                                         return XmlNodeType.Text;
266                                 default:
267                                         return current_node;
268                                 }
269                         }
270                 }
271
272                 public override string Prefix {
273                         get { return String.Empty; }
274                 }
275
276                 public override ReadState ReadState {
277                         get { return read_state; }
278                 }
279
280                 public override string Value {
281                         get {
282                                 switch (attr_state) {
283                                 case AttributeState.Type:
284                                 case AttributeState.TypeValue:
285                                         return elements.Peek ().Type;
286                                 case AttributeState.RuntimeType:
287                                 case AttributeState.RuntimeTypeValue:
288                                         return current_runtime_type;
289                                 default:
290                                         return current_node == XmlNodeType.Text ? simple_value : String.Empty;
291                                 }
292                         }
293                 }
294
295                 public override void Close ()
296                 {
297                         if (on_close != null) {
298                                 on_close (this);
299                                 on_close = null;
300                         }
301                         read_state = ReadState.Closed;
302                 }
303
304                 public override string GetAttribute (int index)
305                 {
306                         if (index == 0 && current_node == XmlNodeType.Element)
307                                 return elements.Peek ().Type;
308                         else if (index == 1 && current_runtime_type != null)
309                                 return current_runtime_type;
310                         throw new ArgumentOutOfRangeException ("index", "Index is must be either 0 or 1 when there is an explicit __type in the object, and only valid on an element on this XmlDictionaryReader");
311                 }
312
313                 public override string GetAttribute (string name)
314                 {
315                         if (current_node != XmlNodeType.Element)
316                                 return null;
317                         switch (name) {
318                         case "type":
319                                 return elements.Peek ().Type;
320                         case "__type":
321                                 return current_runtime_type;
322                         default:
323                                 return null;
324                         }
325                 }
326
327                 public override string GetAttribute (string localName, string ns)
328                 {
329                         if (ns == String.Empty)
330                                 return GetAttribute (localName);
331                         else
332                                 return null;
333                 }
334
335                 public override string LookupNamespace (string prefix)
336                 {
337                         if (prefix == null)
338                                 throw new ArgumentNullException ("prefix");
339                         else if (prefix.Length == 0)
340                                 return String.Empty;
341                         return null;
342                 }
343
344                 public override bool MoveToAttribute (string name)
345                 {
346                         if (current_node != XmlNodeType.Element)
347                                 return false;
348                         switch (name) {
349                         case "type":
350                                 attr_state = AttributeState.Type;
351                                 return true;
352                         case "__type":
353                                 if (current_runtime_type == null)
354                                         return false;
355                                 attr_state = AttributeState.RuntimeType;
356                                 return true;
357                         default:
358                                 return false;
359                         }
360                 }
361
362                 public override bool MoveToAttribute (string localName, string ns)
363                 {
364                         if (ns != String.Empty)
365                                 return false;
366                         return MoveToAttribute (localName);
367                 }
368
369                 public override bool MoveToElement ()
370                 {
371                         if (attr_state == AttributeState.None)
372                                 return false;
373                         attr_state = AttributeState.None;
374                         return true;
375                 }
376
377                 public override bool MoveToFirstAttribute ()
378                 {
379                         if (current_node != XmlNodeType.Element)
380                                 return false;
381                         attr_state = AttributeState.Type;
382                         return true;
383                 }
384
385                 public override bool MoveToNextAttribute ()
386                 {
387                         if (attr_state == AttributeState.None)
388                                 return MoveToFirstAttribute ();
389                         else
390                                 return MoveToAttribute ("__type");
391                 }
392
393                 public override bool ReadAttributeValue ()
394                 {
395                         switch (attr_state) {
396                         case AttributeState.Type:
397                                 attr_state = AttributeState.TypeValue;
398                                 return true;
399                         case AttributeState.RuntimeType:
400                                 attr_state = AttributeState.RuntimeTypeValue;
401                                 return true;
402                         }
403                         return false;
404                 }
405
406                 public override void ResolveEntity ()
407                 {
408                         throw new NotSupportedException ();
409                 }
410
411                 public override bool Read ()
412                 {
413                         switch (read_state) {
414                         case ReadState.EndOfFile:
415                         case ReadState.Closed:
416                         case ReadState.Error:
417                                 return false;
418                         case ReadState.Initial:
419                                 read_state = ReadState.Interactive;
420                                 next_element = "root";
421                                 current_node = XmlNodeType.Element;
422                                 break;
423                         }
424
425                         MoveToElement ();
426
427                         if (content_stored) {
428                                 if (current_node == XmlNodeType.Element) {
429                                         if (elements.Peek ().Type == "null") {
430                                                 // since null is not consumed as text content, it skips Text state.
431                                                 current_node = XmlNodeType.EndElement;
432                                                 content_stored = false;
433                                         }
434                                         else
435                                                 current_node = XmlNodeType.Text;
436                                         return true;
437                                 } else if (current_node == XmlNodeType.Text) {
438                                         current_node = XmlNodeType.EndElement;
439                                         content_stored = false;
440                                         return true;
441                                 }
442                         }
443                         else if (current_node == XmlNodeType.EndElement) {
444                                 // clear EndElement state
445                                 elements.Pop ();
446                                 if (elements.Count > 0)
447                                         elements.Peek ().HasContent = true;
448                                 else
449                                         finished = true;
450                         }
451
452                         SkipWhitespaces ();
453
454                         attr_state = AttributeState.None;
455                         // Default. May be overriden only as EndElement or None.
456                         current_node = XmlNodeType.Element;
457
458                         if (!ReadContent (false))
459                                 return false;
460                         if (finished)
461                                 throw XmlError ("Multiple top-level content is not allowed");
462                         return true;
463                 }
464
465                 bool TryReadString (string str)
466                 {
467                         for (int i = 0; i < str.Length; i ++) {
468                                 int ch = ReadChar ();
469                                 if (ch != str[i]) {
470                                         for (int j = i; j >= 0; j--)
471                                                 PushbackChar (j);
472                                         return false;
473                                 }
474                         }
475
476                         return true;
477                 }
478
479                 bool ReadContent (bool objectValue)
480                 {
481                         int ch = ReadChar ();
482                         if (ch < 0) {
483                                 ReadEndOfStream ();
484                                 return false;
485                         }
486
487                         bool itemMustFollow = false;
488
489                         if (!objectValue && elements.Count > 0 && elements.Peek ().HasContent) {
490                                 if (ch == ',') {
491                                         switch (elements.Peek ().Type) {
492                                         case "object":
493                                         case "array":
494                                                 SkipWhitespaces ();
495                                                 ch = ReadChar ();
496                                                 itemMustFollow = true;
497                                                 break;
498                                         }
499                                 }
500                                 else if (ch != '}' && ch != ']')
501                                         throw XmlError ("Comma is required unless an array or object is at the end");
502                         }
503
504                         if (elements.Count > 0 && elements.Peek ().Type == "array")
505                                 next_element = "item";
506                         else if (next_object_content_name != null) {
507                                 next_element = next_object_content_name;
508                                 next_object_content_name = null;
509                                 if (ch != ':')
510                                         throw XmlError ("':' is expected after a name of an object content");
511                                 SkipWhitespaces ();
512                                 ReadContent (true);
513                                 return true;
514                         }
515
516                         switch (ch) {
517                         case '{':
518                                 ReadStartObject ();
519                                 return true;
520                         case '[':
521                                 ReadStartArray ();
522                                 return true;
523                         case '}':
524                                 if (itemMustFollow)
525                                         throw XmlError ("Invalid comma before an end of object");
526                                 if (objectValue)
527                                         throw XmlError ("Invalid end of object as an object content");
528                                 ReadEndObject ();
529                                 return true;
530                         case ']':
531                                 if (itemMustFollow)
532                                         throw XmlError ("Invalid comma before an end of array");
533                                 if (objectValue)
534                                         throw XmlError ("Invalid end of array as an object content");
535                                 ReadEndArray ();
536                                 return true;
537                         case '"':
538                                 bool lame = LameSilverlightLiteralParser && ch != '"';
539                                 string s = ReadStringLiteral (lame);
540                                 if (!objectValue && elements.Count > 0 && elements.Peek ().Type == "object") {
541                                         next_element = s;
542                                         SkipWhitespaces ();
543                                         if (!lame)
544                                                 Expect (':');
545                                         SkipWhitespaces ();
546                                         ReadContent (true);
547                                 }
548                                 else
549                                         ReadAsSimpleContent ("string", s);
550                                 return true;
551                         case '-':
552                                 ReadNumber (ch);
553                                 return true;
554                         case 'n':
555                                 if (TryReadString("ull")) {
556                                         ReadAsSimpleContent ("null", "null");
557                                         return true;
558                                 }
559                                 else {
560                                         // the pushback for 'n' is taken care of by the
561                                         // default case if we're in lame silverlight literal
562                                         // mode
563                                         goto default;
564                                 }
565                         case 't':
566                                 if (TryReadString ("rue")) {
567                                         ReadAsSimpleContent ("boolean", "true");
568                                         return true;
569                                 }
570                                 else {
571                                         // the pushback for 't' is taken care of by the
572                                         // default case if we're in lame silverlight literal
573                                         // mode
574                                         goto default;
575                                 }
576                         case 'f':
577                                 if (TryReadString ("alse")) {
578                                         ReadAsSimpleContent ("boolean", "false");
579                                         return true;
580                                 }
581                                 else {
582                                         // the pushback for 'f' is taken care of by the
583                                         // default case if we're in lame silverlight literal
584                                         // mode
585                                         goto default;
586                                 }
587                         default:
588                                 if ('0' <= ch && ch <= '9') {
589                                         ReadNumber (ch);
590                                         return true;
591                                 }
592                                 if (LameSilverlightLiteralParser) {
593                                         PushbackChar (ch);
594                                         goto case '"';
595                                 }
596                                 throw XmlError (String.Format ("Unexpected token: '{0}' ({1:X04})", (char) ch, (int) ch));
597                         }
598                 }
599
600                 void ReadStartObject ()
601                 {
602                         ElementInfo ei = new ElementInfo (next_element, "object");
603                         elements.Push (ei);
604
605                         SkipWhitespaces ();
606                         if (PeekChar () == '"') { // it isn't premise: the object might be empty
607                                 ReadChar ();
608                                 string s = ReadStringLiteral ();
609                                 if (s == "__type") {
610                                         SkipWhitespaces ();
611                                         Expect (':');
612                                         SkipWhitespaces ();
613                                         Expect ('"');
614                                         current_runtime_type = ReadStringLiteral ();
615                                         SkipWhitespaces ();
616                                         ei.HasContent = true;
617                                 }
618                                 else
619                                         next_object_content_name = s;
620                         }
621                 }
622
623                 void ReadStartArray ()
624                 {
625                         elements.Push (new ElementInfo (next_element, "array"));
626                 }
627
628                 void ReadEndObject ()
629                 {
630                         if (elements.Count == 0 || elements.Peek ().Type != "object")
631                                 throw XmlError ("Unexpected end of object");
632                         current_node = XmlNodeType.EndElement;
633                 }
634
635                 void ReadEndArray ()
636                 {
637                         if (elements.Count == 0 || elements.Peek ().Type != "array")
638                                 throw XmlError ("Unexpected end of array");
639                         current_node = XmlNodeType.EndElement;
640                 }
641
642                 void ReadEndOfStream ()
643                 {
644                         if (elements.Count > 0)
645                                 throw XmlError (String.Format ("{0} missing end of arrays or objects", elements.Count));
646                         read_state = ReadState.EndOfFile;
647                         current_node = XmlNodeType.None;
648                 }
649
650                 void ReadAsSimpleContent (string type, string value)
651                 {
652                         elements.Push (new ElementInfo (next_element, type));
653                         simple_value = value;
654                         content_stored = true;
655                 }
656
657                 void ReadNumber (int ch)
658                 {
659                         elements.Push (new ElementInfo (next_element, "number"));
660                         content_stored = true;
661
662                         int init = ch;
663                         int prev;
664                         bool floating = false, exp = false;
665
666                         StringBuilder sb = new StringBuilder ();
667                         bool cont = true;
668                         do {
669                                 sb.Append ((char) ch);
670                                 prev = ch;
671                                 ch = ReadChar ();
672
673                                 if (prev == '-' && !IsNumber (ch)) // neither '.', '-' or '+' nor anything else is valid
674                                         throw XmlError ("Invalid JSON number");
675
676                                 switch (ch) {
677                                 case 'e':
678                                 case 'E':
679                                         if (exp)
680                                                 throw XmlError ("Invalid JSON number token. Either 'E' or 'e' must not occur more than once");
681                                         if (!IsNumber (prev))
682                                                 throw XmlError ("Invalid JSON number token. only a number is valid before 'E' or 'e'");
683                                         exp = true;
684                                         break;
685                                 case '.':
686                                         if (floating)
687                                                 throw XmlError ("Invalid JSON number token. '.' must not occur twice");
688                                         if (exp)
689                                                 throw XmlError ("Invalid JSON number token. '.' must not occur after 'E' or 'e'");
690                                         floating = true;
691                                         break;
692                                 case '+':
693                                 case '-':
694                                         if (prev == 'E' || prev == 'e')
695                                                 break;
696                                         goto default;
697                                 default:
698                                         if (!IsNumber (ch)) {
699                                                 PushbackChar (ch);
700                                                 cont = false;
701                                         }
702                                         break;
703                                 }
704                         } while (cont);
705
706                         if (!IsNumber (prev)) // only number is valid at the end
707                                 throw XmlError ("Invalid JSON number");
708
709                         simple_value = sb.ToString ();
710
711                         if (init == '0' && !floating && !exp && simple_value != "0")
712                                 throw XmlError ("Invalid JSON number");
713                 }
714
715                 bool IsNumber (int c)
716                 {
717                         return '0' <= c && c <= '9';
718                 }
719
720                 StringBuilder vb = new StringBuilder ();
721
722                 string ReadStringLiteral ()
723                 {
724                         return ReadStringLiteral (false);
725                 }
726
727                 string ReadStringLiteral (bool endWithColon)
728                 {
729                         vb.Length = 0;
730                         while (true) {
731                                 int c = ReadChar ();
732                                 if (c < 0)
733                                         throw XmlError ("JSON string is not closed");
734                                 if (c == '"' && !endWithColon)
735                                         return vb.ToString ();
736                                 else if (c == ':' && endWithColon)
737                                         return vb.ToString ();
738                                 else if (c != '\\') {
739                                         vb.Append ((char) c);
740                                         continue;
741                                 }
742
743                                 // escaped expression
744                                 c = ReadChar ();
745                                 if (c < 0)
746                                         throw XmlError ("Invalid JSON string literal; incomplete escape sequence");
747                                 switch (c) {
748                                 case '"':
749                                 case '\\':
750                                 case '/':
751                                         vb.Append ((char) c);
752                                         break;
753                                 case 'b':
754                                         vb.Append ('\x8');
755                                         break;
756                                 case 'f':
757                                         vb.Append ('\f');
758                                         break;
759                                 case 'n':
760                                         vb.Append ('\n');
761                                         break;
762                                 case 'r':
763                                         vb.Append ('\r');
764                                         break;
765                                 case 't':
766                                         vb.Append ('\t');
767                                         break;
768                                 case 'u':
769                                         ushort cp = 0;
770                                         for (int i = 0; i < 4; i++) {
771                                                 if ((c = ReadChar ()) < 0)
772                                                         throw XmlError ("Incomplete unicode character escape literal");
773                                                 cp *= 16;
774                                                 if ('0' <= c && c <= '9')
775                                                         cp += (ushort) (c - '0');
776                                                 if ('A' <= c && c <= 'F')
777                                                         cp += (ushort) (c - 'A' + 10);
778                                                 if ('a' <= c && c <= 'f')
779                                                         cp += (ushort) (c - 'a' + 10);
780                                         }
781                                         vb.Append ((char) cp);
782                                         break;
783                                 default:
784                                         throw XmlError ("Invalid JSON string literal; unexpected escape character");
785                                 }
786                         }
787                 }
788
789                 int PeekChar ()
790                 {
791                         return reader.Peek ();
792                 }
793
794                 int ReadChar ()
795                 {
796                         int v = reader.Read ();
797                         if (v == '\n') {
798                                 line++;
799                                 column = 0;
800                         }
801                         else
802                                 column++;
803                         return v;
804                 }
805
806                 void PushbackChar (int ch)
807                 {
808                         // FIXME handle lines (and columns?  ugh, how?)
809                         reader.Pushback (ch);
810                 }
811
812                 void SkipWhitespaces ()
813                 {
814                         do {
815                                 switch (PeekChar ()) {
816                                 case ' ':
817                                 case '\t':
818                                 case '\r':
819                                 case '\n':
820                                         ReadChar ();
821                                         continue;
822                                 default:
823                                         return;
824                                 }
825                         } while (true);
826                 }
827
828                 void Expect (char c)
829                 {
830                         int v = ReadChar ();
831                         if (v < 0)
832                                 throw XmlError (String.Format ("Expected '{0}' but got EOF", c));
833                         if (v != c)
834                                 throw XmlError (String.Format ("Expected '{0}' but got '{1}'", c, (char) v));
835                 }
836
837                 Exception XmlError (string s)
838                 {
839                         return new XmlException (String.Format ("{0} ({1},{2})", s, line, column));
840                 }
841         }
842 }