2004-05-20 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.Data / System.Data / XmlSchemaDataImporter.cs
1 //
2 // XmlSchemaDataImporter.cs
3 //
4 // Author:
5 //      Atsushi Enomoto <atsushi@ximian.com>
6 //
7 // (C)2004 Novell Inc.
8 //
9 //
10 // ***** The design note became somewhat obsolete. Should be rewritten. *****
11 //
12 // * Design Notes
13 //
14 // ** Abstract
15 //
16 //      This class is used to import an XML Schema into a DataSet schema.
17 //
18 //      Only XmlReader is acceptable as the input to the class.
19 //      This class is not expected to read XML Schema multi time.
20 //
21 // ** Targetable Schema Components
22 //
23 //      Only global global elements that hold complex type are converted 
24 //      into a table. 
25 //      <del>
26 //      The components of the type of the element are subsequently converted
27 //      into a table, BUT there is an exception. As for "DataSet elements",
28 //      the type is just ignored (see "DataSet Element definition" below).
29 //      </del><ins>
30 //      The components of the type of the element are subsequently converted
31 //      into a table. As for "DataSet elements", its complex type is also 
32 //      handled.
33 //      </ins>
34 //
35 //      Unused complex types are never be converted.
36 //
37 //      Global simple types and global attributes are never converted.
38 //      They cannot be a table.
39 //      Local complex types are also converted into a table.
40 //
41 //      Local elements are converted into either a table or a column in
42 //      the "context DataTable". Simple-typed element is not always converted
43 //      into a DataColumn; if maxOccurs > 1, it will be converted as a table.
44 //
45 // ** Name Convention
46 //
47 //      Ignore this section. Microsoft.NET was buggy enough to confuse
48 //      against these name conflicts.
49 //
50 //      Since local complex types are anonymous, we have to name for each
51 //      component. Thus, and since complex types and elements can have the 
52 //      same name each other, we have to manage a table for mappings from 
53 //      a name to a component. The names must be also used in DataRelation
54 //      definitions correctly.
55 //
56 // ** DataSet element definition
57 //
58 //      "DataSet element" is 1) such element that has an attribute 
59 //      msdata:IsDataSet (where prefix "msdata" is bound to 
60 //      urn:schemas-microsoft-com:xml-msdata), or 2) the only one
61 //      element definition in the schema.
62 //
63 //      There is another complicated rule. 1) If there is only one element EL 
64 //      in the schema, and 2) if the type of EL is complex named CT, and 3)
65 //      the content of the CT is a group base, and 4) the group base contains 
66 //      an element EL2, and finally 5) if EL2 is complex, THEN the element is
67 //      the DataSet element.
68 //
69 //      Only the first global element that matches the condition above is
70 //      regarded as DataSet element (by necessary design or just a bug?) 
71 //      instead of handling as an error.
72 //
73 //      All global elements are considered as an alternative in the dataset
74 //      element.
75 //
76 //      For local elements, msdata:IsDataSet are just ignored.
77 //
78 // ** Importing Complex Types as Columns
79 //
80 //      When an xs:element is going to be mapped, its complex type (remember
81 //      that only complex-typed elements are targettable) are expanded to
82 //      DataColumn.
83 //
84 //      DataColumn has a property MappingType that shows whether this column
85 //       came from attribute or element.
86 //
87 //      [Question: How about MappingType.Simple? How is it used?]
88 //
89 //      Additionally, for particle elements, it might also create another
90 //      DataTable (but for the particle elements in context DataTable, it
91 //      will create an index to the new table).
92 //
93 //      For group base particles (XmlSchemaGroupBase; sequence, choice, all)
94 //      each component in those groups are mapped to a column. Even if you
95 //      import "choice" or "all" components, DataSet.WriteXmlSchema() will
96 //      output them just as a "sequence".
97 //
98 //      Columns cannot be added directly to current context DataTable; they
99 //      need to be added after processing all the columns, because they may
100 //      have msdata:Ordinal attribute that specifies the order of the columns
101 //      in the DataTable.
102 //
103 //      "Nested elements" are not allowed. (Clarification required?)
104 //
105 // ** Identity Constraints and DataRelations
106 //
107 // *** DataRelations from element identity constraints
108 //
109 //      Only constraints on "DataSet element" is considered. All other
110 //      constraint definitions are ignored. Note that it is DataSet that has
111 //      the property Relations (of type DataRelationCollection).
112 //
113 //      xs:key and xs:unique are handled as the same (then both will be
114 //      serialized as xs:unique).
115 //
116 //      The XPath expressions in the constraints are strictly limited; they
117 //      are expected to be expandable enough to be mappable for each
118 //
119 //              * selector to "any_valid_XPath/is/OK/blah" 
120 //                where "blah" is one of the DataTable name. It looks that
121 //                only the last QName section is significant and any heading
122 //                XPath step is OK (even if the mapped node does not exist).
123 //              * field to QName that is mapped to DataColumn in the DataTable
124 //                (even ./QName is not allowed)
125 //
126 // *** DataRelations from annotations
127 //
128 //      See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/_mapping_relationship_specified_for_nested_elements.asp and http://msdn.microsoft.com/library/en-us/cpguide/html/_specifying_relationship_between_elements_with_no_nesting.asp
129 //
130 // ** Informative References
131 //
132 // Generating DataSet Relational Structure from XML Schema (XSD)
133 // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/_generating_dataset_relational_structure_from_xsd.asp
134 //
135
136 using System;
137 using System.Collections;
138 using System.Data;
139 using System.Globalization;
140 using System.Xml;
141 using System.Xml.Schema;
142
143
144 namespace System.Data
145 {
146         internal class TableStructureCollection : CollectionBase
147         {
148                 public void Add (TableStructure table)
149                 {
150                         List.Add (table);
151                 }
152
153                 public TableStructure this [int i] {
154                         get { return List [i] as TableStructure; }
155                 }
156
157                 public TableStructure this [string name] {
158                         get {
159                                 foreach (TableStructure ts in List)
160                                         if (ts.Table.TableName == name)
161                                                 return ts;
162                                 return null;
163                         }
164                 }
165         }
166
167         internal class RelationStructureCollection : CollectionBase
168         {
169                 public void Add (RelationStructure rel)
170                 {
171                         List.Add (rel);
172                 }
173
174                 public RelationStructure this [int i] {
175                         get { return List [i] as RelationStructure; }
176                 }
177
178                 public RelationStructure this [string parent, string child] {
179                         get {
180                                 foreach (RelationStructure rel in List)
181                                         if (rel.ParentTableName == parent && rel.ChildTableName == child)
182                                                 return rel;
183                                 return null;
184                         }
185                 }
186         }
187
188         internal class TableStructure
189         {
190                 public TableStructure (DataTable table)
191                 {
192                         this.Table = table;
193                 }
194
195                 // The columns and orders which will be added to the context
196                 // table (See design notes; Because of the ordinal problem)
197                 public DataTable Table;
198                 public Hashtable OrdinalColumns = new Hashtable ();
199                 public ArrayList NonOrdinalColumns = new ArrayList ();
200                 public DataColumn PrimaryKey;
201
202                 public bool ContainsColumn (string name)
203                 {
204                         foreach (DataColumn col in NonOrdinalColumns)
205                                 if (col.ColumnName == name)
206                                         return true;
207                         foreach (DataColumn col in OrdinalColumns.Keys)
208                                 if (col.ColumnName == name)
209                                         return true;
210                         return false;
211                 }
212         }
213
214         internal class RelationStructure
215         {
216                 public string ExplicitName;
217                 public string ParentTableName;
218                 public string ChildTableName;
219                 public string ParentColumnName;
220                 public string ChildColumnName;
221                 public bool IsNested;
222                 public bool CreateConstraint;
223         }
224
225         internal class XmlSchemaDataImporter
226         {
227                 static readonly XmlSchemaDatatype schemaIntegerType;
228                 static readonly XmlSchemaDatatype schemaDecimalType;
229                 static readonly XmlSchemaComplexType schemaAnyType;
230
231                 static XmlSchemaDataImporter ()
232                 {
233                         XmlSchema s = new XmlSchema ();
234                         XmlSchemaAttribute a = new XmlSchemaAttribute ();
235                         a.Name = "foo";
236                         // FIXME: mcs looks to have a bug around static 
237                         // reference resolution. XmlSchema.Namespace should work.
238                         a.SchemaTypeName = new XmlQualifiedName ("integer", System.Xml.Schema.XmlSchema.Namespace);
239                         s.Items.Add (a);
240                         XmlSchemaAttribute b = new XmlSchemaAttribute ();
241                         b.Name = "bar";
242                         // FIXME: mcs looks to have a bug around static 
243                         // reference resolution. XmlSchema.Namespace should work.
244                         b.SchemaTypeName = new XmlQualifiedName ("decimal", System.Xml.Schema.XmlSchema.Namespace);
245                         s.Items.Add (b);
246                         XmlSchemaElement e = new XmlSchemaElement ();
247                         e.Name = "bar";
248                         s.Items.Add (e);
249                         s.Compile (null);
250                         schemaIntegerType = a.AttributeType as XmlSchemaDatatype;
251                         schemaDecimalType = b.AttributeType as XmlSchemaDatatype;
252                         schemaAnyType = e.ElementType as XmlSchemaComplexType;
253                 }
254
255                 #region Fields
256
257                 DataSet dataset;
258                 XmlSchema schema;
259
260                 ArrayList relations = new ArrayList ();
261
262                 // such element that has an attribute msdata:IsDataSet="true"
263                 XmlSchemaElement datasetElement;
264
265                 // choice alternatives in the "dataset element"
266                 ArrayList topLevelElements = new ArrayList ();
267
268                 // import target elements
269                 ArrayList targetElements = new ArrayList ();
270
271                 TableStructure currentTable;
272
273                 #endregion
274
275                 // .ctor()
276
277                 public XmlSchemaDataImporter (DataSet dataset, XmlReader reader)
278                 {
279                         this.dataset = dataset;
280                         dataset.DataSetName = "NewDataSet"; // Initialize always
281                         schema = XmlSchema.Read (reader, null);
282                         // FIXME: Just XmlSchema.Namespace should work (mcs bug)
283                         if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "schema" && reader.NamespaceURI == System.Xml.Schema.XmlSchema.Namespace)
284                                 reader.ReadEndElement ();
285                         schema.Compile (null);
286                 }
287
288                 // methods
289
290                 public void Process ()
291                 {
292                         if (schema.Id != null)
293                                 dataset.DataSetName = schema.Id; // default. Overridable by "DataSet element"
294                         dataset.Namespace = schema.TargetNamespace;
295
296                         foreach (XmlSchemaObject obj in schema.Items) {
297                                 if (obj is XmlSchemaElement) {
298                                         XmlSchemaElement el = obj as XmlSchemaElement;
299                                         if (el.ElementType is XmlSchemaComplexType &&
300 el.ElementType != schemaAnyType)
301                                                 targetElements.Add (obj);
302                                 }
303                         }
304
305                         // This collection will grow up while processing elements.
306                         int globalElementCount = targetElements.Count;
307
308                         for (int i = 0; i < globalElementCount; i++)
309                                 ProcessGlobalElement ((XmlSchemaElement) targetElements [i]);
310
311                         // Rest are local elements.
312                         for (int i = globalElementCount; i < targetElements.Count; i++)
313                                 ProcessDataTableElement ((XmlSchemaElement) targetElements [i]);
314
315                         // Handle relation definitions written as xs:annotation.
316                         // See detail: http://msdn.microsoft.com/library/shared/happyUrl/fnf_msdn.asp?Redirect=%22http://msdn.microsoft.com/404/default.asp%22
317                         foreach (XmlSchemaObject obj in schema.Items)
318                                 if (obj is XmlSchemaAnnotation)
319                                         HandleAnnotations ((XmlSchemaAnnotation) obj, false);
320
321                         foreach (RelationStructure rs in this.relations)
322                                 dataset.Relations.Add (GenerateRelationship (rs));
323
324                         if (datasetElement != null) {
325                                 // Handle constraints in the DataSet element. First keys.
326                                 foreach (XmlSchemaObject obj in datasetElement.Constraints)
327                                         if (! (obj is XmlSchemaKeyref))
328                                                 ProcessParentKey ((XmlSchemaIdentityConstraint) obj);
329                                 // Then keyrefs.
330                                 foreach (XmlSchemaObject obj in datasetElement.Constraints)
331                                         if (obj is XmlSchemaKeyref)
332                                                 ProcessReferenceKey (datasetElement, (XmlSchemaKeyref) obj);
333                         }
334                 }
335
336                 private bool IsDataSetElement (XmlSchemaElement el)
337                 {
338                         if (schema.Elements.Count != 1)
339                                 return false;
340                         if (!(el.SchemaType is XmlSchemaComplexType))
341                                 return false;
342                         XmlSchemaComplexType ct = (XmlSchemaComplexType) el.SchemaType;
343                         if (ct.AttributeUses.Count > 0)
344                                 return false;
345                         XmlSchemaGroupBase gb = ct.ContentTypeParticle as XmlSchemaGroupBase;
346                         if (gb == null || gb.Items.Count == 0)
347                                 return false;
348                         foreach (XmlSchemaParticle p in gb.Items) {
349                                 if (ContainsColumn (p))
350                                         return false;
351                         }
352                         return true;
353                 }
354
355                 private bool ContainsColumn (XmlSchemaParticle p)
356                 {
357                         XmlSchemaElement el = p as XmlSchemaElement;
358                         if (el != null) {
359                                 XmlSchemaComplexType ct = el.ElementType as XmlSchemaComplexType;
360                                 if (ct == null || ct == schemaAnyType)
361                                         return true; // column element
362                                 if (ct.AttributeUses.Count > 0)
363                                         return false; // table element
364                                 switch (ct.ContentType) {
365                                 case XmlSchemaContentType.Empty:
366                                 case XmlSchemaContentType.TextOnly:
367                                         return true; // column element
368                                 default:
369                                         return false; // table element
370                                 }
371                         }
372                         XmlSchemaGroupBase gb = p as XmlSchemaGroupBase;
373                         for (int i = 0; i < gb.Items.Count; i++) {
374                                 if (ContainsColumn ((XmlSchemaParticle) gb.Items [i]))
375                                         return true;
376                         }
377                         return false;
378                 }
379
380                 private void ProcessGlobalElement (XmlSchemaElement el)
381                 {
382                         // If it is already registered (by resolving reference
383                         // in previously-imported elements), just ignore.
384                         if (dataset.Tables.Contains (el.QualifiedName.Name))
385                                 return;
386
387                         // Check if element is DataSet element
388                         if (el.UnhandledAttributes != null) {
389                                 foreach (XmlAttribute attr in el.UnhandledAttributes) {
390                                         if (attr.LocalName == "IsDataSet" &&
391                                                 attr.NamespaceURI == XmlConstants.MsdataNamespace) {
392                                                 switch (attr.Value) {
393                                                 case "true": // case sensitive
394                                                         ProcessDataSetElement (el);
395                                                         return;
396                                                 case "false":
397                                                         break;
398                                                 default:
399                                                         throw new DataException (String.Format ("Value {0} is invalid for attribute 'IsDataSet'.", attr.Value));
400                                                 }
401                                         }
402                                 }
403                         }
404
405                         // If type is not complex, just skip this element
406                         if (! (el.ElementType is XmlSchemaComplexType && el.ElementType != schemaAnyType))
407                                 return;
408
409                         if (IsDataSetElement (el)) {
410                                 ProcessDataSetElement (el);
411                                 return;
412                         }
413
414                         // Register as a top-level element
415                         topLevelElements.Add (el);
416                         // Create DataTable for this element
417                         ProcessDataTableElement (el);
418                 }
419
420                 private void ProcessDataSetElement (XmlSchemaElement el)
421                 {
422                         dataset.DataSetName = el.Name;
423                         this.datasetElement = el;
424
425                         // Search for msdata:Locale attribute
426                         if (el.UnhandledAttributes != null) {
427                                 foreach (XmlAttribute attr in el.UnhandledAttributes) {
428                                         if (attr.LocalName == "Locale" &&
429                                                 attr.NamespaceURI == XmlConstants.MsdataNamespace) {
430                                                 CultureInfo ci = new CultureInfo (attr.Value);
431                                                 dataset.Locale = ci;
432                                         }
433                                 }
434                         }
435
436                         // Process content type particle (and create DataTable)
437                         XmlSchemaComplexType ct = el.ElementType as XmlSchemaComplexType;
438                         XmlSchemaParticle p = ct != null ? ct.ContentTypeParticle : null;
439                         if (p != null)
440                                 HandleDataSetContentTypeParticle (p);
441                 }
442
443                 private void HandleDataSetContentTypeParticle (XmlSchemaParticle p)
444                 {
445                         XmlSchemaElement el = p as XmlSchemaElement;
446                         if (el != null) {
447                                 if (el.ElementType is XmlSchemaComplexType && el.RefName != el.QualifiedName)
448                                         ProcessDataTableElement (el);
449                         }
450                         else if (p is XmlSchemaGroupBase) {
451                                 foreach (XmlSchemaParticle pc in ((XmlSchemaGroupBase) p).Items)
452                                         HandleDataSetContentTypeParticle (pc);
453                         }
454                 }
455
456                 private void ProcessDataTableElement (XmlSchemaElement el)
457                 {
458                         string tableName = XmlConvert.DecodeName (el.QualifiedName.Name);
459                         // If it is already registered, just ignore.
460                         if (dataset.Tables.Contains (tableName))
461                                 return;
462
463                         DataTable table = new DataTable (tableName);
464                         currentTable = new TableStructure (table);
465
466                         dataset.Tables.Add (table);
467
468                         // Find Locale
469                         if (el.UnhandledAttributes != null) {
470                                 foreach (XmlAttribute attr in el.UnhandledAttributes) {
471                                         if (attr.LocalName == "Locale" &&
472                                                 attr.NamespaceURI == XmlConstants.MsdataNamespace)
473                                                 table.Locale = new CultureInfo (attr.Value);
474                                 }
475                         }
476
477                         // Handle complex type (NOTE: It is or should be 
478                         // impossible the type is other than complex type).
479                         XmlSchemaComplexType ct = (XmlSchemaComplexType) el.ElementType;
480
481                         // Handle attributes
482                         foreach (DictionaryEntry de in ct.AttributeUses)
483                                 ImportColumnAttribute ((XmlSchemaAttribute) de.Value);
484
485                         // Handle content type particle
486                         if (ct.ContentTypeParticle is XmlSchemaElement)
487                                 ImportColumnElement (el, (XmlSchemaElement) ct.ContentTypeParticle);
488                         else if (ct.ContentTypeParticle is XmlSchemaGroupBase)
489                                 ImportColumnGroupBase (el, (XmlSchemaGroupBase) ct.ContentTypeParticle);
490                         // else if null then do nothing.
491
492                         // Handle simple content
493                         switch (ct.ContentType) {
494                         case XmlSchemaContentType.TextOnly:
495 //                      case XmlSchemaContentType.Mixed:
496                                 // LAMESPEC: When reading from XML Schema, it maps to "_text", while on the data inference, it is mapped to "_Text" (case ignorant).
497                                 string simpleName = el.QualifiedName.Name + "_text";
498                                 DataColumn simple = new DataColumn (simpleName);
499                                 simple.Namespace = el.QualifiedName.Namespace;
500                                 simple.AllowDBNull = (el.MinOccurs == 0);
501                                 simple.ColumnMapping = MappingType.SimpleContent;
502                                 simple.DataType = ConvertDatatype (ct.Datatype);
503                                 currentTable.NonOrdinalColumns.Add (simple);
504                                 break;
505                         }
506
507                         // add columns to the table in specified order 
508                         // (by msdata:Ordinal attributes)
509                         SortedList sd = new SortedList ();
510                         foreach (DictionaryEntry de in currentTable.OrdinalColumns)
511                                 sd.Add (de.Value, de.Key);
512                         foreach (DictionaryEntry de in sd)
513                                 table.Columns.Add ((DataColumn) de.Value);
514                         foreach (DataColumn dc in currentTable.NonOrdinalColumns)
515                                 table.Columns.Add (dc);
516                 }
517
518                 private DataRelation GenerateRelationship (RelationStructure rs)
519                 {
520                         DataTable ptab = dataset.Tables [rs.ParentTableName];
521                         DataTable ctab = dataset.Tables [rs.ChildTableName];
522                         DataColumn pcol = ptab.Columns [rs.ParentColumnName];
523                         DataColumn ccol = ctab.Columns [rs.ChildColumnName];
524
525                         if (ccol == null) {
526                                 ccol = new DataColumn ();
527                                 ccol.ColumnName = pcol.ColumnName;
528                                 ccol.Namespace = String.Empty; // don't copy
529                                 ccol.ColumnMapping = MappingType.Hidden;
530                                 ccol.DataType = pcol.DataType;
531                                 ctab.Columns.Add (ccol);
532                         }
533
534                         string name = rs.ExplicitName != null ? rs.ExplicitName : XmlConvert.DecodeName (ptab.TableName) + '_' + XmlConvert.DecodeName (ctab.TableName);
535                         DataRelation rel = new DataRelation (name, pcol, ccol, rs.CreateConstraint);
536                         rel.Nested = rs.IsNested;
537                         if (rs.CreateConstraint)
538                                 rel.ParentTable.PrimaryKey = rel.ParentColumns;
539                         return rel;
540                 }
541
542                 private void ImportColumnGroupBase (XmlSchemaElement parent, XmlSchemaGroupBase gb)
543                 {
544                         foreach (XmlSchemaParticle p in gb.Items) {
545                                 XmlSchemaElement el = p as XmlSchemaElement;
546                                 if (el != null)
547                                         ImportColumnElement (parent, el);
548                                 else
549                                         ImportColumnGroupBase (parent, (XmlSchemaGroupBase) p);
550                         }
551                 }
552
553                 private XmlSchemaDatatype GetSchemaPrimitiveType (object type)
554                 {
555                         if (type is XmlSchemaComplexType)
556                                 return null; // It came here, so that maybe it is xs:anyType
557                         XmlSchemaDatatype dt = type as XmlSchemaDatatype;
558                         if (dt == null && type != null)
559                                 dt = ((XmlSchemaSimpleType) type).Datatype;
560                         return dt;
561                 }
562
563                 // Note that this column might be Hidden
564                 private void ImportColumnAttribute (XmlSchemaAttribute attr)
565                 {
566                         DataColumn col = new DataColumn ();
567                         col.ColumnName = attr.QualifiedName.Name;
568                         col.Namespace = attr.QualifiedName.Namespace;
569                         XmlSchemaDatatype dt = GetSchemaPrimitiveType (attr.AttributeType);
570                         // This complicated check comes from the fact that
571                         // MS.NET fails to map System.Object to anyType (that
572                         // will cause ReadTypedObject() fail on XmlValidatingReader).
573                         // ONLY In DataSet context, we set System.String for
574                         // simple ur-type.
575                         col.DataType = ConvertDatatype (dt);
576                         if (col.DataType == typeof (object))
577                                 col.DataType = typeof (string);
578                         // When attribute use="prohibited", then it is regarded as 
579                         // Hidden column.
580                         if (attr.Use == XmlSchemaUse.Prohibited)
581                                 col.ColumnMapping = MappingType.Hidden;
582                         else {
583                                 col.ColumnMapping = MappingType.Attribute;
584                                 col.DefaultValue = GetAttributeDefaultValue (attr);
585                         }
586                         if (attr.Use == XmlSchemaUse.Required)
587                                 col.AllowDBNull = false;
588
589                         FillFacet (col, attr.AttributeType as XmlSchemaSimpleType);
590
591                         // Call this method after filling the name
592                         ImportColumnMetaInfo (attr, attr.QualifiedName, col);
593                         AddColumn (col);
594                 }
595
596                 private void ImportColumnElement (XmlSchemaElement parent, XmlSchemaElement el)
597                 {
598                         // FIXME: element nest check
599
600                         DataColumn col = new DataColumn ();
601                         col.DefaultValue = GetElementDefaultValue (el);
602                         col.AllowDBNull = (el.MinOccurs == 0);
603
604                         if (el.ElementType is XmlSchemaComplexType && el.ElementType != schemaAnyType)
605                                 FillDataColumnComplexElement (parent, el, col);
606                         else if (el.MaxOccurs != 1)
607                                 FillDataColumnRepeatedSimpleElement (parent, el, col);
608                         else
609                                 FillDataColumnSimpleElement (el, col);
610                 }
611
612                 // common process for element and attribute
613                 private void ImportColumnMetaInfo (XmlSchemaAnnotated obj, XmlQualifiedName name, DataColumn col)
614                 {
615                         int ordinal = -1;
616                         if (obj.UnhandledAttributes != null) {
617                                 foreach (XmlAttribute attr in obj.UnhandledAttributes) {
618                                         if (attr.NamespaceURI != XmlConstants.MsdataNamespace)
619                                                 continue;
620                                         switch (attr.LocalName) {
621                                         case XmlConstants.Caption:
622                                                 col.Caption = attr.Value;
623                                                 break;
624                                         case XmlConstants.DataType:
625                                                 col.DataType = Type.GetType (attr.Value);
626                                                 break;
627                                         case XmlConstants.AutoIncrement:
628                                                 col.AutoIncrement = bool.Parse (attr.Value);
629                                                 break;
630                                         case XmlConstants.AutoIncrementSeed:
631                                                 col.AutoIncrementSeed = int.Parse (attr.Value);
632                                                 break;
633                                         case XmlConstants.Ordinal:
634                                                 ordinal = int.Parse (attr.Value);
635                                                 break;
636                                         }
637                                 }
638                         }
639                 }
640
641                 private void FillDataColumnComplexElement (XmlSchemaElement parent, XmlSchemaElement el, DataColumn col)
642                 {
643                         if (targetElements.Contains (el))
644                                 return; // do nothing
645
646                         string elName = XmlConvert.DecodeName (el.QualifiedName.Name);
647                         if (elName == dataset.DataSetName)
648                                 // Well, why it is ArgumentException :-?
649                                 throw new ArgumentException ("Nested element must not have the same name as DataSet's name.");
650
651                         if (el.Annotation != null)
652                                 HandleAnnotations (el.Annotation, true);
653                         else {
654                                 AddParentKeyColumn (parent, el, col);
655                                 DataColumn pkey = currentTable.PrimaryKey;
656
657                                 RelationStructure rel = new RelationStructure ();
658                                 rel.ParentTableName = XmlConvert.DecodeName (parent.QualifiedName.Name);
659                                 rel.ChildTableName = elName;
660                                 rel.ParentColumnName = pkey.ColumnName;
661                                 rel.ChildColumnName = pkey.ColumnName;
662                                 rel.CreateConstraint = true;
663                                 rel.IsNested = true;
664                                 relations.Add (rel);
665                         }
666
667                         // If the element is not referenced one, the element will be handled later.
668                         if (el.RefName == XmlQualifiedName.Empty)
669                                 targetElements.Add (el);
670
671                 }
672
673                 private void AddParentKeyColumn (XmlSchemaElement parent, XmlSchemaElement el, DataColumn col)
674                 {
675                         if (currentTable.PrimaryKey != null)
676                                 return;
677
678                         // check name identity
679                         string name = XmlConvert.DecodeName (parent.QualifiedName.Name) + "_Id";
680                         if (currentTable.ContainsColumn (name))
681                                 throw new DataException (String.Format ("There is already a column that has the same name: {0}", name));
682                         // check existing primary key
683                         if (currentTable.Table.PrimaryKey.Length > 0)
684                                 throw new DataException (String.Format ("There is already primary key columns in the table \"{0}\".", currentTable.Table.TableName));
685
686                         col.ColumnName = name;
687                         col.ColumnMapping = MappingType.Hidden;
688                         col.Namespace = parent.QualifiedName.Namespace;
689                         col.DataType = typeof (int);
690                         col.Unique = true;
691                         col.AutoIncrement = true;
692                         col.AllowDBNull = false;
693
694                         ImportColumnMetaInfo (el, el.QualifiedName, col);
695                         AddColumn (col);
696                         currentTable.PrimaryKey = col;
697                 }
698
699                 private void FillDataColumnRepeatedSimpleElement (XmlSchemaElement parent, XmlSchemaElement el, DataColumn col)
700                 {
701                         if (targetElements.Contains (el))
702                                 return; // do nothing
703
704                         AddParentKeyColumn (parent, el, col);
705                         DataColumn pkey = currentTable.PrimaryKey;
706
707                         string elName = XmlConvert.DecodeName (el.QualifiedName.Name);
708                         string parentName = XmlConvert.DecodeName (parent.QualifiedName.Name);
709
710                         DataTable dt = new DataTable ();
711                         dt.TableName = elName;
712                         // reference key column to parent
713                         DataColumn cc = new DataColumn ();
714                         cc.ColumnName = parentName + "_Id";
715                         cc.Namespace = parent.QualifiedName.Namespace;
716                         cc.ColumnMapping = MappingType.Hidden;
717                         cc.DataType = typeof (int);
718
719                         // repeatable content simple element
720                         DataColumn cc2 = new DataColumn ();
721                         cc2.ColumnName = elName + "_Column";
722                         cc2.Namespace = el.QualifiedName.Namespace;
723                         cc2.ColumnMapping = MappingType.SimpleContent;
724                         cc2.AllowDBNull = false;
725                         cc2.DataType = ConvertDatatype (GetSchemaPrimitiveType (el.ElementType));
726
727                         dt.Columns.Add (cc2);
728                         dt.Columns.Add (cc);
729                         dataset.Tables.Add (dt);
730
731                         RelationStructure rel = new RelationStructure ();
732                         rel.ParentTableName = parentName;
733                         rel.ChildTableName = dt.TableName;
734                         rel.ParentColumnName = pkey.ColumnName;
735                         rel.ChildColumnName = cc.ColumnName;
736                         rel.IsNested = true;
737                         rel.CreateConstraint = true;
738                         relations.Add (rel);
739                 }
740
741                 private void FillDataColumnSimpleElement (XmlSchemaElement el, DataColumn col)
742                 {
743                         col.ColumnName = XmlConvert.DecodeName (el.QualifiedName.Name);
744                         col.Namespace = el.QualifiedName.Namespace;
745                         col.ColumnMapping = MappingType.Element;
746                         col.DataType = ConvertDatatype (GetSchemaPrimitiveType (el.ElementType));
747                         FillFacet (col, el.ElementType as XmlSchemaSimpleType);
748
749                         ImportColumnMetaInfo (el, el.QualifiedName, col);
750
751                         AddColumn (col);
752                 }
753
754                 private void AddColumn (DataColumn col)
755                 {
756                         if (col.Ordinal < 0)
757                                 currentTable.NonOrdinalColumns.Add (col);
758                         else
759                                 currentTable.OrdinalColumns.Add (col, col.Ordinal);
760                 }
761
762                 private void FillFacet (DataColumn col, XmlSchemaSimpleType st)
763                 {
764                         if (st == null || st.Content == null)
765                                 return;
766
767                         // Handle restriction facets
768
769                         XmlSchemaSimpleTypeRestriction restriction = st == null ? null : st.Content as XmlSchemaSimpleTypeRestriction;
770                         if (restriction == null)
771                                 throw new DataException ("DataSet does not suport 'list' nor 'union' simple type.");
772
773                         foreach (XmlSchemaFacet f in restriction.Facets) {
774                                 if (f is XmlSchemaMaxLengthFacet)
775                                         // There is no reason why MaxLength is limited to int, except for the fact that DataColumn.MaxLength property is int.
776                                         col.MaxLength = int.Parse (f.Value);
777                         }
778                 }
779
780                 private Type ConvertDatatype (XmlSchemaDatatype dt)
781                 {
782                         if (dt == null)
783                                 return typeof (string);
784                         else if (dt.ValueType == typeof (decimal)) {
785                                 // LAMESPEC: MSDN documentation says it is based 
786                                 // on ValueType. However, in the System.Xml.Schema
787                                 // context, xs:integer is mapped to Decimal, while
788                                 // in DataSet context it is mapped to Int64.
789                                 if (dt == schemaDecimalType)
790                                         return typeof (decimal);
791                                 else if (dt == schemaIntegerType)
792                                         return typeof (long);
793                                 else
794                                         return typeof (ulong);
795                         }
796                         else
797                                 return dt.ValueType;
798                 }
799
800                 // This method cuts out the local name of the last step from XPath.
801                 // It is nothing more than hack. However, MS looks to do similar.
802                 private string GetSelectorTarget (string xpath)
803                 {
804                         string tableName = xpath;
805                         int index = tableName.LastIndexOf ('/');
806                         // '>' is enough. If XPath [0] = '/', it is invalid. 
807                         // Selector can specify only element axes.
808                         if (index > 0)
809                                 tableName = tableName.Substring (index + 1);
810
811                         // Round QName to NSName
812                         index = tableName.LastIndexOf (':');
813                         if (index > 0)
814                                 tableName = tableName.Substring (index + 1);
815
816                         return XmlConvert.DecodeName (tableName);
817                 }
818
819                 private void ProcessParentKey (XmlSchemaIdentityConstraint ic)
820                 {
821                         // Basic concept came from XmlSchemaMapper.cs
822
823                         string tableName = GetSelectorTarget (ic.Selector.XPath);
824                         
825                         DataTable dt = dataset.Tables [tableName];
826                         if (dt == null)
827                                 throw new DataException (String.Format ("Invalid XPath selection inside selector. Cannot find: {0}", tableName));
828
829                         DataColumn [] cols = new DataColumn [ic.Fields.Count];
830                         int i = 0;
831                         foreach (XmlSchemaXPath Field in ic.Fields) {
832                                 string colName = Field.XPath;
833                                 bool isAttr = colName.Length > 0 && colName [0] == '@';
834                                 int index = colName.LastIndexOf (':', isAttr ? 1 : 0);
835                                 if (index > 0)
836                                         colName = colName.Substring (index + 1);
837                                 else if (isAttr)
838                                         colName = colName.Substring (1);
839
840                                 colName = XmlConvert.DecodeName (colName);
841                                 DataColumn col = dt.Columns [colName];
842                                 if (col == null)
843                                         throw new DataException (String.Format ("Invalid XPath selection inside field. Cannot find: {0}", tableName));
844                                 if (isAttr && col.ColumnMapping != MappingType.Attribute)
845                                         throw new DataException ("The XPath specified attribute field, but mapping type is not attribute.");
846                                 if (!isAttr && col.ColumnMapping != MappingType.Element)
847                                         throw new DataException ("The XPath specified simple element field, but mapping type is not simple element.");
848
849                                 cols [i] = dt.Columns [colName];
850                                 i++;
851                         }
852                         
853                         bool isPK = false;
854                         // find if there is an attribute with the constraint name
855                         // if not use the XmlSchemaConstraint's name.
856                         string constraintName = ic.Name;
857                         if (ic.UnhandledAttributes != null) {
858                                 foreach (XmlAttribute attr in ic.UnhandledAttributes) {
859                                         if (attr.NamespaceURI != XmlConstants.MsdataNamespace)
860                                                 continue;
861                                         switch (attr.LocalName) {
862                                         case XmlConstants.ConstraintName:
863                                                 constraintName = attr.Value;
864                                                 break;
865                                         case XmlConstants.PrimaryKey:
866                                                 isPK = bool.Parse(attr.Value);
867                                                 break;
868                                         }
869                                 }
870                         }
871                         UniqueConstraint c = new UniqueConstraint (constraintName, cols, isPK);
872                         dt.Constraints.Add (c);
873                 }
874
875                 private void ProcessReferenceKey (XmlSchemaElement element, XmlSchemaKeyref keyref)
876                 {
877                         // Basic concept came from XmlSchemaMapper.cs
878
879                         string tableName = GetSelectorTarget (keyref.Selector.XPath);
880
881                         DataColumn [] cols;
882                         DataTable dt = dataset.Tables [tableName];
883                         if (dt == null)
884                                 throw new DataException (String.Format ("Invalid XPath selection inside selector. Cannot find: {0}", tableName));
885
886                         cols = new DataColumn [keyref.Fields.Count];
887                         int i = 0;
888                         foreach (XmlSchemaXPath Field in keyref.Fields) {
889                                 string colName = Field.XPath;
890                                 bool isAttr = colName.Length > 0 && colName [0] == '@';
891                                 int index = colName.LastIndexOf (':', isAttr ? 1 : 0);
892                                 if (index > 0)
893                                         colName = colName.Substring (index + 1);
894                                 else if (isAttr)
895                                         colName = colName.Substring (1);
896
897                                 colName = XmlConvert.DecodeName (colName);
898                                 DataColumn col = dt.Columns [colName];
899                                 if (isAttr && col.ColumnMapping != MappingType.Attribute)
900                                         throw new DataException ("The XPath specified attribute field, but mapping type is not attribute.");
901                                 if (!isAttr && col.ColumnMapping != MappingType.Element)
902                                         throw new DataException ("The XPath specified simple element field, but mapping type is not simple element.");
903                                 cols [i] = col;
904                                 i++;
905                         }
906                         string name = keyref.Refer.Name;
907                         // get the unique constraint for the releation
908                         UniqueConstraint uniq = FindConstraint (name, element);
909                         // generate the FK.
910                         ForeignKeyConstraint fkc = new ForeignKeyConstraint(keyref.Name, uniq.Columns, cols);
911                         dt.Constraints.Add (fkc);
912                         // generate the relation.
913                         DataRelation rel = new DataRelation (keyref.Name, uniq.Columns, cols, false);
914                         if (keyref.UnhandledAttributes != null) {
915                                 foreach (XmlAttribute attr in keyref.UnhandledAttributes)
916                                         if (attr.LocalName == "IsNested" && attr.Value == "true" && attr.NamespaceURI == XmlConstants.MsdataNamespace)
917                                                 rel.Nested = true;
918                         }
919                         rel.SetParentKeyConstraint (uniq);
920                         rel.SetChildKeyConstraint (fkc);
921
922                         dataset.Relations.Add (rel);
923                 }
924
925                 // get the unique constraint for the relation.
926                 // name - the name of the XmlSchemaUnique element
927                 private UniqueConstraint FindConstraint (string name, XmlSchemaElement element)
928                 {
929                         // Copied from XmlSchemaMapper.cs
930
931                         // find the element in the constraint collection.
932                         foreach (XmlSchemaIdentityConstraint c in element.Constraints) {
933                                 if (c is XmlSchemaKeyref)
934                                         continue;
935
936                                 if (c.Name == name) {
937                                         string tableName = GetSelectorTarget (c.Selector.XPath);
938
939                                         // find the table in the dataset.
940                                         DataTable dt = dataset.Tables [tableName];
941
942                                         string constraintName = c.Name;
943                                         // find if there is an attribute with the constraint name
944                                         // if not use the XmlSchemaUnique name.
945                                         if (c.UnhandledAttributes != null)
946                                                 foreach (XmlAttribute attr in c.UnhandledAttributes)
947                                                         if (attr.LocalName == "ConstraintName" && attr.NamespaceURI == XmlConstants.MsdataNamespace)
948                                                                 constraintName = attr.Value;
949                                         return (UniqueConstraint) dt.Constraints [constraintName];
950                                 }
951                         }
952                         throw new DataException ("Target identity constraint was not found: " + name);
953                 }
954
955                 private void HandleAnnotations (XmlSchemaAnnotation an, bool nested)
956                 {
957                         foreach (XmlSchemaObject content in an.Items) {
958                                 XmlSchemaAppInfo ai = content as XmlSchemaAppInfo;
959                                 if (ai != null) {
960                                         foreach (XmlNode n in ai.Markup) {
961                                                 XmlElement el = n as XmlElement;
962                                                 if (el != null && el.LocalName == "Relationship" && el.NamespaceURI == XmlConstants.MsdataNamespace)
963                                                         HandleRelationshipAnnotation (el, nested);
964                                         }
965                                 }
966                         }
967                 }
968
969                 private void HandleRelationshipAnnotation (XmlElement el, bool nested)
970                 {
971                         string name = el.GetAttribute ("name");
972                         string ptn = el.GetAttribute ("parent", XmlConstants.MsdataNamespace);
973                         string ctn = el.GetAttribute ("child", XmlConstants.MsdataNamespace);
974                         string pkn = el.GetAttribute ("parentkey", XmlConstants.MsdataNamespace);
975                         string fkn = el.GetAttribute ("childkey", XmlConstants.MsdataNamespace);
976
977                         RelationStructure rel = new RelationStructure ();
978                         rel.ExplicitName = name;
979                         rel.ParentTableName = ptn;
980                         rel.ChildTableName = ctn;
981                         rel.ParentColumnName = pkn;
982                         rel.ChildColumnName = fkn;
983                         rel.IsNested = nested;
984                         rel.CreateConstraint = false; // by default?
985                         relations.Add (rel);
986                 }
987
988                 private object GetElementDefaultValue (XmlSchemaElement elem)
989                 {
990                         // Unlike attribute, element cannot have a default value.
991                         if (elem.RefName == XmlQualifiedName.Empty)
992                                 return elem.DefaultValue;
993                         XmlSchemaElement referenced = schema.Elements [elem.RefName] as XmlSchemaElement;
994                         if (referenced == null) // considering missing sub components
995                                 return null;
996                         return referenced.DefaultValue;
997                 }
998
999                 private object GetAttributeDefaultValue (XmlSchemaAttribute attr)
1000                 {
1001 #if BUGGY_MS_COMPATIBLE
1002                         if (attr == null)
1003                                 return null;
1004                         else if (attr.RefName != XmlQualifiedName.Empty) {
1005                                 XmlSchemaAttribute referenced = schema.Attributes [attr.RefName] as XmlSchemaAttribute;
1006                                 if (referenced != null)
1007                                         return referenced.DefaultValue;
1008                                 else
1009                                         return null;
1010                         }
1011                         if (attr.DefaultValue != null)
1012                                 return attr.DefaultValue;
1013                         return attr.FixedValue;
1014 #else
1015                         if (attr.DefaultValue != null)
1016                                 return attr.DefaultValue;
1017                         else if (attr.FixedValue != null)
1018                                 return attr.FixedValue;
1019                         else if (attr.RefName == XmlQualifiedName.Empty)
1020                                 return null;
1021                         XmlSchemaAttribute referenced = schema.Attributes [attr.RefName] as XmlSchemaAttribute;
1022                         if (referenced == null) // considering missing sub components
1023                                 return null;
1024                         if (referenced.DefaultValue != null)
1025                                 return referenced.DefaultValue;
1026                         return referenced.FixedValue;
1027 #endif
1028                 }
1029         }
1030 }