2010-01-20 Zoltan Varga <vargaz@gmail.com>
[mono.git] / mcs / class / System.Runtime.Serialization / System.Xml / XmlBinaryDictionaryWriter.cs
1 //
2 // XmlBinaryDictionaryWriter.cs
3 //
4 // Author:
5 //      Atsushi Enomoto  <atsushi@ximian.com>
6 //
7 // Copyright (C) 2005, 2007 Novell, Inc.  http://www.novell.com
8 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30 using System;
31 using System.Collections;
32 using System.Collections.Specialized;
33 using System.Collections.Generic;
34 using System.Globalization;
35 using System.IO;
36 using System.Linq;
37 using System.Text;
38
39 using BF = System.Xml.XmlBinaryFormat;
40
41 namespace System.Xml
42 {
43         internal partial class XmlBinaryDictionaryWriter : XmlDictionaryWriter
44         {
45                 class MyBinaryWriter : BinaryWriter
46                 {
47                         public MyBinaryWriter (Stream s)
48                                 : base (s)
49                         {
50                         }
51
52                         public void WriteFlexibleInt (int value)
53                         {
54                                 Write7BitEncodedInt (value);
55                         }
56                 }
57
58                 #region Fields
59                 MyBinaryWriter original, writer, buffer_writer;
60                 IXmlDictionary dict_ext;
61                 XmlDictionary dict_int = new XmlDictionary ();
62                 XmlBinaryWriterSession session;
63                 bool owns_stream;
64                 Encoding utf8Enc = new UTF8Encoding ();
65                 MemoryStream buffer = new MemoryStream ();
66
67                 const string XmlNamespace = "http://www.w3.org/XML/1998/namespace";
68                 const string XmlnsNamespace = "http://www.w3.org/2000/xmlns/";
69
70                 WriteState state = WriteState.Start;
71                 bool open_start_element = false;
72                 // transient current node info
73                 List<KeyValuePair<string,object>> namespaces = new List<KeyValuePair<string,object>> ();
74                 string xml_lang = null;
75                 XmlSpace xml_space = XmlSpace.None;
76                 int ns_index = 0;
77                 // stacked info
78                 Stack<int> ns_index_stack = new Stack<int> ();
79                 Stack<string> xml_lang_stack = new Stack<string> ();
80                 Stack<XmlSpace> xml_space_stack = new Stack<XmlSpace> ();
81                 Stack<string> element_ns_stack = new Stack<string> ();
82                 string element_ns = String.Empty;
83                 int element_count;
84                 string element_prefix; // only meaningful at Element state
85                 // current attribute info
86                 string attr_value;
87                 string current_attr_prefix;
88                 object current_attr_name, current_attr_ns;
89                 bool attr_typed_value;
90                 SaveTarget save_target;
91
92                 enum SaveTarget {
93                         None,
94                         Namespaces,
95                         XmlLang,
96                         XmlSpace
97                 }
98
99                 // XmlWriterSettings support
100
101                 #endregion
102
103                 #region Constructors
104
105                 public XmlBinaryDictionaryWriter (Stream stream,
106                         IXmlDictionary dictionary,
107                         XmlBinaryWriterSession session, bool ownsStream)
108                 {
109                         if (dictionary == null)
110                                 dictionary = new XmlDictionary ();
111                         if (session == null)
112                                 session = new XmlBinaryWriterSession ();
113
114                         original = new MyBinaryWriter (stream);
115                         this.writer = original;
116                         buffer_writer = new MyBinaryWriter (buffer);
117                         this.dict_ext = dictionary;
118                         this.session = session;
119                         owns_stream = ownsStream;
120
121                         AddNamespace ("xml", "http://www.w3.org/XML/1998/namespace");
122                         AddNamespace ("xml", "http://www.w3.org/2000/xmlns/");
123                         ns_index = 2;
124                 }
125
126                 #endregion
127
128                 #region Properties
129
130                 public override WriteState WriteState {
131                         get { return state; }
132                 }
133                 
134                 public override string XmlLang {
135                         get { return xml_lang; }
136                 }
137
138                 public override XmlSpace XmlSpace {
139                         get { return xml_space; }
140                 }
141
142                 #endregion
143
144                 #region Methods
145
146                 private void AddMissingElementXmlns ()
147                 {
148                         // push new namespaces to manager.
149                         for (int i = ns_index; i < namespaces.Count; i++) {
150                                 var ent = namespaces [i];
151                                 string prefix = (string) ent.Key;
152                                 string ns = ent.Value as string;
153                                 XmlDictionaryString dns = ent.Value as XmlDictionaryString;
154                                 if (ns != null) {
155                                         if (prefix.Length > 0) {
156                                                 writer.Write (BF.PrefixNSString);
157                                                 writer.Write (prefix);
158                                         }
159                                         else
160                                                 writer.Write (BF.DefaultNSString);
161                                         writer.Write (ns);
162                                 } else {
163                                         if (prefix.Length > 0) {
164                                                 writer.Write (BF.PrefixNSIndex);
165                                                 writer.Write (prefix);
166                                         }
167                                         else
168                                                 writer.Write (BF.DefaultNSIndex);
169                                         WriteDictionaryIndex (dns);
170                                 }
171                         }
172                         ns_index = namespaces.Count;
173                 }
174
175                 private void CheckState ()
176                 {
177                         if (state == WriteState.Closed) {
178                                 throw new InvalidOperationException ("The Writer is closed.");
179                         }
180                 }
181
182                 void ProcessStateForContent ()
183                 {
184                         CheckState ();
185
186                         if (state == WriteState.Element)
187                                 CloseStartElement ();
188
189                         ProcessPendingBuffer (false, false);
190                         if (state != WriteState.Attribute)
191                                 writer = buffer_writer;
192                 }
193
194                 void ProcessTypedValue ()
195                 {
196                         ProcessStateForContent ();
197                         if (state == WriteState.Attribute) {
198                                 if (attr_typed_value)
199                                         throw new InvalidOperationException (String.Format ("A typed value for the attribute '{0}' in namespace '{1}' was already written", current_attr_name, current_attr_ns));
200                                 attr_typed_value = true;
201                         }
202                 }
203
204                 void ProcessPendingBuffer (bool last, bool endElement)
205                 {
206                         if (buffer.Position > 0) {
207                                 byte [] arr = buffer.GetBuffer ();
208                                 if (endElement)
209                                         arr [0]++;
210                                 original.Write (arr, 0, (int) buffer.Position);
211                                 buffer.SetLength (0);
212                         }
213                         if (last)
214                                 writer = original;
215                 }
216
217                 public override void Close ()
218                 {
219                         CloseOpenAttributeAndElements ();
220
221                         if (owns_stream)
222                                 writer.Close ();
223                         else if (state != WriteState.Closed)
224                                 writer.Flush ();
225                         state = WriteState.Closed;
226                 }
227
228                 private void CloseOpenAttributeAndElements ()
229                 {
230                         CloseStartElement ();
231
232                          while (element_count > 0)
233                                 WriteEndElement ();
234                 }
235
236                 private void CloseStartElement ()
237                 {
238                         if (!open_start_element)
239                                 return;
240
241                         if (state == WriteState.Attribute)
242                                 WriteEndAttribute ();
243
244                         AddMissingElementXmlns ();
245
246                         state = WriteState.Content;
247                         open_start_element = false;
248                 }
249
250                 public override void Flush ()
251                 {
252                         writer.Flush ();
253                 }
254
255                 public override string LookupPrefix (string ns)
256                 {
257                         if (ns == null || ns == String.Empty)
258                                 throw new ArgumentException ("The Namespace cannot be empty.");
259
260                         var de = namespaces.LastOrDefault (i => i.Value.ToString () == ns);
261                         return de.Key; // de is KeyValuePair and its default key is null.
262                 }
263
264                 public override void WriteBase64 (byte[] buffer, int index, int count)
265                 {
266                         if (count < 0)
267                                 throw new IndexOutOfRangeException ("Negative count");
268                         ProcessStateForContent ();
269
270                         if (count < 0x100) {
271                                 writer.Write (BF.Bytes8);
272                                 writer.Write ((byte) count);
273                                 writer.Write (buffer, index, count);
274                         } else if (count < 0x10000) {
275                                 writer.Write (BF.Bytes8);
276                                 writer.Write ((ushort) count);
277                                 writer.Write (buffer, index, count);
278                         } else {
279                                 writer.Write (BF.Bytes32);
280                                 writer.Write (count);
281                                 writer.Write (buffer, index, count);
282                         }
283                 }
284
285                 public override void WriteCData (string text)
286                 {
287                         if (text.IndexOf ("]]>") >= 0)
288                                 throw new ArgumentException ("CDATA section cannot contain text \"]]>\".");
289
290                         ProcessStateForContent ();
291
292                         WriteTextBinary (text);
293                 }
294
295                 public override void WriteCharEntity (char ch)
296                 {
297                         WriteChars (new char [] {ch}, 0, 1);
298                 }
299
300                 public override void WriteChars (char[] buffer, int index, int count)
301                 {
302                         ProcessStateForContent ();
303
304                         int blen = Encoding.UTF8.GetByteCount (buffer, index, count);
305
306                         if (blen == 0)
307                                 writer.Write (BF.EmptyText);
308                         else if (blen < 0x100) {
309                                 writer.Write (BF.Chars8);
310                                 writer.Write ((byte) blen);
311                                 writer.Write (buffer, index, count);
312                         } else if (blen < 0x10000) {
313                                 writer.Write (BF.Chars16);
314                                 writer.Write ((ushort) blen);
315                                 writer.Write (buffer, index, count);
316                         } else {
317                                 writer.Write (BF.Chars32);
318                                 writer.Write (blen);
319                                 writer.Write (buffer, index, count);
320                         }
321                 }
322
323                 public override void WriteComment (string text)
324                 {
325                         if (text.EndsWith("-"))
326                                 throw new ArgumentException ("An XML comment cannot contain \"--\" inside.");
327                         else if (text.IndexOf("--") > 0)
328                                 throw new ArgumentException ("An XML comment cannot end with \"-\".");
329
330                         ProcessStateForContent ();
331
332                         if (state == WriteState.Attribute)
333                                 throw new InvalidOperationException ("Comment node is not allowed inside an attribute");
334
335                         writer.Write (BF.Comment);
336                         writer.Write (text);
337                 }
338
339                 public override void WriteDocType (string name, string pubid, string sysid, string subset)
340                 {
341                         throw new NotSupportedException ("This XmlWriter implementation does not support document type.");
342                 }
343
344                 public override void WriteEndAttribute ()
345                 {
346                         if (state != WriteState.Attribute)
347                                 throw new InvalidOperationException("Token EndAttribute in state Start would result in an invalid XML document.");
348
349                         CheckState ();
350
351                         if (attr_value == null)
352                                 attr_value = String.Empty;
353
354                         switch (save_target) {
355                         case SaveTarget.XmlLang:
356                                 xml_lang = attr_value;
357                                 goto default;
358                         case SaveTarget.XmlSpace:
359                                 switch (attr_value) {
360                                 case "preserve":
361                                         xml_space = XmlSpace.Preserve;
362                                         break;
363                                 case "default":
364                                         xml_space = XmlSpace.Default;
365                                         break;
366                                 default:
367                                         throw new ArgumentException (String.Format ("Invalid xml:space value: '{0}'", attr_value));
368                                 }
369                                 goto default;
370                         case SaveTarget.Namespaces:
371                                 if (current_attr_name.ToString ().Length > 0 && attr_value.Length == 0)
372                                         throw new ArgumentException ("Cannot use prefix with an empty namespace.");
373
374                                 AddNamespaceChecked (current_attr_name.ToString (), attr_value);
375                                 break;
376                         default:
377                                 if (!attr_typed_value)
378                                         WriteTextBinary (attr_value);
379                                 break;
380                         }
381
382                         if (current_attr_prefix.Length > 0 && save_target != SaveTarget.Namespaces)
383                                 AddNamespaceChecked (current_attr_prefix, current_attr_ns);
384
385                         state = WriteState.Element;
386                         current_attr_prefix = null;
387                         current_attr_name = null;
388                         current_attr_ns = null;
389                         attr_value = null;
390                         attr_typed_value = false;
391                 }
392
393                 public override void WriteEndDocument ()
394                 {
395                         CloseOpenAttributeAndElements ();
396
397                         switch (state) {
398                         case WriteState.Start:
399                                 throw new InvalidOperationException ("Document has not started.");
400                         case WriteState.Prolog:
401                                 throw new ArgumentException ("This document does not have a root element.");
402                         }
403
404                         state = WriteState.Start;
405                 }
406
407                 bool SupportsCombinedEndElementSupport (byte operation)
408                 {
409                         switch (operation) {
410                         case BF.Comment:
411                                 return false;
412                         }
413                         return true;
414                 }
415
416                 public override void WriteEndElement ()
417                 {
418                         if (element_count-- == 0)
419                                 throw new InvalidOperationException("There was no XML start tag open.");
420
421                         if (state == WriteState.Attribute)
422                                 WriteEndAttribute ();
423
424                         // Comment+EndElement does not exist
425                         bool needExplicitEndElement = buffer.Position == 0 || !SupportsCombinedEndElementSupport (buffer.GetBuffer () [0]);
426                         ProcessPendingBuffer (true, !needExplicitEndElement);
427                         CheckState ();
428                         AddMissingElementXmlns ();
429
430                         if (needExplicitEndElement)
431                                 writer.Write (BF.EndElement);
432
433                         element_ns = element_ns_stack.Pop ();
434                         xml_lang = xml_lang_stack.Pop ();
435                         xml_space = xml_space_stack.Pop ();
436                         int cur = namespaces.Count;
437                         ns_index = ns_index_stack.Pop ();
438                         namespaces.RemoveRange (ns_index, cur - ns_index);
439                         open_start_element = false;
440
441                         Depth--;
442                 }
443
444                 public override void WriteEntityRef (string name)
445                 {
446                         throw new NotSupportedException ("This XmlWriter implementation does not support entity references.");
447                 }
448
449                 public override void WriteFullEndElement ()
450                 {
451                         WriteEndElement ();
452                 }
453
454                 public override void WriteProcessingInstruction (string name, string text)
455                 {
456                         if (name != "xml")
457                                 throw new ArgumentException ("Processing instructions are not supported. ('xml' is allowed for XmlDeclaration; this is because of design problem of ECMA XmlWriter)");
458                         // Otherwise, silently ignored. WriteStartDocument()
459                         // is still callable after this method(!)
460                 }
461
462                 public override void WriteQualifiedName (XmlDictionaryString local, XmlDictionaryString ns)
463                 {
464                         string prefix = namespaces.LastOrDefault (i => i.Value.ToString () == ns.ToString ()).Key;
465                         bool use_dic = prefix != null;
466                         if (prefix == null)
467                                 prefix = LookupPrefix (ns.Value);
468                         if (prefix == null)
469                                 throw new ArgumentException (String.Format ("Namespace URI '{0}' is not bound to any of the prefixes", ns));
470
471                         ProcessTypedValue ();
472
473                         if (use_dic && prefix.Length == 1) {
474                                 writer.Write (BF.QNameIndex);
475                                 writer.Write ((byte) (prefix [0] - 'a'));
476                                 WriteDictionaryIndex (local);
477                         } else {
478                                 // QNameIndex is not applicable.
479                                 WriteString (prefix);
480                                 WriteString (":");
481                                 WriteString (local);
482                         }
483                 }
484
485                 public override void WriteRaw (string data)
486                 {
487                         WriteString (data);
488                 }
489
490                 public override void WriteRaw (char[] buffer, int index, int count)
491                 {
492                         WriteChars (buffer, index, count);
493                 }
494
495                 void CheckStateForAttribute ()
496                 {
497                         CheckState ();
498
499                         if (state != WriteState.Element)
500                                 throw new InvalidOperationException ("Token StartAttribute in state " + WriteState + " would result in an invalid XML document.");
501                 }
502
503                 string CreateNewPrefix ()
504                 {
505                         return CreateNewPrefix (String.Empty);
506                 }
507                 
508                 string CreateNewPrefix (string p)
509                 {
510                         for (char c = 'a'; c <= 'z'; c++)
511                                 if (!namespaces.Any (iter => iter.Key == p + c))
512                                         return p + c;
513                         for (char c = 'a'; c <= 'z'; c++) {
514                                 var s = CreateNewPrefix (c.ToString ());
515                                 if (s != null)
516                                         return s;
517                         }
518                         throw new InvalidOperationException ("too many prefix population");
519                 }
520
521                 bool CollectionContains (ICollection col, string value)
522                 {
523                         foreach (string v in col)
524                                 if (v == value)
525                                         return true;
526                         return false;
527                 }
528
529                 void ProcessStartAttributeCommon (ref string prefix, string localName, string ns, object nameObj, object nsObj)
530                 {
531                         // dummy prefix is created here, while the caller
532                         // still uses empty string as the prefix there.
533                         if (prefix.Length == 0 && ns.Length > 0) {
534                                 prefix = LookupPrefix (ns);
535                                 // Not only null but also ""; when the returned
536                                 // string is empty, then it still needs a dummy
537                                 // prefix, since default namespace does not
538                                 // apply to attribute
539                                 if (String.IsNullOrEmpty (prefix))
540                                         prefix = CreateNewPrefix ();
541                         }
542                         else if (prefix.Length > 0 && ns.Length == 0) {
543                                 switch (prefix) {
544                                 case "xml":
545                                         nsObj = ns = XmlNamespace;
546                                         break;
547                                 case "xmlns":
548                                         nsObj = ns = XmlnsNamespace;
549                                         break;
550                                 default:
551                                         throw new ArgumentException ("Cannot use prefix with an empty namespace.");
552                                 }
553                         }
554                         // here we omit such cases that it is used for writing
555                         // namespace-less xml, unlike XmlTextWriter.
556                         if (prefix == "xmlns" && ns != XmlnsNamespace)
557                                 throw new ArgumentException (String.Format ("The 'xmlns' attribute is bound to the reserved namespace '{0}'", XmlnsNamespace));
558
559                         CheckStateForAttribute ();
560
561                         state = WriteState.Attribute;
562
563                         save_target = SaveTarget.None;
564                         switch (prefix) {
565                         case "xml":
566                                 // MS.NET looks to allow other names than 
567                                 // lang and space (e.g. xml:link, xml:hack).
568                                 ns = XmlNamespace;
569                                 switch (localName) {
570                                 case "lang":
571                                         save_target = SaveTarget.XmlLang;
572                                         break;
573                                 case "space":
574                                         save_target = SaveTarget.XmlSpace;
575                                         break;
576                                 }
577                                 break;
578                         case "xmlns":
579                                 save_target = SaveTarget.Namespaces;
580                                 break;
581                         }
582
583                         current_attr_prefix = prefix;
584                         current_attr_name = nameObj;
585                         current_attr_ns = nsObj;
586                 }
587
588                 public override void WriteStartAttribute (string prefix, string localName, string ns)
589                 {
590                         if (prefix == null)
591                                 prefix = String.Empty;
592                         if (ns == null)
593                                 ns = String.Empty;
594                         if (localName == "xmlns" && prefix.Length == 0) {
595                                 prefix = "xmlns";
596                                 localName = String.Empty;
597                         }
598
599                         ProcessStartAttributeCommon (ref prefix, localName, ns, localName, ns);
600
601                         // for namespace nodes we don't write attribute node here.
602                         if (save_target == SaveTarget.Namespaces)
603                                 return;
604
605                         byte op = prefix.Length == 1 && 'a' <= prefix [0] && prefix [0] <= 'z' ?
606                                   (byte) (prefix [0] - 'a' + BF.PrefixNAttrStringStart) :
607                                   prefix.Length == 0 ? BF.AttrString :
608                                   BF.AttrStringPrefix;
609
610                         if (BF.PrefixNAttrStringStart <= op && op <= BF.PrefixNAttrStringEnd) {
611                                 writer.Write (op);
612                                 writer.Write (localName);
613                         } else {
614                                 writer.Write (op);
615                                 if (prefix.Length > 0)
616                                         writer.Write (prefix);
617                                 writer.Write (localName);
618                         }
619                 }
620
621                 public override void WriteStartDocument ()
622                 {
623                         WriteStartDocument (false);
624                 }
625
626                 public override void WriteStartDocument (bool standalone)
627                 {
628                         if (state != WriteState.Start)
629                                 throw new InvalidOperationException("WriteStartDocument should be the first call.");
630
631                         CheckState ();
632
633                         // write nothing to stream.
634
635                         state = WriteState.Prolog;
636                 }
637
638                 void PrepareStartElement ()
639                 {
640                         ProcessPendingBuffer (true, false);
641                         CheckState ();
642                         CloseStartElement ();
643
644                         Depth++;
645
646                         element_ns_stack.Push (element_ns);
647                         xml_lang_stack.Push (xml_lang);
648                         xml_space_stack.Push (xml_space);
649                         ns_index_stack.Push (ns_index);
650                 }
651
652                 public override void WriteStartElement (string prefix, string localName, string ns)
653                 {
654                         PrepareStartElement ();
655
656                         if ((prefix != null && prefix != String.Empty) && ((ns == null) || (ns == String.Empty)))
657                                 throw new ArgumentException ("Cannot use a prefix with an empty namespace.");
658
659                         if (ns == null)
660                                 ns = String.Empty;
661                         if (ns == String.Empty)
662                                 prefix = String.Empty;
663                         if (prefix == null)
664                                 prefix = String.Empty;
665
666                         byte op = prefix.Length == 1 && 'a' <= prefix [0] && prefix [0] <= 'z' ?
667                                   (byte) (prefix [0] - 'a' + BF.PrefixNElemStringStart) :
668                                   prefix.Length == 0 ? BF.ElemString :
669                                   BF.ElemStringPrefix;
670
671                         if (BF.PrefixNElemStringStart <= op && op <= BF.PrefixNElemStringEnd) {
672                                 writer.Write (op);
673                                 writer.Write (localName);
674                         } else {
675                                 writer.Write (op);
676                                 if (prefix.Length > 0)
677                                         writer.Write (prefix);
678                                 writer.Write (localName);
679                         }
680
681                         OpenElement (prefix, ns);
682                 }
683
684                 void OpenElement (string prefix, object nsobj)
685                 {
686                         string ns = nsobj.ToString ();
687
688                         state = WriteState.Element;
689                         open_start_element = true;
690                         element_prefix = prefix;
691                         element_count++;
692                         element_ns = nsobj.ToString ();
693
694                         if (element_ns != String.Empty && LookupPrefix (element_ns) != prefix)
695                                 AddNamespace (prefix, nsobj);
696                 }
697
698                 void AddNamespace (string prefix, object nsobj)
699                 {
700                         namespaces.Add (new KeyValuePair<string,object> (prefix, nsobj));
701                 }
702
703                 void CheckIfTextAllowed ()
704                 {
705                         switch (state) {
706                         case WriteState.Start:
707                         case WriteState.Prolog:
708                                 throw new InvalidOperationException ("Token content in state Prolog would result in an invalid XML document.");
709                         }
710                 }
711
712                 public override void WriteString (string text)
713                 {
714                         if (text == null)
715                                 throw new ArgumentNullException ("text");
716                         CheckIfTextAllowed ();
717
718                         if (text == null)
719                                 text = String.Empty;
720
721                         ProcessStateForContent ();
722
723                         if (state == WriteState.Attribute)
724                                 attr_value += text;
725                         else
726                                 WriteTextBinary (text);
727                 }
728
729                 public override void WriteString (XmlDictionaryString text)
730                 {
731                         if (text == null)
732                                 throw new ArgumentNullException ("text");
733                         CheckIfTextAllowed ();
734
735                         if (text == null)
736                                 text = XmlDictionaryString.Empty;
737
738                         ProcessStateForContent ();
739
740                         if (state == WriteState.Attribute)
741                                 attr_value += text.Value;
742                         else if (text.Equals (XmlDictionary.Empty))
743                                 writer.Write (BF.EmptyText);
744                         else {
745                                 writer.Write (BF.TextIndex);
746                                 WriteDictionaryIndex (text);
747                         }
748                 }
749
750                 public override void WriteSurrogateCharEntity (char lowChar, char highChar)
751                 {
752                         WriteChars (new char [] {highChar, lowChar}, 0, 2);
753                 }
754
755                 public override void WriteWhitespace (string ws)
756                 {
757                         for (int i = 0; i < ws.Length; i++) {
758                                 switch (ws [i]) {
759                                 case ' ': case '\t': case '\r': case '\n':
760                                         continue;
761                                 default:
762                                         throw new ArgumentException ("Invalid Whitespace");
763                                 }
764                         }
765
766                         ProcessStateForContent ();
767
768                         WriteTextBinary (ws);
769                 }
770
771                 public override void WriteXmlnsAttribute (string prefix, string namespaceUri)
772                 {
773                         if (namespaceUri == null)
774                                 throw new ArgumentNullException ("namespaceUri");
775
776                         if (String.IsNullOrEmpty (prefix))
777                                 prefix = CreateNewPrefix ();
778
779                         CheckStateForAttribute ();
780
781                         AddNamespaceChecked (prefix, namespaceUri);
782
783                         state = WriteState.Element;
784                 }
785
786                 void AddNamespaceChecked (string prefix, object ns)
787                 {
788                         switch (ns.ToString ()) {
789                         case XmlnsNamespace:
790                         case XmlNamespace:
791                                 return;
792                         }
793
794                         if (prefix == null)
795                                 throw new InvalidOperationException ();
796                         var o = namespaces.LastOrDefault (i => i.Key == prefix);
797                         if (o.Key != null) { // i.e. exists
798                                 if (o.Value.ToString () != ns.ToString ()) {
799                                         if (namespaces.LastIndexOf (o) >= ns_index)
800                                                 throw new ArgumentException (String.Format ("The prefix '{0}' is already mapped to another namespace URI '{1}' in this element scope and cannot be mapped to '{2}'", prefix ?? "(null)", o.Value ?? "(null)", ns.ToString ()));
801                                         else
802                                                 AddNamespace  (prefix, ns);
803                                 }
804                         }
805                         else
806                                 AddNamespace  (prefix, ns);
807                 }
808
809                 #region DictionaryString
810
811                 void WriteDictionaryIndex (XmlDictionaryString ds)
812                 {
813                         XmlDictionaryString ds2;
814                         bool isSession = false;
815                         int idx = ds.Key;
816                         if (ds.Dictionary != dict_ext) {
817                                 isSession = true;
818                                 if (dict_int.TryLookup (ds.Value, out ds2))
819                                         ds = ds2;
820                                 if (!session.TryLookup (ds, out idx))
821                                         session.TryAdd (dict_int.Add (ds.Value), out idx);
822                         }
823                         if (idx >= 0x80) {
824                                 writer.Write ((byte) (0x80 + ((idx % 0x80) << 1) + (isSession ? 1 : 0)));
825                                 writer.Write ((byte) ((byte) (idx / 0x80) << 1));
826                         }
827                         else
828                                 writer.Write ((byte) (((idx % 0x80) << 1) + (isSession ? 1 : 0)));
829                 }
830
831                 public override void WriteStartElement (string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri)
832                 {
833                         PrepareStartElement ();
834
835                         if (prefix == null)
836                                 prefix = String.Empty;
837
838                         byte op = prefix.Length == 1 && 'a' <= prefix [0] && prefix [0] <= 'z' ?
839                                   (byte) (prefix [0] - 'a' + BF.PrefixNElemIndexStart) :
840                                   prefix.Length == 0 ? BF.ElemIndex :
841                                   BF.ElemIndexPrefix;
842
843                         if (BF.PrefixNElemIndexStart <= op && op <= BF.PrefixNElemIndexEnd) {
844                                 writer.Write (op);
845                                 WriteDictionaryIndex (localName);
846                         } else {
847                                 writer.Write (op);
848                                 if (prefix.Length > 0)
849                                         writer.Write (prefix);
850                                 WriteDictionaryIndex (localName);
851                         }
852
853                         OpenElement (prefix, namespaceUri);
854                 }
855
856                 public override void WriteStartAttribute (string prefix, XmlDictionaryString localName, XmlDictionaryString ns)
857                 {
858                         if (localName == null)
859                                 throw new ArgumentNullException ("localName");
860                         if (prefix == null)
861                                 prefix = String.Empty;
862                         if (ns == null)
863                                 ns = XmlDictionaryString.Empty;
864                         if (localName.Value == "xmlns" && prefix.Length == 0) {
865                                 prefix = "xmlns";
866                                 localName = XmlDictionaryString.Empty;
867                         }
868
869                         ProcessStartAttributeCommon (ref prefix, localName.Value, ns.Value, localName, ns);
870
871                         if (save_target == SaveTarget.Namespaces)
872                                 return;
873
874                         if (prefix.Length == 1 && 'a' <= prefix [0] && prefix [0] <= 'z') {
875                                 writer.Write ((byte) (prefix [0] - 'a' + BF.PrefixNAttrIndexStart));
876                                 WriteDictionaryIndex (localName);
877                         } else {
878                                 byte op = ns.Value.Length == 0 ? BF.AttrIndex :BF.AttrIndexPrefix;
879                                 // Write to Stream
880                                 writer.Write (op);
881                                 if (prefix.Length > 0)
882                                         writer.Write (prefix);
883                                 WriteDictionaryIndex (localName);
884                         }
885                 }
886
887                 public override void WriteXmlnsAttribute (string prefix, XmlDictionaryString namespaceUri)
888                 {
889                         if (namespaceUri == null)
890                                 throw new ArgumentNullException ("namespaceUri");
891
892                         if (String.IsNullOrEmpty (prefix))
893                                 prefix = CreateNewPrefix ();
894
895                         CheckStateForAttribute ();
896
897                         AddNamespaceChecked (prefix, namespaceUri);
898
899                         state = WriteState.Element;
900                 }
901                 #endregion
902
903                 #region WriteValue
904                 public override void WriteValue (bool value)
905                 {
906                         ProcessTypedValue ();
907                         writer.Write ((byte) (value ? BF.BoolTrue : BF.BoolFalse));
908                 }
909
910                 public override void WriteValue (int value)
911                 {
912                         WriteValue ((long) value);
913                 }
914
915                 public override void WriteValue (long value)
916                 {
917                         ProcessTypedValue ();
918
919                         if (value == 0)
920                                 writer.Write (BF.Zero);
921                         else if (value == 1)
922                                 writer.Write (BF.One);
923                         else if (value < 0 || value > uint.MaxValue) {
924                                 writer.Write (BF.Int64);
925                                 for (int i = 0; i < 8; i++) {
926                                         writer.Write ((byte) (value & 0xFF));
927                                         value >>= 8;
928                                 }
929                         } else if (value <= byte.MaxValue) {
930                                 writer.Write (BF.Int8);
931                                 writer.Write ((byte) value);
932                         } else if (value <= short.MaxValue) {
933                                 writer.Write (BF.Int16);
934                                 writer.Write ((byte) (value & 0xFF));
935                                 writer.Write ((byte) (value >> 8));
936                         } else if (value <= int.MaxValue) {
937                                 writer.Write (BF.Int32);
938                                 for (int i = 0; i < 4; i++) {
939                                         writer.Write ((byte) (value & 0xFF));
940                                         value >>= 8;
941                                 }
942                         }
943                 }
944
945                 public override void WriteValue (float value)
946                 {
947                         ProcessTypedValue ();
948                         writer.Write (BF.Single);
949                         WriteValueContent (value);
950                 }
951
952                 void WriteValueContent (float value)
953                 {
954                         writer.Write (value);
955                 }
956
957                 public override void WriteValue (double value)
958                 {
959                         ProcessTypedValue ();
960                         writer.Write (BF.Double);
961                         WriteValueContent (value);
962                 }
963
964                 void WriteValueContent (double value)
965                 {
966                         writer.Write (value);
967                 }
968
969                 public override void WriteValue (decimal value)
970                 {
971                         ProcessTypedValue ();
972                         writer.Write (BF.Decimal);
973                         WriteValueContent (value);
974                 }
975
976                 void WriteValueContent (decimal value)
977                 {
978                         int [] bits = Decimal.GetBits (value);
979                         // so, looks like it is saved as its internal form,
980                         // not the returned order.
981                         // BinaryWriter.Write(Decimal) is useless here.
982                         writer.Write (bits [3]);
983                         writer.Write (bits [2]);
984                         writer.Write (bits [0]);
985                         writer.Write (bits [1]);
986                 }
987
988                 public override void WriteValue (DateTime value)
989                 {
990                         ProcessTypedValue ();
991                         writer.Write (BF.DateTime);
992                         WriteValueContent (value);
993                 }
994
995                 void WriteValueContent (DateTime value)
996                 {
997                         writer.Write (value.Ticks);
998                 }
999
1000                 public override void WriteValue (Guid value)
1001                 {
1002                         ProcessTypedValue ();
1003                         writer.Write (BF.Guid);
1004                         WriteValueContent (value);
1005                 }
1006
1007                 void WriteValueContent (Guid value)
1008                 {
1009                         byte [] bytes = value.ToByteArray ();
1010                         writer.Write (bytes, 0, bytes.Length);
1011                 }
1012
1013                 public override void WriteValue (UniqueId value)
1014                 {
1015                         if (value == null)
1016                                 throw new ArgumentNullException ("value");
1017
1018                         Guid guid;
1019                         if (value.TryGetGuid (out guid)) {
1020                                 // this conditional branching is required for
1021                                 // attr_typed_value not being true.
1022                                 ProcessTypedValue ();
1023
1024                                 writer.Write (BF.UniqueId);
1025                                 byte [] bytes = guid.ToByteArray ();
1026                                 writer.Write (bytes, 0, bytes.Length);
1027                         } else {
1028                                 WriteValue (value.ToString ());
1029                         }
1030                 }
1031
1032                 public override void WriteValue (TimeSpan value)
1033                 {
1034                         ProcessTypedValue ();
1035
1036                         writer.Write (BF.TimeSpan);
1037                         WriteValueContent (value);
1038                 }
1039
1040                 void WriteValueContent (TimeSpan value)
1041                 {
1042                         WriteBigEndian (value.Ticks, 8);
1043                 }
1044                 #endregion
1045
1046                 private void WriteBigEndian (long value, int digits)
1047                 {
1048                         long v = 0;
1049                         for (int i = 0; i < digits; i++) {
1050                                 v = (v << 8) + (value & 0xFF);
1051                                 value >>= 8;
1052                         }
1053                         for (int i = 0; i < digits; i++) {
1054                                 writer.Write ((byte) (v & 0xFF));
1055                                 v >>= 8;
1056                         }
1057                 }
1058
1059                 private void WriteTextBinary (string text)
1060                 {
1061                         if (text.Length == 0)
1062                                 writer.Write (BF.EmptyText);
1063                         else {
1064                                 char [] arr = text.ToCharArray ();
1065                                 WriteChars (arr, 0, arr.Length);
1066                         }
1067                 }
1068
1069                 #endregion
1070
1071                 #region Write typed array content
1072
1073                 // they are packed in WriteValue(), so we cannot reuse
1074                 // them for array content.
1075
1076                 void WriteValueContent (bool value)
1077                 {
1078                         writer.Write (value ? (byte) 1 : (byte) 0);
1079                 }
1080
1081                 void WriteValueContent (short value)
1082                 {
1083                         writer.Write (value);
1084                 }
1085
1086                 void WriteValueContent (int value)
1087                 {
1088                         writer.Write (value);
1089                 }
1090
1091                 void WriteValueContent (long value)
1092                 {
1093                         writer.Write (value);
1094                 }
1095
1096                 #endregion
1097         }
1098 }