importing messaging-2008 branch to trunk [continued]
[mono.git] / mcs / class / System.Data / System.Data / XmlSchemaWriter.cs
1 //
2 // XmlSchemaWriter.cs - DataSet.WriteXmlSchema() support
3 //
4 // Author:
5 //
6 //      Atsushi Enomoto  <atsushi@ximian.com>
7 //
8 // Original WriteXml/WriteXmlSchema authors are:
9 //      Ville Palo, Alan Tam, Lluis Sanchez and Eran Domb.
10 //
11
12 //
13 // Copyright (C) 2004-05 Novell, Inc (http://www.novell.com)
14 //
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
22 // 
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
25 // 
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 //
34
35 using System;
36 using System.Collections;
37 using System.Collections.Specialized;
38 using System.Globalization;
39 using System.Data;
40 using System.Xml;
41
42 namespace System.Data
43 {
44         internal class XmlSchemaWriter
45         {
46                 const string xmlnsxs = System.Xml.Schema.XmlSchema.Namespace;
47
48                 public static void WriteXmlSchema (DataSet dataset,
49                         XmlWriter writer)
50                 {
51                         WriteXmlSchema (dataset, writer, dataset.Tables,
52                                 dataset.Relations);
53                 }
54
55                 public static void WriteXmlSchema (DataSet dataset,
56                         XmlWriter writer, DataTableCollection tables,
57                         DataRelationCollection relations)
58                 {
59                         new XmlSchemaWriter (dataset, writer,
60                                 tables, relations).WriteSchema ();
61                 }
62
63                 internal static void WriteXmlSchema (XmlWriter writer, DataTable[] tables,
64                                                      DataRelation[] relations,
65                                                      string mainDataTable,
66                                                      string dataSetName,
67                                                      CultureInfo locale)
68                 {
69                         new XmlSchemaWriter (writer, tables, relations, mainDataTable, dataSetName, locale).WriteSchema ();
70                 }
71
72                 public XmlSchemaWriter (DataSet dataset,
73                         XmlWriter writer, DataTableCollection tables,
74                         DataRelationCollection relations)
75                 {
76                         dataSetName = dataset.DataSetName;
77                         dataSetNamespace = dataset.Namespace;
78 #if NET_2_0
79                         dataSetLocale = dataset.LocaleSpecified ? dataset.Locale : null;
80 #else
81                         dataSetLocale = dataset.Locale;
82 #endif
83                         dataSetProperties = dataset.ExtendedProperties;
84                         w = writer;
85                         if (tables != null) {
86                                 this.tables = new DataTable[tables.Count];
87                                 for(int i=0;i<tables.Count;i++) this.tables[i] = tables[i];
88                         }
89                         if (relations != null) {
90                                 this.relations = new DataRelation[relations.Count];
91                                 for(int i=0;i<relations.Count;i++) this.relations[i] = relations[i];
92                         }
93                 }
94
95                 public XmlSchemaWriter (XmlWriter writer,
96                         DataTable[] tables,
97                         DataRelation[] relations,
98                         string mainDataTable,
99                         string dataSetName,
100                         CultureInfo locale)
101                 {
102                         w = writer;
103                         this.tables = tables;
104                         this.relations = relations;
105                         this.mainDataTable = mainDataTable;
106                         this.dataSetName = dataSetName;
107                         this.dataSetLocale = locale;
108                         this.dataSetProperties = new PropertyCollection();
109                         if (tables[0].DataSet != null) {
110                                 dataSetNamespace = tables[0].DataSet.Namespace;
111 #if !NET_2_0
112                                 dataSetLocale = tables[0].DataSet.Locale;
113 #endif
114                         } else {
115                                 dataSetNamespace = tables[0].Namespace;
116 #if !NET_2_0
117                                 dataSetLocale = tables[0].Locale;
118 #endif
119                         }
120                 }
121
122                 XmlWriter w;
123                 DataTable[] tables;
124                 DataRelation[] relations;
125                 string mainDataTable;
126                 string dataSetName;
127                 string dataSetNamespace;
128                 PropertyCollection dataSetProperties;
129                 CultureInfo dataSetLocale;
130
131                 ArrayList globalTypeTables = new ArrayList ();
132                 Hashtable additionalNamespaces = new Hashtable ();
133
134                 ArrayList annotation = new ArrayList ();
135
136                 public string ConstraintPrefix {
137                         get { return dataSetNamespace != String.Empty ? XmlConstants.TnsPrefix + ':' : String.Empty; }
138                 }
139
140                 // the whole DataSet
141
142                 public void WriteSchema ()
143                 {
144                         ListDictionary names = new ListDictionary ();
145                         ListDictionary includes = new ListDictionary ();
146
147                         // Add namespaces used in DataSet components (tables, columns, ...)
148                         foreach (DataTable dt in tables) {
149                                 foreach (DataColumn col in dt.Columns)
150                                         CheckNamespace (col.Prefix, col.Namespace, names, includes);
151                                 CheckNamespace (dt.Prefix, dt.Namespace, names, includes);
152                         }
153
154                         w.WriteStartElement ("xs", "schema", xmlnsxs);
155                         w.WriteAttributeString ("id", XmlHelper.Encode (dataSetName));
156
157                         if (dataSetNamespace != String.Empty) {
158                                 w.WriteAttributeString ("targetNamespace",
159                                         dataSetNamespace);
160                                 w.WriteAttributeString (
161                                         "xmlns",
162                                         XmlConstants.TnsPrefix,
163                                         XmlConstants.XmlnsNS,
164                                         dataSetNamespace);
165                         }
166                         w.WriteAttributeString ("xmlns", dataSetNamespace);
167
168                         w.WriteAttributeString ("xmlns", "xs",
169                                 XmlConstants.XmlnsNS, xmlnsxs);
170                         w.WriteAttributeString ("xmlns",
171                                 XmlConstants.MsdataPrefix,
172                                 XmlConstants.XmlnsNS,
173                                 XmlConstants.MsdataNamespace);
174
175                         if (CheckExtendedPropertyExists (tables, relations))
176                                 w.WriteAttributeString ("xmlns",
177                                         XmlConstants.MspropPrefix,
178                                         XmlConstants.XmlnsNS,
179                                         XmlConstants.MspropNamespace);
180
181                         if (dataSetNamespace != String.Empty) {
182                                 w.WriteAttributeString ("attributeFormDefault", "qualified");
183                                 w.WriteAttributeString ("elementFormDefault", "qualified");
184                         }
185
186                         foreach (string prefix in names.Keys)
187                                 w.WriteAttributeString ("xmlns", prefix,
188                                         XmlConstants.XmlnsNS,
189                                         names [prefix] as string);
190
191                         if (includes.Count > 0)
192                                 w.WriteComment ("ATTENTION: This schema contains references to other imported schemas");
193                         foreach (string ns in includes.Keys) {
194                                 w.WriteStartElement ("xs", "import", xmlnsxs);
195                                 w.WriteAttributeString ("namespace", ns);
196                                 w.WriteAttributeString ("schemaLocation", includes [ns] as string);
197                                 w.WriteEndElement ();
198                         }
199                         
200                         foreach (DataTable table in tables) {
201                                 bool isTopLevel = true;
202                                 foreach (DataRelation rel in table.ParentRelations) {
203                                         if (rel.Nested) {
204                                                 isTopLevel = false;
205                                                 break;
206                                         }
207                                 }
208                                 // LAMESPEC: You have a nested relationship but only one table, 
209                                 // write table element first
210                                 if (!isTopLevel && tables.Length < 2)
211                                                 WriteTableElement (table);
212                         }
213         
214                         WriteDataSetElement ();
215
216                         w.WriteEndElement (); // xs:schema
217
218                         w.Flush ();
219                 }
220
221                 // FIXME: actually there are some cases that this method(ology)
222                 // does not apply.
223                 private void WriteDataSetElement ()
224                 {
225                         w.WriteStartElement ("xs", "element", xmlnsxs);
226                         w.WriteAttributeString ("name", XmlHelper.Encode (dataSetName));
227                         w.WriteAttributeString (XmlConstants.MsdataPrefix,
228                                 "IsDataSet", XmlConstants.MsdataNamespace,
229                                 "true");
230
231 #if NET_2_0
232                         bool useCurrentLocale = (dataSetLocale == null);
233
234                         if (!useCurrentLocale)
235 #endif
236                         {
237                                 w.WriteAttributeString (
238                                         XmlConstants.MsdataPrefix,
239                                         "Locale",
240                                         XmlConstants.MsdataNamespace,
241                                         dataSetLocale.Name);
242                         }
243
244 #if NET_2_0
245                         if(mainDataTable != null && mainDataTable != "")
246                                 w.WriteAttributeString (
247                                         XmlConstants.MsdataPrefix,
248                                         "MainDataTable",
249                                         XmlConstants.MsdataNamespace,
250                                         mainDataTable);
251
252                         if (useCurrentLocale) {
253                                 w.WriteAttributeString (
254                                         XmlConstants.MsdataPrefix,
255                                         "UseCurrentLocale",
256                                         XmlConstants.MsdataNamespace,
257                                         "true");
258                         }
259 #endif
260
261                         AddExtendedPropertyAttributes (dataSetProperties);
262
263                         w.WriteStartElement ("xs", "complexType", xmlnsxs);
264                         w.WriteStartElement ("xs", "choice", xmlnsxs);
265                         w.WriteAttributeString ("minOccurs", "0");
266                         w.WriteAttributeString ("maxOccurs", "unbounded");
267
268                         foreach (DataTable table in tables) {
269                                 bool isTopLevel = true;
270                                 foreach (DataRelation rel in table.ParentRelations) {
271                                         if (rel.Nested) {
272                                                 isTopLevel = false;
273                                                 break;
274                                         }
275                                 }
276
277                                 // If there is a relation but only one table, could be that
278                                 // we have to add a ref attribute
279                                 bool addref = false;
280                                 if (!isTopLevel && tables.Length < 2) {
281                                         isTopLevel = true;
282                                         addref = true;
283                                 }
284                                 
285                                 if (isTopLevel) {
286                                         if (dataSetNamespace != table.Namespace ||
287                                             addref) {
288                                                 // <xs:element ref="X:y" />
289                                                 w.WriteStartElement ("xs",
290                                                         "element",
291                                                         xmlnsxs);
292                                                 w.WriteStartAttribute ("ref", String.Empty);
293                                                 w.WriteQualifiedName (XmlHelper.Encode (table.TableName), table.Namespace);
294                                                 w.WriteEndAttribute ();
295                                                 w.WriteEndElement ();
296                                         }
297                                         else
298                                                 WriteTableElement (table);
299                                 }
300                         }
301
302                         w.WriteEndElement (); // choice
303                         w.WriteEndElement (); // complexType
304
305                         WriteConstraints (); // DataSet constraints
306
307                         w.WriteEndElement (); // element
308
309                         if (annotation.Count > 0) {
310                                 w.WriteStartElement ("xs", "annotation", xmlnsxs);
311                                 w.WriteStartElement ("xs", "appinfo", xmlnsxs);
312
313                                 foreach (object o in annotation) {
314                                         if (!(o is DataRelation))
315                                                 continue;
316                                         WriteDataRelationAnnotation ((DataRelation)o);  
317                                 }
318                                 w.WriteEndElement ();
319                                 w.WriteEndElement ();
320                         }
321                 }
322
323                 private void WriteDataRelationAnnotation (DataRelation rel) 
324                 {
325                         String colnames = String.Empty;
326                         w.WriteStartElement (XmlConstants.MsdataPrefix, "Relationship",
327                                  XmlConstants.MsdataNamespace);
328
329                         w.WriteAttributeString ("name", XmlHelper.Encode (rel.RelationName));
330
331                         w.WriteAttributeString (
332                                         XmlConstants.MsdataPrefix,
333                                         "parent",
334                                         XmlConstants.MsdataNamespace,
335                                         XmlHelper.Encode (rel.ParentTable.TableName));
336
337                         w.WriteAttributeString (
338                                         XmlConstants.MsdataPrefix,
339                                         "child",
340                                         XmlConstants.MsdataNamespace,
341                                         XmlHelper.Encode (rel.ChildTable.TableName));
342
343                         colnames = String.Empty;
344                         foreach (DataColumn col in rel.ParentColumns)
345                                 colnames += XmlHelper.Encode (col.ColumnName) + " ";
346                         w.WriteAttributeString (
347                                         XmlConstants.MsdataPrefix,
348                                         "parentkey",
349                                         XmlConstants.MsdataNamespace,
350                                         colnames.TrimEnd ());
351
352                         colnames = String.Empty;
353                         foreach (DataColumn col in rel.ChildColumns)
354                                 colnames += XmlHelper.Encode (col.ColumnName) + " ";
355                         w.WriteAttributeString (
356                                         XmlConstants.MsdataPrefix,
357                                         "childkey",
358                                         XmlConstants.MsdataNamespace,
359                                         colnames.TrimEnd ());
360
361                         w.WriteEndElement ();
362                 }
363
364                 private void WriteConstraints ()
365                 {
366                         ArrayList names = new ArrayList ();
367
368                         // add all unique constraints.
369                         foreach (DataTable table in tables) {
370                                 foreach (Constraint c in table.Constraints) {
371                                         UniqueConstraint u =
372                                                 c as UniqueConstraint;
373                                         if (u != null) {
374                                                 AddUniqueConstraints (u, names);
375                                                 continue;
376                                         }
377
378                                         ForeignKeyConstraint fk = c as ForeignKeyConstraint;
379                                         bool haveConstraint = false;
380                                         if (relations != null) {
381                                                 foreach (DataRelation r in relations)
382                                                         if (r.RelationName == fk.ConstraintName)
383                                                                 haveConstraint = true;
384                                         }
385                                         if (tables.Length > 1 && fk != null && !haveConstraint) {
386                                                 DataRelation rel = new DataRelation (fk.ConstraintName,
387                                                                                 fk.RelatedColumns, fk.Columns);
388                                                 AddForeignKeys (rel, names, true);
389                                                 continue;
390                                         }
391                                 }
392                         }
393
394                         // Add all foriegn key constraints.
395                         if (relations != null)
396                                 foreach (DataRelation rel in relations) {
397                                         if (rel.ParentKeyConstraint == null || rel.ChildKeyConstraint == null) {
398                                                 annotation.Add (rel);
399                                                 continue;
400                                         }
401                                         AddForeignKeys (rel, names,false);
402                                 }
403                 }
404
405                 // Add unique constaraints to the schema.
406                 // return hashtable with the names of all XmlSchemaUnique elements we created.
407                 private void AddUniqueConstraints (UniqueConstraint uniq,
408                         ArrayList names)
409                 {
410                         // if column of the constraint is hidden do not write the constraint.
411                         foreach (DataColumn column in uniq.Columns)
412                                 if (column.ColumnMapping == MappingType.Hidden)
413                                         return; // do nothing
414
415                         w.WriteStartElement ("xs", "unique", xmlnsxs);
416                         // if constaraint name do not exist in the hashtable we can use it.
417                         string name;
418                         if (!names.Contains (uniq.ConstraintName)) {
419                                 name = XmlHelper.Encode (uniq.ConstraintName);
420                                 w.WriteAttributeString ("name", name);
421                         }
422                         // otherwise generate new constraint name for the
423                         // XmlSchemaUnique element.
424                         else {
425                                 name = XmlHelper.Encode (uniq.Table.TableName) + "_" + XmlHelper.Encode (uniq.ConstraintName);
426                                 w.WriteAttributeString ("name", name);
427                                 w.WriteAttributeString (
428                                         XmlConstants.MsdataPrefix,
429                                         XmlConstants.ConstraintName,
430                                         XmlConstants.MsdataNamespace,
431                                         XmlHelper.Encode (uniq.ConstraintName));
432                         }
433                         names.Add (name);
434
435                         if (uniq.IsPrimaryKey) {
436                                 w.WriteAttributeString (
437                                         XmlConstants.MsdataPrefix,
438                                         XmlConstants.PrimaryKey,
439                                         XmlConstants.MsdataNamespace,
440                                         "true");
441                         }
442
443                         AddExtendedPropertyAttributes (uniq.ExtendedProperties);
444
445                         w.WriteStartElement ("xs", "selector",
446                                 xmlnsxs);
447                         w.WriteAttributeString ("xpath", ".//" +
448                                 ConstraintPrefix + XmlHelper.Encode (uniq.Table.TableName));
449                         w.WriteEndElement (); // selector
450                         foreach (DataColumn c in uniq.Columns) {
451                                 w.WriteStartElement ("xs", "field",
452                                         xmlnsxs);
453                                 w.WriteStartAttribute ("xpath", String.Empty);
454                                 if (c.ColumnMapping == MappingType.Attribute)
455                                         w.WriteString ("@");
456                                 w.WriteString (ConstraintPrefix);
457                                 w.WriteString (XmlHelper.Encode (c.ColumnName));
458                                 w.WriteEndAttribute (); // xpath
459                                 w.WriteEndElement (); // field
460                         }
461
462                         w.WriteEndElement (); // unique
463                 }
464
465                 // Add the foriegn keys to the schema.
466                 private void AddForeignKeys (DataRelation rel, ArrayList names, bool isConstraintOnly)
467                 {
468                         // Do nothing if it contains hidden relation
469                         foreach (DataColumn col in rel.ParentColumns)
470                                 if (col.ColumnMapping == MappingType.Hidden)
471                                         return;
472                         foreach (DataColumn col in rel.ChildColumns)
473                                 if (col.ColumnMapping == MappingType.Hidden)
474                                         return;
475
476                         w.WriteStartElement ("xs", "keyref", xmlnsxs);
477                         w.WriteAttributeString ("name", XmlHelper.Encode (rel.RelationName));
478
479                         //ForeignKeyConstraint fkConst = rel.ChildKeyConstraint;
480                         UniqueConstraint uqConst = null; 
481
482                         if (isConstraintOnly) {
483                                 foreach (Constraint c in rel.ParentTable.Constraints) {
484                                         uqConst = c as UniqueConstraint;
485                                         if (uqConst == null)
486                                                 continue;
487                                         if (uqConst.Columns == rel.ParentColumns)
488                                                 break;
489                                 }
490                         } 
491                         else
492                                 uqConst = rel.ParentKeyConstraint;
493
494                         string concatName = XmlHelper.Encode (rel.ParentTable.TableName) + "_" + XmlHelper.Encode (uqConst.ConstraintName);
495                         // first try to find the concatenated name. If we didn't find it - use constraint name.
496                         if (names.Contains (concatName)) {
497                                 w.WriteStartAttribute ("refer", String.Empty);
498                                 w.WriteQualifiedName (concatName, dataSetNamespace);
499                                 w.WriteEndAttribute ();
500                         }
501                         else {
502                                 w.WriteStartAttribute ("refer", String.Empty);
503                                 w.WriteQualifiedName (XmlHelper.Encode (uqConst.ConstraintName), dataSetNamespace);
504                                 w.WriteEndAttribute ();
505                         }
506
507                         if (isConstraintOnly)
508                                 w.WriteAttributeString ( XmlConstants.MsdataPrefix,
509                                         XmlConstants.ConstraintOnly,
510                                         XmlConstants.MsdataNamespace,
511                                         "true");
512                         else if (rel.Nested)
513                                 w.WriteAttributeString (
514                                         XmlConstants.MsdataPrefix,
515                                         XmlConstants.IsNested,
516                                         XmlConstants.MsdataNamespace,
517                                         "true");
518
519                         AddExtendedPropertyAttributes (uqConst.ExtendedProperties);
520
521                         w.WriteStartElement ("xs", "selector", xmlnsxs);
522                         w.WriteAttributeString ("xpath", ".//" + 
523                                 ConstraintPrefix + XmlHelper.Encode (rel.ChildTable.TableName));
524                         w.WriteEndElement ();
525
526                         foreach (DataColumn c in rel.ChildColumns) {
527                                 w.WriteStartElement ("xs", "field",
528                                         xmlnsxs);
529                                 w.WriteStartAttribute ("xpath", String.Empty);
530                                 if (c.ColumnMapping == MappingType.Attribute)
531                                         w.WriteString ("@");
532                                 w.WriteString (ConstraintPrefix);
533                                 w.WriteString (XmlHelper.Encode (c.ColumnName));
534                                 w.WriteEndAttribute ();
535                                 w.WriteEndElement (); // field
536                         }
537
538                         w.WriteEndElement (); // keyref
539                 }
540
541                 // ExtendedProperties
542
543                 private bool CheckExtendedPropertyExists (
544                         DataTable[] tables,
545                         DataRelation[] relations)
546                 {
547                         if (dataSetProperties.Count > 0)
548                                 return true;
549                         foreach (DataTable dt in tables) {
550                                 if (dt.ExtendedProperties.Count > 0)
551                                         return true;
552                                 foreach (DataColumn col in dt.Columns)
553                                         if (col.ExtendedProperties.Count > 0)
554                                                 return true;
555                                 foreach (Constraint c in dt.Constraints)
556                                         if (c.ExtendedProperties.Count > 0)
557                                                 return true;
558                         }
559                         if (relations == null)
560                                 return false;
561                         foreach (DataRelation rel in relations)
562                                 if (rel.ExtendedProperties.Count > 0)
563                                         return true;
564                         return false;
565                 }
566
567                 private void AddExtendedPropertyAttributes (PropertyCollection props)
568                 {
569                         // add extended properties to xs:element
570                         foreach (DictionaryEntry de in props) {
571                                 w.WriteStartAttribute (
572                                         XmlConstants.MspropPrefix,
573                                         XmlConvert.EncodeName (de.Key.ToString ()),
574                                         XmlConstants.MspropNamespace);
575                                 if (de.Value != null)
576                                         w.WriteString (
577                                                 DataSet.WriteObjectXml (de.Value));
578                                 w.WriteEndAttribute ();
579                         }
580                 }
581
582                 // Table
583
584                 private void WriteTableElement (DataTable table)
585                 {
586                         w.WriteStartElement ("xs", "element", xmlnsxs);
587                         w.WriteAttributeString ("name", XmlHelper.Encode (table.TableName));
588
589                         AddExtendedPropertyAttributes (table.ExtendedProperties);
590
591                         WriteTableType (table);
592
593                         w.WriteEndElement ();
594                 }
595
596                 private void WriteTableType (DataTable table)
597                 {
598                         ArrayList elements;
599                         ArrayList atts;
600                         DataColumn simple;
601
602                         DataSet.SplitColumns (table, out atts, out elements, out simple);
603
604                         w.WriteStartElement ("xs", "complexType", xmlnsxs);
605
606                         if (simple != null) {
607                                 w.WriteStartElement ("xs", "simpleContent", xmlnsxs);
608                                 // add column name attribute
609                                 w.WriteAttributeString (
610                                         XmlConstants.MsdataPrefix,
611                                         XmlConstants.ColumnName,
612                                         XmlConstants.MsdataNamespace,
613                                         XmlHelper.Encode (simple.ColumnName));
614
615                                 // add ordinal attribute
616                                 w.WriteAttributeString (
617                                         XmlConstants.MsdataPrefix,
618                                         XmlConstants.Ordinal,
619                                         XmlConstants.MsdataNamespace,
620                                         XmlConvert.ToString (simple.Ordinal));
621
622                                 // add extension
623                                 w.WriteStartElement ("xs", "extension",
624                                         xmlnsxs);
625                                 w.WriteStartAttribute ("base", String.Empty);
626                                 WriteQName (MapType (simple.DataType));
627                                 w.WriteEndAttribute ();
628
629                                 WriteTableAttributes (atts);
630
631                                 w.WriteEndElement ();
632                         } else {
633                                 WriteTableAttributes (atts);
634                                 bool isNested = false;
635                                 foreach (DataRelation rel in table.ParentRelations) {
636                                         if (rel.Nested) {
637                                                 isNested = true;
638                                                 break;
639                                         }
640                                 }
641                                 
642                                 // When we have a nested relationship and only one table,
643                                 // could be that we have a reference attribute
644                                 if (elements.Count > 0 || (isNested && tables.Length < 2)) {
645                                         w.WriteStartElement ("xs", "sequence",
646                                                 xmlnsxs);
647
648                                         foreach (DataColumn col in elements)
649                                                 WriteTableTypeParticles (col);
650
651                                         foreach (DataRelation rel in table.ChildRelations)
652                                                 if (rel.Nested)
653                                                         WriteChildRelations (rel);
654                                         w.WriteEndElement ();
655                                 }
656                         }
657
658                         w.WriteFullEndElement (); // complexType
659                 }
660
661                 private void WriteTableTypeParticles (DataColumn col)
662                 {
663                         w.WriteStartElement ("xs", "element", xmlnsxs);
664                         w.WriteAttributeString ("name", XmlHelper.Encode (col.ColumnName));
665
666                         if (col.ColumnName != col.Caption && col.Caption != String.Empty)
667                                 w.WriteAttributeString (
668                                         XmlConstants.MsdataPrefix,
669                                         XmlConstants.Caption, 
670                                         XmlConstants.MsdataNamespace,
671                                         col.Caption);
672
673                         if (col.AutoIncrement == true)
674                                 w.WriteAttributeString (
675                                         XmlConstants.MsdataPrefix,
676                                         XmlConstants.AutoIncrement,
677                                         XmlConstants.MsdataNamespace,
678                                         "true");
679
680                         if (col.AutoIncrementSeed != 0) {
681                                 w.WriteAttributeString (
682
683                                         XmlConstants.MsdataPrefix,
684                                         XmlConstants.AutoIncrementSeed,
685                                         XmlConstants.MsdataNamespace,
686                                         XmlConvert.ToString (col.AutoIncrementSeed));
687                         }
688
689                         if (col.AutoIncrementStep != 1) {
690                                 w.WriteAttributeString (
691
692                                         XmlConstants.MsdataPrefix,
693                                         XmlConstants.AutoIncrementStep,
694                                         XmlConstants.MsdataNamespace,
695                                         XmlConvert.ToString (col.AutoIncrementStep));
696                         }
697
698                         if (!DataColumn.GetDefaultValueForType (col.DataType).Equals (col.DefaultValue))
699                                 w.WriteAttributeString ("default",
700                                         DataSet.WriteObjectXml (col.DefaultValue));
701
702                         if (col.ReadOnly)
703                                 w.WriteAttributeString (
704                                         XmlConstants.MsdataPrefix,
705                                         XmlConstants.ReadOnly,
706                                         XmlConstants.MsdataNamespace,
707                                         "true");
708
709                         XmlQualifiedName typeQName = null;
710                         if (col.MaxLength < 0) {
711                                 w.WriteStartAttribute ("type", String.Empty);
712                                 typeQName = MapType (col.DataType);
713                                 WriteQName (typeQName);
714                                 w.WriteEndAttribute ();
715                         }
716
717                         if (typeQName == XmlConstants.QnString
718                                 && col.DataType != typeof (string)
719                                 && col.DataType != typeof (char)) {
720                                 w.WriteStartAttribute (
721                                         XmlConstants.MsdataPrefix,
722                                         XmlConstants.DataType,
723                                         XmlConstants.MsdataNamespace);
724                                 string runtimeName = col.DataType.AssemblyQualifiedName;
725                                 w.WriteString (runtimeName);
726                                 w.WriteEndAttribute ();
727                         }
728
729                         if (col.AllowDBNull)
730                                 w.WriteAttributeString ("minOccurs", "0");
731
732                         //writer.WriteAttributeString (XmlConstants.MsdataPrefix, 
733                         //                            XmlConstants.Ordinal, 
734                         //                            XmlConstants.MsdataNamespace, 
735                         //                            col.Ordinal.ToString ());
736
737                         // Write SimpleType if column have MaxLength
738                         if (col.MaxLength > -1)
739                                 WriteSimpleType (col);
740
741                         AddExtendedPropertyAttributes (col.ExtendedProperties);
742
743                         w.WriteEndElement (); // sequence
744                 }
745
746                 private void WriteChildRelations (DataRelation rel)
747                 {
748                         // If there is a parent/child relation and only one table,
749                         // it would just be a ref element.
750                         if (rel.ChildTable.Namespace != dataSetNamespace ||
751                             tables.Length < 2) {
752                                 w.WriteStartElement ("xs", "element", xmlnsxs);
753                                 w.WriteStartAttribute ("ref", String.Empty);
754                                 w.WriteQualifiedName (
755                                         XmlHelper.Encode (rel.ChildTable.TableName),
756                                         rel.ChildTable.Namespace);
757                                 w.WriteEndAttribute ();
758                         } else {
759                                 w.WriteStartElement ("xs", "element", xmlnsxs);
760                                 w.WriteStartAttribute ("name", String.Empty);
761                                 w.WriteQualifiedName (
762                                         XmlHelper.Encode (rel.ChildTable.TableName),
763                                         rel.ChildTable.Namespace);
764                                 w.WriteEndAttribute ();
765                                 w.WriteAttributeString ("minOccurs", "0");
766                                 w.WriteAttributeString ("maxOccurs", "unbounded");
767
768                                 globalTypeTables.Add (rel.ChildTable);
769                         }
770                         
771                         // Iff there is a genuine ChildTable and not just a ref, call WriteTableType
772                         if (tables.Length > 1)
773                                 WriteTableType (rel.ChildTable);
774                         w.WriteEndElement ();
775                 }
776
777                 private void WriteTableAttributes (ArrayList atts)
778                 {
779                         //Then a list of attributes
780                         foreach (DataColumn col in atts) {
781                                 w.WriteStartElement ("xs", "attribute", xmlnsxs);
782
783                                 string name = XmlHelper.Encode (col.ColumnName);
784                                 if (col.Namespace != String.Empty) {
785                                         w.WriteAttributeString ("form", "qualified");
786                                         string prefix = col.Prefix == String.Empty ? "app" + additionalNamespaces.Count : col.Prefix;
787                                         name = prefix + ":" + name;
788                                         // FIXME: Handle prefix mapping correctly.
789                                         additionalNamespaces [prefix] = col.Namespace;
790                                 }
791                                 w.WriteAttributeString ("name", name);
792
793                                 AddExtendedPropertyAttributes (
794                                         col.ExtendedProperties);
795
796                                 if (col.ReadOnly)
797                                         w.WriteAttributeString (
798                                                 XmlConstants.MsdataPrefix,
799                                                 XmlConstants.ReadOnly,
800                                                 XmlConstants.MsdataNamespace,
801                                                 "true");
802
803                                 if (col.MaxLength < 0) {
804                                         // otherwise simpleType is written later
805                                         w.WriteStartAttribute ("type", String.Empty);
806                                         WriteQName (MapType (col.DataType));
807                                         w.WriteEndAttribute ();
808                                 }
809
810                                 if (!col.AllowDBNull)
811                                         w.WriteAttributeString ("use", "required");
812                                 if (col.DefaultValue != DataColumn.GetDefaultValueForType (col.DataType))
813                                         w.WriteAttributeString ("default",
814                                                 DataSet.WriteObjectXml (col.DefaultValue));
815
816                                 if (col.MaxLength > -1)
817                                         WriteSimpleType (col);
818
819                                 w.WriteEndElement (); // attribute
820                         }
821                 }
822
823                 private void WriteSimpleType (DataColumn col)
824                 {
825                         w.WriteStartElement ("xs", "simpleType", xmlnsxs);
826                         w.WriteStartElement ("xs", "restriction", xmlnsxs);
827                         w.WriteStartAttribute ("base", String.Empty);
828                         WriteQName (MapType (col.DataType));
829                         w.WriteEndAttribute ();
830
831                         w.WriteStartElement ("xs", "maxLength", xmlnsxs);
832                         w.WriteAttributeString ("value",
833                                 XmlConvert.ToString (col.MaxLength));
834                         w.WriteEndElement (); // maxLength
835                         w.WriteEndElement (); // restriction
836                         w.WriteEndElement (); // simpleType
837                 }
838
839                 private void WriteQName (XmlQualifiedName name)
840                 {
841                         w.WriteQualifiedName (name.Name, name.Namespace);
842                 }
843
844                 private void CheckNamespace (string prefix, string ns, ListDictionary names, ListDictionary includes)
845                 {
846                         if (ns == String.Empty)
847                                 return;
848                         if (dataSetNamespace != ns) {
849                                 if ((string)names [prefix] != ns) {
850                                         for (int i = 1; i < int.MaxValue; i++) {
851                                                 string p = "app" + i;
852                                                 if (names [p] == null) {
853                                                         names.Add (p, ns);
854                                                         HandleExternalNamespace (p, ns, includes);
855                                                         break;
856                                                 }
857                                         }
858                                 }
859                         }
860                 }
861
862                 private void HandleExternalNamespace (string prefix, string ns, ListDictionary includes)
863                 {
864                         if (includes.Contains (ns))
865                                 return; // nothing to do
866                         includes.Add (ns, "_" + prefix + ".xsd");
867                 }
868
869                 private /*static*/ XmlQualifiedName MapType (Type type)
870                 {
871                         switch (Type.GetTypeCode (type)) {
872                                 case TypeCode.String: return XmlConstants.QnString;
873                                 case TypeCode.Int16: return XmlConstants.QnShort;
874                                 case TypeCode.Int32: return XmlConstants.QnInt;
875                                 case TypeCode.Int64: return XmlConstants.QnLong;
876                                 case TypeCode.Boolean: return XmlConstants.QnBoolean;
877                                 case TypeCode.Byte: return XmlConstants.QnUnsignedByte;
878                                 //case TypeCode.Char: return XmlConstants.QnChar;
879                                 case TypeCode.DateTime: return XmlConstants.QnDateTime;
880                                 case TypeCode.Decimal: return XmlConstants.QnDecimal;
881                                 case TypeCode.Double: return XmlConstants.QnDouble;
882                                 case TypeCode.SByte: return XmlConstants.QnSbyte;
883                                 case TypeCode.Single: return XmlConstants.QnFloat;
884                                 case TypeCode.UInt16: return XmlConstants.QnUnsignedShort;
885                                 case TypeCode.UInt32: return XmlConstants.QnUnsignedInt;
886                                 case TypeCode.UInt64: return XmlConstants.QnUnsignedLong;
887                         }
888                         
889                         if (typeof (TimeSpan) == type)
890                                 return XmlConstants.QnDuration;
891                         else if (typeof (System.Uri) == type)
892                                 return XmlConstants.QnUri;
893                         else if (typeof (byte[]) == type)
894                                 return XmlConstants.QnBase64Binary;
895                         else if (typeof (XmlQualifiedName) == type)
896                                 return XmlConstants.QnXmlQualifiedName;
897                         else
898                                 return XmlConstants.QnString;
899                 }
900         }
901 }