2003-03-17 Daniel Morgan <danmorg@sc.rr.com>
[mono.git] / mcs / class / System.Data / System.Data / DataSet.cs
1 // 
2 // System.Data/DataSet.cs
3 //
4 // Author:
5 //   Christopher Podurgiel <cpodurgiel@msn.com>
6 //   Daniel Morgan <danmorg@sc.rr.com>
7 //   Rodrigo Moya <rodrigo@ximian.com>
8 //   Stuart Caborn <stuart.caborn@virgin.net>
9 //   Tim Coleman (tim@timcoleman.com)
10 //   Ville Palo <vi64pa@koti.soon.fi>
11 //
12 // (C) Ximian, Inc. 2002
13 // Copyright (C) Tim Coleman, 2002
14 //
15
16 using System;
17 using System.Collections;
18 using System.ComponentModel;
19 using System.Globalization;
20 using System.Threading;
21 using System.IO;
22 using System.Runtime.Serialization;
23 using System.Xml;
24 using System.Xml.Schema;
25
26 namespace System.Data {
27         /// <summary>
28         /// an in-memory cache of data 
29         /// </summary>
30         //[Designer]
31         [ToolboxItem (false)]
32         [DefaultProperty ("DataSetName")]
33         [Serializable]
34         public class DataSet : MarshalByValueComponent, IListSource,
35                 ISupportInitialize, ISerializable {
36                 private string dataSetName;
37                 private string _namespace = "";
38                 private string prefix;
39                 private bool caseSensitive;
40                 private bool enforceConstraints = true;
41                 private DataTableCollection tableCollection;
42                 private DataRelationCollection relationCollection;
43                 private PropertyCollection properties;
44                 private DataViewManager defaultView;
45                 private CultureInfo locale;
46                 
47                 #region Constructors
48
49                 public DataSet() : this ("NewDataSet") {                
50                 }
51
52                 public DataSet(string name) {
53                         dataSetName = name;
54                         tableCollection = new DataTableCollection (this);
55                         relationCollection = new DataRelationCollection.DataSetRelationCollection (this);
56                         properties = new PropertyCollection();
57                 }
58
59                 [MonoTODO]
60                 protected DataSet(SerializationInfo info, StreamingContext context) : this () {
61                         throw new NotImplementedException ();
62                 }
63
64                 #endregion // Constructors
65
66                 #region Public Properties
67
68                 [DataCategory ("Data")]
69                 [DataSysDescription ("Indicates whether comparing strings within the DataSet is case sensitive.")]
70                 [DefaultValue (false)]
71                 public bool CaseSensitive {
72                         get { return caseSensitive; } 
73                         set {
74                                 foreach (DataTable T in Tables) {
75                                         if (T.VirginCaseSensitive)
76                                                 T.CaseSensitive = value;
77                                 }
78
79                                 caseSensitive = value; 
80                         }
81                 }
82
83                 [DataCategory ("Data")]
84                 [DataSysDescription ("The name of this DataSet.")]
85                 [DefaultValue ("")]
86                 public string DataSetName {
87                         get { return dataSetName; } 
88                         set { dataSetName = value; }
89                 }
90
91                 [DataSysDescription ("Indicates a custom \"view\" of the data contained by the DataSet. This view allows filtering, searching, and navigating through the custom data view.")]
92                 [Browsable (false)]
93                 public DataViewManager DefaultViewManager {
94                         get {
95                                 if (defaultView == null)
96                                         defaultView = new DataViewManager (this);
97                                 return defaultView;
98                         } 
99                 }
100
101                 [DataSysDescription ("Indicates whether constraint rules are to be followed.")]
102                 [DefaultValue (true)]
103                 public bool EnforceConstraints {
104                         get { return enforceConstraints; } 
105                         set { enforceConstraints = value; }
106                 }
107
108                 [Browsable (false)]
109                 [DataCategory ("Data")]
110                 [DataSysDescription ("The collection that holds custom user information.")]
111                 public PropertyCollection ExtendedProperties {
112                         get { return properties; }
113                 }
114
115                 [Browsable (false)]
116                 [DataSysDescription ("Indicates that the DataSet has errors.")]
117                 public bool HasErrors {
118                         [MonoTODO]
119                         get {
120                                 throw new NotImplementedException ();
121                         }
122                 }
123
124                 [DataCategory ("Data")]
125                 [DataSysDescription ("Indicates a locale under which to compare strings within the DataSet.")]
126                 public CultureInfo Locale {
127                         get {
128                                 return locale;
129                         }
130                         set {
131                                 if (locale == null || !locale.Equals(value)) {
132                                         // TODO: check if the new locale is valid
133                                         // TODO: update locale of all tables
134                                         locale = value;
135                                 }
136                         }
137                 }
138
139                 public void Merge (DataRow[] rows)
140                 {
141                         Merge (rows, false, MissingSchemaAction.Add);
142                 }
143                 
144                 public void Merge (DataSet dataSet)
145                 {
146                         Merge (dataSet, false, MissingSchemaAction.Add);
147                 }
148                 
149                 public void Merge (DataTable table)
150                 {
151                         Merge (table, false, MissingSchemaAction.Add);
152                 }
153                 
154                 public void Merge (DataSet dataSet, bool preserveChanges)
155                 {
156                         Merge (dataSet, preserveChanges, MissingSchemaAction.Add);
157                 }
158                 
159                 [MonoTODO]
160                 public void Merge (DataRow[] rows, bool preserveChanges, MissingSchemaAction missingSchemaAction)
161                 {
162                         throw new NotImplementedException();
163                 }
164                 
165                 [MonoTODO]
166                 public void Merge (DataSet dataSet, bool preserveChanges, MissingSchemaAction missingSchemaAction)
167                 {
168                         throw new NotImplementedException();
169                 }
170                 
171                 [MonoTODO]
172                 public void Merge (DataTable table, bool preserveChanges, MissingSchemaAction missingSchemaAction)
173                 {
174                         throw new NotImplementedException();
175                 }
176                 
177                 [DataCategory ("Data")]
178                 [DataSysDescription ("Indicates the XML uri namespace for the root element pointed at by this DataSet.")]
179                 [DefaultValue ("")]
180                 public string Namespace {
181                         [MonoTODO]
182                         get { return _namespace; } 
183                         [MonoTODO]
184                         set {
185                                 //TODO - trigger an event if this happens?
186                                 _namespace = value;
187                         }
188                 }
189
190                 [DataCategory ("Data")]
191                 [DataSysDescription ("Indicates the prefix of the namespace used for this DataSet.")]
192                 [DefaultValue ("")]
193                 public string Prefix {
194                         [MonoTODO]
195                         get { return prefix; } 
196                         [MonoTODO]
197                         set {
198                                 //TODO - trigger an event if this happens?
199                                 prefix = value;
200                         }
201                 }
202
203                 [DataCategory ("Data")]
204                 [DataSysDescription ("The collection that holds the relations for this DatSet.")]
205                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
206                 public DataRelationCollection Relations {
207                         get {
208                                 return relationCollection;              
209                         }
210                 }
211
212                 [Browsable (false)]
213                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
214                 public override ISite Site {
215                         [MonoTODO]
216                         get {
217                                 throw new NotImplementedException ();
218                         } 
219                         
220                         [MonoTODO]
221                         set {
222                                 throw new NotImplementedException ();
223                         }
224                 }
225
226                 [DataCategory ("Data")]
227                 [DataSysDescription ("The collection that holds the tables for this DataSet.")]
228                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
229                 public DataTableCollection Tables {
230                         get { return tableCollection; }
231                 }
232
233                 #endregion // Public Properties
234
235                 #region Public Methods
236
237                 [MonoTODO]
238                 public void AcceptChanges()
239                 {
240                         foreach (DataTable tempTable in tableCollection)
241                                 tempTable.AcceptChanges ();
242                 }
243
244                 public void Clear()
245                 {
246                         // TODO: if currently bound to a XmlDataDocument
247                         //       throw a NotSupportedException
248                         for (int t = 0; t < tableCollection.Count; t++) {
249                                 tableCollection[t].Clear ();
250                         }
251                 }
252
253                 public virtual DataSet Clone()
254                 {
255                         DataSet Copy = new DataSet ();
256                         CopyProperties (Copy);
257
258                         foreach (DataTable Table in Tables) {
259                                 Copy.Tables.Add (Table.Clone ());
260                         }       
261                         
262                         return Copy;
263                 }
264
265                 // Copies both the structure and data for this DataSet.
266                 public DataSet Copy()
267                 {
268                         DataSet Copy = new DataSet ();
269                         CopyProperties (Copy);
270
271                         // Copy DatSet's tables
272                         foreach (DataTable Table in Tables) {                           
273                                 Copy.Tables.Add (Table.Copy ());
274                         }
275
276                         return Copy;
277                 }
278
279                 [MonoTODO]
280                 private void CopyProperties (DataSet Copy)
281                 {
282                         Copy.CaseSensitive = CaseSensitive;
283                         //Copy.Container = Container
284                         Copy.DataSetName = DataSetName;
285                         //Copy.DefaultViewManager
286                         //Copy.DesignMode
287                         Copy.EnforceConstraints = EnforceConstraints;
288                         //Copy.ExtendedProperties 
289                         //Copy.HasErrors
290                         //Copy.Locale = Locale;
291                         Copy.Namespace = Namespace;
292                         Copy.Prefix = Prefix;
293                         //Copy.Relations = Relations;
294                         //Copy.Site = Site;
295
296                 }
297
298                 public DataSet GetChanges()
299                 {
300                         throw new NotImplementedException ();
301                 }
302
303                 
304                 public DataSet GetChanges(DataRowState rowStates)
305                 {
306                         throw new NotImplementedException ();
307                 }
308
309                 public string GetXml()
310                 {
311                         StringWriter Writer = new StringWriter ();
312                         WriteXml (Writer, XmlWriteMode.IgnoreSchema);
313                         return Writer.ToString ();
314                 }
315
316                 public string GetXmlSchema()
317                 {
318                         StringWriter Writer = new StringWriter ();
319                         WriteXmlSchema (Writer);
320                         return Writer.ToString ();
321                 }
322
323                 [MonoTODO]
324                 public bool HasChanges()
325                 {
326                         throw new NotImplementedException ();
327                 }
328
329                 [MonoTODO]
330                 public bool HasChanges(DataRowState rowState)
331                 {
332                         throw new NotImplementedException ();
333                 }
334
335                 [MonoTODO]
336                 public void InferXmlSchema(XmlReader reader, string[] nsArray)
337                 {
338                 }
339
340                 public void InferXmlSchema(Stream stream, string[] nsArray)
341                 {
342                         InferXmlSchema (new XmlTextReader(stream), nsArray);
343                 }
344
345                 public void InferXmlSchema(TextReader reader, string[] nsArray)
346                 {
347                         InferXmlSchema (new XmlTextReader(reader), nsArray);
348                 }
349
350                 public void InferXmlSchema(string fileName, string[] nsArray)
351                 {
352                         XmlTextReader reader = new XmlTextReader(fileName);
353                         try {
354                                 InferXmlSchema (reader, nsArray);
355                         } finally {
356                                 reader.Close ();
357                         }
358                 }
359
360                 public virtual void RejectChanges()
361                 {
362                         throw new NotImplementedException ();
363                 }
364
365                 public virtual void Reset()
366                 {
367                         throw new NotImplementedException ();
368                 }
369
370                 public void WriteXml(Stream stream)
371                 {
372                         XmlWriter writer = new XmlTextWriter(stream, null );
373                         
374                         WriteXml( writer );
375                 }
376
377                 ///<summary>
378                 /// Writes the current data for the DataSet to the specified file.
379                 /// </summary>
380                 /// <param name="filename">Fully qualified filename to write to</param>
381                 public void WriteXml(string fileName)
382                 {
383                         XmlWriter writer = new XmlTextWriter(fileName, null );
384                         
385                         WriteXml( writer );
386                         
387                         writer.Close();
388                 }
389
390                 public void WriteXml(TextWriter writer)
391                 {
392                         XmlWriter xwriter = new XmlTextWriter(writer );
393                         
394                         WriteXml( xwriter );
395                 }
396
397                 public void WriteXml(XmlWriter writer)
398                 {
399                         WriteXml( writer, XmlWriteMode.IgnoreSchema );
400                 }
401
402                 public void WriteXml(Stream stream, XmlWriteMode mode)
403                 {
404                         XmlWriter writer = new XmlTextWriter(stream, null );
405                         
406                         WriteXml( writer, mode );
407                 }
408
409                 public void WriteXml(string fileName, XmlWriteMode mode)
410                 {
411                         XmlWriter writer = new XmlTextWriter(fileName, null );
412                         
413                         WriteXml( writer, mode );
414                         
415                         writer.Close();
416                 }
417
418                 public void WriteXml(TextWriter writer, XmlWriteMode mode)
419                 {
420                         XmlWriter xwriter = new XmlTextWriter(writer);
421                         
422                         WriteXml( xwriter, mode );
423                 }
424
425                 public void WriteXml(XmlWriter writer, XmlWriteMode mode)
426                 {
427                         if (writer.WriteState == WriteState.Start)
428                                 writer.WriteStartDocument (true);
429
430                         ((XmlTextWriter)writer).Formatting = Formatting.Indented;
431                         WriteStartElement( writer, mode, Namespace, Prefix, DataSetName );
432                         
433                         if( mode == XmlWriteMode.WriteSchema )
434                         {
435                                 DoWriteXmlSchema( writer );
436                         }
437                         
438                         //Write out each table in order, providing it is not
439                         //part of another table structure via a nested parent relationship
440                         foreach( DataTable table in Tables )
441                         {
442                                 bool isTopLevel = true;
443                                 foreach( DataRelation rel in table.ParentRelations )
444                                 {
445                                         if( rel.Nested )
446                                         {
447                                                 isTopLevel = false;
448                                                 break;
449                                         }
450                                 }
451                                 
452                                 if( isTopLevel )
453                                 {
454                                         WriteTable(  writer, table, mode );
455                                 }
456                         }
457                         
458                         writer.WriteEndElement();                       
459                 }
460
461                 public void WriteXmlSchema(Stream stream)
462                 {
463                         XmlWriter writer = new XmlTextWriter(stream, null  );
464                         
465                         WriteXmlSchema( writer );       
466                 }
467
468                 public void WriteXmlSchema(string fileName)
469                 {
470                         XmlWriter writer = new XmlTextWriter( fileName, null );
471                 
472                         WriteXmlSchema( writer );
473                 }
474
475                 public void WriteXmlSchema(TextWriter writer)
476                 {
477                         XmlWriter xwriter = new XmlTextWriter( writer );
478                         
479                         WriteXmlSchema( xwriter );
480                 }
481
482                 public void WriteXmlSchema(XmlWriter writer)
483                 {
484                         ((XmlTextWriter)writer).Formatting = Formatting.Indented;
485                         //Create a skeleton doc and then write the schema 
486                         //proper which is common to the WriteXml method in schema mode
487                         writer.WriteStartDocument();
488                         
489                         DoWriteXmlSchema( writer );
490                         
491                         writer.WriteEndDocument();
492                 }
493
494                 public void ReadXmlSchema(Stream stream)
495                 {
496                         XmlReader reader = new XmlTextReader( stream, null );
497                         ReadXmlSchema( reader);
498                 }
499
500                 public void ReadXmlSchema(string str)
501                 {
502                         XmlReader reader = new XmlTextReader( str );
503                         ReadXmlSchema( reader );
504                 }
505
506                 public void ReadXmlSchema(TextReader treader)
507                 {
508                         XmlReader reader = new XmlTextReader( treader );
509                         ReadXmlSchema( reader );                        
510                 }
511
512                 public void ReadXmlSchema(XmlReader reader)
513                 {
514                         XmlSchemaMapper SchemaMapper = new XmlSchemaMapper (this);
515                         SchemaMapper.Read (reader);
516                 }
517
518                 public XmlReadMode ReadXml (Stream stream)
519                 {
520                         return ReadXml (new XmlTextReader (stream));
521                 }
522
523                 public XmlReadMode ReadXml (string str)
524                 {
525                         return ReadXml (new XmlTextReader (str));
526                 }
527
528                 public XmlReadMode ReadXml (TextReader reader)
529                 {
530                         return ReadXml (new XmlTextReader (reader));
531                 }
532
533                 public XmlReadMode ReadXml (XmlReader r)
534                 {
535                         XmlDataLoader Loader = new XmlDataLoader (this);
536                         // FIXME: somekinda exception?
537                         if (!r.Read ())
538                                 return XmlReadMode.Auto; // FIXME
539
540                         /*\
541                          *  If document is diffgram we will use diffgram
542                         \*/
543                         if (r.LocalName == "diffgram")
544                                 return ReadXml (r, XmlReadMode.DiffGram);
545
546                         /*\
547                          *  If we already have a schema, or the document 
548                          *  contains an in-line schema, sets XmlReadMode to ReadSchema.
549                         \*/
550
551                         // FIXME: is this always true: "if we have tables we have to have schema also"
552                         if (Tables.Count > 0)                           
553                                 return ReadXml (r, XmlReadMode.ReadSchema);
554
555                         /*\
556                          *  If we dont have a schema yet and document 
557                          *  contains no inline-schema  mode is XmlReadMode.InferSchema
558                         \*/
559
560                         return ReadXml (r, XmlReadMode.InferSchema);
561
562                 }
563
564                 public XmlReadMode ReadXml (Stream stream, XmlReadMode mode)
565                 {
566                         return ReadXml (new XmlTextReader (stream), mode);
567                 }
568
569                 public XmlReadMode ReadXml (string str, XmlReadMode mode)
570                 {
571                         return ReadXml (new XmlTextReader (str), mode);
572                 }
573
574                 public XmlReadMode ReadXml (TextReader reader, XmlReadMode mode)
575                 {
576                         return ReadXml (new XmlTextReader (reader), mode);
577                 }
578
579                 [MonoTODO]
580                 public XmlReadMode ReadXml (XmlReader reader, XmlReadMode mode)
581                 {
582                         XmlReadMode Result = XmlReadMode.Auto;
583
584                         if (mode == XmlReadMode.DiffGram) {
585                                 XmlDiffLoader DiffLoader = new XmlDiffLoader (this);
586                                 DiffLoader.Load (reader);
587                                 Result =  XmlReadMode.DiffGram;
588                         }
589                         else {
590                                 XmlDataLoader Loader = new XmlDataLoader (this);
591                                 Result = Loader.LoadData (reader, mode);
592                         }
593
594                         return Result;
595                 }
596
597                 #endregion // Public Methods
598
599                 #region Public Events
600
601                 [DataCategory ("Action")]
602                 [DataSysDescription ("Occurs when it is not possible to merge schemas for two tables with the same name.")]
603                 public event MergeFailedEventHandler MergeFailed;
604
605                 #endregion // Public Events
606
607                 #region Destructors
608
609                 ~DataSet()
610                 {
611                 }
612
613                 #endregion Destructors
614
615                 #region IListSource methods
616                 IList IListSource.GetList ()
617                 {
618                         return DefaultViewManager;
619                 }
620                 
621                 bool IListSource.ContainsListCollection {
622                         get {
623                                 return true;
624                         }
625                 }
626                 #endregion IListSource methods
627                 
628                 #region ISupportInitialize methods
629                 public void BeginInit ()
630                 {
631                         throw new NotImplementedException ();
632                 }
633                 
634                 public void EndInit ()
635                 {
636                         throw new NotImplementedException ();
637                 }
638                 #endregion
639
640                 #region ISerializable
641                 void ISerializable.GetObjectData (SerializationInfo si, StreamingContext sc)
642                 {
643                         throw new NotImplementedException ();
644                 }
645                 #endregion
646                 
647                 #region Protected Methods
648                 protected void GetSerializationData(SerializationInfo info, StreamingContext context)
649                 {
650                         string s = info.GetValue ("XmlDiffGram", typeof (String)) as String;
651                         if (s != null) ReadXmlSerializable (new XmlTextReader(new StringReader(s)));
652                 }
653                 
654                 
655                 protected virtual System.Xml.Schema.XmlSchema GetSchemaSerializable()
656                 {
657                         return null; // FIXME
658                 }
659                 
660                 protected virtual void ReadXmlSerializable(XmlReader reader)
661                 {
662                         ReadXml(reader, XmlReadMode.DiffGram); // FIXME
663                 }
664                 
665                 protected virtual bool ShouldSerializeRelations ()
666                 {
667                         return true;
668                 }
669                 
670                 protected virtual bool ShouldSerializeTables ()
671                 {
672                         return true;
673                 }
674
675                 [MonoTODO]
676                 protected internal virtual void OnPropertyChanging (PropertyChangedEventArgs pcevent)
677                 {
678                 }
679
680                 [MonoTODO]
681                 protected virtual void OnRemoveRelation (DataRelation relation)
682                 {
683                 }
684
685                 [MonoTODO]
686                 protected virtual void OnRemoveTable (DataTable table)
687                 {
688                 }
689
690                 [MonoTODO]
691                 protected internal void RaisePropertyChanging (string name)
692                 {
693                 }
694                 #endregion
695
696                 #region Private Xml Serialisation
697
698                 private string WriteObjectXml( object o ) {
699                         switch (Type.GetTypeCode (o.GetType ())) {
700                         case TypeCode.Boolean:
701                                 return XmlConvert.ToString ((Boolean) o);
702                         case TypeCode.Byte:
703                                 return XmlConvert.ToString ((Byte) o);
704                         case TypeCode.Char:
705                                 return XmlConvert.ToString ((Char) o);
706                         case TypeCode.DateTime:
707                                 return XmlConvert.ToString ((DateTime) o);
708                         case TypeCode.Decimal:
709                                 return XmlConvert.ToString ((Decimal) o);
710                         case TypeCode.Double:
711                                 return XmlConvert.ToString ((Double) o);
712                         case TypeCode.Int16:
713                                 return XmlConvert.ToString ((Int16) o);
714                         case TypeCode.Int32:
715                                 return XmlConvert.ToString ((Int32) o);
716                         case TypeCode.Int64:
717                                 return XmlConvert.ToString ((Int64) o);
718                         case TypeCode.SByte:
719                                 return XmlConvert.ToString ((SByte) o);
720                         case TypeCode.Single:
721                                 return XmlConvert.ToString ((Single) o);
722                         case TypeCode.UInt16:
723                                 return XmlConvert.ToString ((UInt16) o);
724                         case TypeCode.UInt32:
725                                 return XmlConvert.ToString ((UInt32) o);
726                         case TypeCode.UInt64:
727                                 return XmlConvert.ToString ((UInt64) o);
728                         }
729                         if (o is TimeSpan) return XmlConvert.ToString ((TimeSpan) o);
730                         if (o is Guid) return XmlConvert.ToString ((Guid) o);
731                         return o.ToString();
732                 }
733         
734                 private void WriteTable( XmlWriter writer, DataTable table, XmlWriteMode mode )
735                 {
736                         DataRow[] rows = new DataRow [table.Rows.Count];
737                         table.Rows.CopyTo (rows, 0);
738                         WriteTable (writer, rows, mode);
739                 }
740
741                 private void WriteTable( XmlWriter writer, DataRow[] rows, XmlWriteMode mode )
742                 {
743                         //The columns can be attributes, hidden, elements, or simple content
744                         //There can be 0-1 simple content cols or 0-* elements
745                         System.Collections.ArrayList atts;
746                         System.Collections.ArrayList elements;
747                         DataColumn simple = null;
748
749                         if (rows.Length == 0) return;
750                         DataTable table = rows[0].Table;
751                         SplitColumns( table, out atts, out elements, out simple );
752
753                         foreach( DataRow row in rows )
754                         {
755                                 //sort out the namespacing
756                                 string nspc = table.Namespace.Length > 0 ? table.Namespace : Namespace;
757
758                                 // First check are all the rows null. If they are we just write empty element
759                                 bool AllNulls = true;
760                                 foreach (DataColumn dc in table.Columns) {
761                                 
762                                         if (row [dc.ColumnName] != DBNull.Value) {
763                                                 AllNulls = false;
764                                                 break;
765                                         } 
766                                 }
767
768                                 // If all of the columns were null, we have to write empty element
769                                 if (AllNulls) {
770                                         writer.WriteElementString (table.TableName, "");
771                                         continue;
772                                 }
773
774                                 WriteStartElement( writer, mode, nspc, table.Prefix, table.TableName );
775                                 
776                                 foreach( DataColumn col in atts )
777                                 {                                       
778                                         WriteAttributeString( writer, mode, col.Namespace, col.Prefix, col.ColumnName, row[col].ToString() );
779                                 }
780                                 
781                                 if( simple != null )
782                                 {
783                                         writer.WriteString( WriteObjectXml(row[simple]) );
784                                 }
785                                 else
786                                 {                                       
787                                         foreach( DataColumn col in elements )
788                                         {
789                                                 string colnspc = nspc;
790                                                 object rowObject = row [col];
791                                                                                                 
792                                                 if (rowObject == null || rowObject == DBNull.Value)
793                                                         continue;
794
795                                                 if( col.Namespace != null )
796                                                 {
797                                                         colnspc = col.Namespace;
798                                                 }
799                                 
800                                                 //TODO check if I can get away with write element string
801                                                 WriteStartElement( writer, mode, colnspc, col.Prefix, col.ColumnName );
802                                                 writer.WriteString( WriteObjectXml(rowObject) );
803                                                 writer.WriteEndElement();
804                                         }
805                                 }
806                                 
807                                 foreach (DataRelation relation in table.ChildRelations) {
808                                         if (relation.Nested) {
809                                                 WriteTable (writer, row.GetChildRows(relation), mode);
810                                         }
811                                 }
812                                 
813                                 writer.WriteEndElement();
814                         }
815
816                 }
817                     
818                 private void WriteStartElement( XmlWriter writer, XmlWriteMode mode, string nspc, string prefix, string name )
819                 {                       
820                         switch(  mode )
821                                 {
822                                         case XmlWriteMode.WriteSchema:
823                                                 if( nspc == null || nspc == "" )
824                                                 {
825                                                         writer.WriteStartElement( name );
826                                                 }
827                                                 else if( prefix != null )
828                                                 {                                                       
829                                                         writer.WriteStartElement(prefix, name, nspc );
830                                                 }                                               
831                                                 else
832                                                 {                                       
833                                                         writer.WriteStartElement( writer.LookupPrefix( nspc ), name, nspc );
834                                                 }
835                                                 break;
836                                         case XmlWriteMode.DiffGram:
837                                                 throw new NotImplementedException();
838                                         default:                                               
839                                                 writer.WriteStartElement(name );
840                                                 break;                                  
841                                 };
842                 }
843                 
844                 private void WriteAttributeString( XmlWriter writer, XmlWriteMode mode, string nspc, string prefix, string name, string stringValue )
845                 {
846                         switch(  mode )
847                                 {
848                                         case XmlWriteMode.WriteSchema:
849                                                 writer.WriteAttributeString(prefix, name, nspc );
850                                                 break;
851                                         case XmlWriteMode.DiffGram:
852                                                 throw new NotImplementedException();                            
853                                         default:
854                                                 writer.WriteAttributeString(name, stringValue );
855                                                 break;                                  
856                                 };
857                 }
858                 
859                 private void DoWriteXmlSchema( XmlWriter writer )
860                 {
861                         //Create the root element and declare all the namespaces etc
862                         writer.WriteStartElement(XmlConstants.SchemaPrefix, XmlConstants.SchemaElement,
863                                                                                 XmlConstants.SchemaNamespace );
864                         writer.WriteAttributeString( XmlConstants.TargetNamespace, Namespace );
865                         writer.WriteAttributeString( "xmlns:" + XmlConstants.TnsPrefix, Namespace );
866                         writer.WriteAttributeString( "xmlns", Namespace );
867                         writer.WriteAttributeString(  "xmlns:" + XmlConstants.MsdataPrefix,                 
868                                                     XmlConstants.MsdataNamespace );
869                         //Set up the attribute and element forms.  
870                         //TODO - is it possible to change this?
871                         //I couldn't spot if it was so I assumed
872                         //that this is set to qualified all round basedon the MS output
873                         writer.WriteAttributeString( XmlConstants.AttributeFormDefault, 
874                                                     XmlConstants.Qualified );
875                         writer.WriteAttributeString( XmlConstants.ElementFormDefault, 
876                                                     XmlConstants.Qualified );
877                         
878                         
879                         //<xs:element name="DSName msdata:IsDataSet="true" msdata:Locale="machine-locale">
880                         //Create the data set element
881                         //All the tables are represented as choice elements in an unlimited series
882                         writer.WriteStartElement( XmlConstants.SchemaPrefix,
883                                                         XmlConstants.Element,
884                                                         XmlConstants.SchemaNamespace );
885                         
886                         writer.WriteAttributeString( XmlConstants.Name, DataSetName );
887                         writer.WriteAttributeString( XmlConstants.MsdataPrefix,  XmlConstants.IsDataSet, XmlConstants.MsdataNamespace, "true" );
888                         //FIXME - sort out the locale string!
889
890                         writer.WriteAttributeString( XmlConstants.MsdataPrefix, XmlConstants.Locale, XmlConstants.MsdataNamespace, Thread.CurrentThread.CurrentCulture.Name);
891                         
892                         //<xs:complexType>
893                         writer.WriteStartElement( XmlConstants.SchemaPrefix,
894                                                         XmlConstants.ComplexType,
895                                                         XmlConstants.SchemaNamespace );
896                         
897                         //<xs:choice maxOccurs="unbounded">
898                         writer.WriteStartElement( XmlConstants.SchemaPrefix,
899                                                         XmlConstants.Choice,
900                                                         XmlConstants.SchemaNamespace );
901                         
902                         writer.WriteAttributeString( XmlConstants.MaxOccurs, XmlConstants.Unbounded );
903                         
904                         
905                         //Write out schema for each table in order, providing it is not
906                         //part of another table structure via a nested parent relationship
907                         foreach( DataTable table in Tables )
908                         {               
909                                 bool isTopLevel = true;
910                                 foreach( DataRelation rel in table.ParentRelations )
911                                 {
912                                         if( rel.Nested )
913                                         {
914                                                 isTopLevel = false;
915                                                 break;
916                                         }
917                                 }
918                                 
919                                 if( isTopLevel )
920                                 {
921                                         WriteTableSchema(  writer, table );
922                                 }
923                         }
924                         
925                         //</xs:choice>
926                         writer.WriteEndElement();
927                         //</xs:complexType>
928                         writer.WriteEndElement();
929                         
930                         //TODO - now add in the relationships as key and unique constraints etc
931                         
932                         //</xs:element>
933                         writer.WriteEndElement();
934                         
935                         
936                         //</schema>
937                         writer.WriteEndElement();
938                 }
939                 
940                 private void WriteTableSchema( XmlWriter writer, DataTable table )
941                 {
942                         ArrayList elements;
943                         ArrayList atts;
944                         DataColumn simple;
945                         
946                         SplitColumns( table,out  atts, out elements, out simple );
947                         
948                         //<xs:element name="TableName">
949                         writer.WriteStartElement( XmlConstants.SchemaPrefix,
950                                                  XmlConstants.Element,
951                                                  XmlConstants.SchemaNamespace );
952                         
953                         writer.WriteAttributeString( XmlConstants.Name, table.TableName );
954                         
955                         //<xs:complexType>
956                         writer.WriteStartElement( XmlConstants.SchemaPrefix,
957                                                  XmlConstants.ComplexType,
958                                                  XmlConstants.SchemaNamespace );
959                         
960                         //TODO - what about the simple content?
961                         if( elements.Count == 0 )                               
962                         {                               
963                         }
964                         else
965                         {                               
966                         //A sequence of element types or a simple content node
967                         //<xs:sequence>
968                         writer.WriteStartElement( XmlConstants.SchemaPrefix,
969                                                  XmlConstants.Sequence,
970                                                  XmlConstants.SchemaNamespace );
971                         foreach( DataColumn col in elements )
972                         {
973                                 //<xs:element name=ColumnName type=MappedType Ordinal=index>
974                                 writer.WriteStartElement( XmlConstants.SchemaPrefix,
975                                                  XmlConstants.Element,
976                                                  XmlConstants.SchemaNamespace );
977                                 
978                                 writer.WriteAttributeString( XmlConstants.Name, col.ColumnName );
979                                 
980                                 if (col.ColumnName != col.Caption && col.Caption != string.Empty)
981                                         writer.WriteAttributeString( XmlConstants.MsdataPrefix, XmlConstants.Caption, 
982                                                                      XmlConstants.MsdataNamespace, col.Caption); 
983
984                                 if (col.DefaultValue.ToString () != string.Empty)
985                                         writer.WriteAttributeString( XmlConstants.Default, col.DefaultValue.ToString ());
986
987                                 writer.WriteAttributeString( XmlConstants.Type, MapType( col.DataType ) );
988
989                                 if( col.AllowDBNull )
990                                 {
991                                         writer.WriteAttributeString( XmlConstants.MinOccurs, "0" );
992                                 }
993
994                                 //writer.WriteAttributeString( XmlConstants.MsdataPrefix,
995                                 //                            XmlConstants.Ordinal,
996                                 //                            XmlConstants.MsdataNamespace,
997                                 //                            col.Ordinal.ToString() );
998
999                                 // Write SimpleType if column have MaxLength
1000                                 if (col.MaxLength > -1) {
1001
1002                                         WriteTableSimpleType (writer, col);
1003                                 }
1004                                 
1005
1006                                 //</xs:element>
1007                                 writer.WriteEndElement();
1008                         }
1009                         //</xs:sequence>
1010                         writer.WriteEndElement();
1011                                 
1012                         }
1013                         //Then a list of attributes
1014                         foreach( DataColumn col in atts )
1015                         {
1016                                 //<xs:attribute name=col.ColumnName form="unqualified" type=MappedType/>
1017                                 writer.WriteStartElement( XmlConstants.SchemaPrefix,
1018                                                  XmlConstants.Attribute,
1019                                                  XmlConstants.SchemaNamespace );
1020                                 
1021                                 writer.WriteAttributeString( XmlConstants.Name, col.ColumnName );
1022                                 writer.WriteAttributeString( XmlConstants.Form, XmlConstants.Unqualified );
1023                                 writer.WriteAttributeString( XmlConstants.Type, MapType( col.DataType ) );
1024                                 
1025                                 writer.WriteEndElement();
1026                         }
1027                         
1028                         //</xs:complexType>
1029                         writer.WriteEndElement();
1030                         
1031                         //</xs:element>
1032                         writer.WriteEndElement();
1033                 }
1034
1035                 private void WriteTableSimpleType (XmlWriter writer, DataColumn col)
1036                 {
1037                         // SimpleType
1038                         writer.WriteStartElement( XmlConstants.SchemaPrefix, XmlConstants.SimpleType, 
1039                                                   XmlConstants.SchemaNamespace);
1040                         
1041                         // Restriction
1042                         writer.WriteStartElement( XmlConstants.SchemaPrefix, XmlConstants.Restriction, 
1043                                                   XmlConstants.SchemaNamespace);
1044                         
1045                         writer.WriteAttributeString( XmlConstants.Base, MapType( col.DataType ) );
1046                         
1047                         // MaxValue
1048                         writer.WriteStartElement( XmlConstants.SchemaPrefix, XmlConstants.MaxLength, 
1049                                                   XmlConstants.SchemaNamespace);
1050                         writer.WriteAttributeString( XmlConstants.Value, col.MaxLength.ToString ());
1051                         
1052                         
1053                         writer.WriteEndElement();
1054                         
1055                         writer.WriteEndElement();
1056                         
1057                         writer.WriteEndElement();
1058                 }
1059
1060                 ///<summary>
1061                 /// Helper function to split columns into attributes elements and simple
1062                 /// content
1063                 /// </summary>
1064                 private void SplitColumns(      DataTable table,
1065                                                                         out ArrayList atts,
1066                                                                         out ArrayList elements,
1067                                                                         out DataColumn simple)
1068                 {
1069                         //The columns can be attributes, hidden, elements, or simple content
1070                         //There can be 0-1 simple content cols or 0-* elements
1071                         atts = new System.Collections.ArrayList();
1072                         elements = new System.Collections.ArrayList();
1073                         simple = null;
1074                         
1075                         //Sort out the columns
1076                         foreach( DataColumn col in table.Columns )
1077                         {
1078                                 switch( col.ColumnMapping )
1079                                 {
1080                                         case MappingType.Attribute:
1081                                                 atts.Add( col );
1082                                                 break;
1083                                         case MappingType.Element:
1084                                                 elements.Add( col );
1085                                                 break;
1086                                         case MappingType.SimpleContent:
1087                                                 if( simple != null )
1088                                                 {
1089                                                         throw new System.InvalidOperationException( "There may only be one simple content element" );
1090                                                 }
1091                                                 simple = col;
1092                                                 break;
1093                                         default:
1094                                                 //ignore Hidden elements
1095                                                 break;
1096                                 }
1097                         }
1098                 }
1099                 
1100                 [MonoTODO]
1101                 private string MapType( Type type )
1102                 {
1103                         string Result = "xs:string";
1104
1105                         // TODO: More types to map?
1106
1107                         if (typeof (string) == type)
1108                                 Result = "xs:string";
1109                         else if (typeof (short) == type)
1110                                 Result = "xs:short";
1111                         else if (typeof (int) == type)
1112                                 Result = "xs:int";
1113                         else if (typeof (long) == type)
1114                                 Result = "xs:long";
1115                         else if (typeof (bool) == type)
1116                                 Result = "xs:boolean";
1117                         else if (typeof (byte) == type)
1118                                 Result = "xs:unsignedByte";
1119                         else if (typeof (char) == type)
1120                                 Result = "xs:char";
1121                         else if (typeof (DateTime) == type)
1122                                 Result = "xs:dateTime";
1123                         else if (typeof (decimal) == type)
1124                                 Result = "xs:decimal";
1125                         else if (typeof (double) == type)
1126                                 Result = "xs:double";
1127                         else if (typeof (sbyte) == type)
1128                                 Result = "xs:sbyte";
1129                         else if (typeof (Single) == type)
1130                                 Result = "xs:float";
1131                         else if (typeof (TimeSpan) == type)
1132                                 Result = "xs:duration";
1133                         else if (typeof (ushort) == type)
1134                                 Result = "xs:usignedShort";
1135                         else if (typeof (uint) == type)
1136                                 Result = "xs:unsignedInt";
1137                         else if (typeof (ulong) == type)
1138                                 Result = "xs:unsignedLong";
1139                 
1140                         return Result;
1141                 }
1142                 
1143                 #endregion //Private Xml Serialisation
1144         }
1145 }