**** Merged from MCS ****
[mono-project.git] / mcs / class / System.Data / System.Data / XmlSchemaDataImporter.cs
blob898839b0b1894cf166db11f8f1dde37bb68df2fc
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. *****
12 // * Design Notes
14 // ** Abstract
16 // This class is used to import an XML Schema into a DataSet schema.
18 // Only XmlReader is acceptable as the input to the class.
19 // This class is not expected to read XML Schema multi time.
21 // ** Targetable Schema Components
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>
35 // Unused complex types are never be converted.
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.
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.
45 // ** Name Convention
47 // Ignore this section. Microsoft.NET was buggy enough to confuse
48 // against these name conflicts.
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.
56 // ** DataSet element definition
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.
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.
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.
73 // All global elements are considered as an alternative in the dataset
74 // element.
76 // For local elements, msdata:IsDataSet are just ignored.
78 // ** Importing Complex Types as Columns
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.
84 // DataColumn has a property MappingType that shows whether this column
85 // came from attribute or element.
87 // [Question: How about MappingType.Simple? How is it used?]
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).
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".
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.
103 // "Nested elements" are not allowed. (Clarification required?)
105 // ** Identity Constraints and DataRelations
107 // *** DataRelations from element identity constraints
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).
113 // xs:key and xs:unique are handled as the same (then both will be
114 // serialized as xs:unique).
116 // The XPath expressions in the constraints are strictly limited; they
117 // are expected to be expandable enough to be mappable for each
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)
126 // *** DataRelations from annotations
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
130 // ** Informative References
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
137 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
139 // Permission is hereby granted, free of charge, to any person obtaining
140 // a copy of this software and associated documentation files (the
141 // "Software"), to deal in the Software without restriction, including
142 // without limitation the rights to use, copy, modify, merge, publish,
143 // distribute, sublicense, and/or sell copies of the Software, and to
144 // permit persons to whom the Software is furnished to do so, subject to
145 // the following conditions:
147 // The above copyright notice and this permission notice shall be
148 // included in all copies or substantial portions of the Software.
150 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
151 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
152 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
153 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
154 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
155 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
156 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
159 using System;
160 using System.Collections;
161 using System.Data;
162 using System.Globalization;
163 using System.Xml;
164 using System.Xml.Schema;
167 namespace System.Data
169 internal class TableStructureCollection : CollectionBase
171 public void Add (TableStructure table)
173 List.Add (table);
176 public TableStructure this [int i] {
177 get { return List [i] as TableStructure; }
180 public TableStructure this [string name] {
181 get {
182 foreach (TableStructure ts in List)
183 if (ts.Table.TableName == name)
184 return ts;
185 return null;
190 internal class RelationStructureCollection : CollectionBase
192 public void Add (RelationStructure rel)
194 List.Add (rel);
197 public RelationStructure this [int i] {
198 get { return List [i] as RelationStructure; }
201 public RelationStructure this [string parent, string child] {
202 get {
203 foreach (RelationStructure rel in List)
204 if (rel.ParentTableName == parent && rel.ChildTableName == child)
205 return rel;
206 return null;
211 internal class TableStructure
213 public TableStructure (DataTable table)
215 this.Table = table;
218 // The columns and orders which will be added to the context
219 // table (See design notes; Because of the ordinal problem)
220 public DataTable Table;
221 public Hashtable OrdinalColumns = new Hashtable ();
222 public ArrayList NonOrdinalColumns = new ArrayList ();
223 public DataColumn PrimaryKey;
225 public bool ContainsColumn (string name)
227 foreach (DataColumn col in NonOrdinalColumns)
228 if (col.ColumnName == name)
229 return true;
230 foreach (DataColumn col in OrdinalColumns.Keys)
231 if (col.ColumnName == name)
232 return true;
233 return false;
237 internal class RelationStructure
239 public string ExplicitName;
240 public string ParentTableName;
241 public string ChildTableName;
242 public string ParentColumnName;
243 public string ChildColumnName;
244 public bool IsNested;
245 public bool CreateConstraint;
248 internal class XmlSchemaDataImporter
250 static readonly XmlSchemaDatatype schemaIntegerType;
251 static readonly XmlSchemaDatatype schemaDecimalType;
252 static readonly XmlSchemaComplexType schemaAnyType;
254 static XmlSchemaDataImporter ()
256 XmlSchema s = new XmlSchema ();
257 XmlSchemaAttribute a = new XmlSchemaAttribute ();
258 a.Name = "foo";
259 // FIXME: mcs looks to have a bug around static
260 // reference resolution. XmlSchema.Namespace should work.
261 a.SchemaTypeName = new XmlQualifiedName ("integer", System.Xml.Schema.XmlSchema.Namespace);
262 s.Items.Add (a);
263 XmlSchemaAttribute b = new XmlSchemaAttribute ();
264 b.Name = "bar";
265 // FIXME: mcs looks to have a bug around static
266 // reference resolution. XmlSchema.Namespace should work.
267 b.SchemaTypeName = new XmlQualifiedName ("decimal", System.Xml.Schema.XmlSchema.Namespace);
268 s.Items.Add (b);
269 XmlSchemaElement e = new XmlSchemaElement ();
270 e.Name = "bar";
271 s.Items.Add (e);
272 s.Compile (null);
273 schemaIntegerType = a.AttributeType as XmlSchemaDatatype;
274 schemaDecimalType = b.AttributeType as XmlSchemaDatatype;
275 schemaAnyType = e.ElementType as XmlSchemaComplexType;
278 #region Fields
280 DataSet dataset;
281 XmlSchema schema;
283 ArrayList relations = new ArrayList ();
285 // such element that has an attribute msdata:IsDataSet="true"
286 XmlSchemaElement datasetElement;
288 // choice alternatives in the "dataset element"
289 ArrayList topLevelElements = new ArrayList ();
291 // import target elements
292 ArrayList targetElements = new ArrayList ();
294 TableStructure currentTable;
296 #endregion
298 // .ctor()
300 public XmlSchemaDataImporter (DataSet dataset, XmlReader reader)
302 this.dataset = dataset;
303 dataset.DataSetName = "NewDataSet"; // Initialize always
304 schema = XmlSchema.Read (reader, null);
305 // FIXME: Just XmlSchema.Namespace should work (mcs bug)
306 if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "schema" && reader.NamespaceURI == System.Xml.Schema.XmlSchema.Namespace)
307 reader.ReadEndElement ();
308 schema.Compile (null);
311 // methods
313 public void Process ()
315 if (schema.Id != null)
316 dataset.DataSetName = schema.Id; // default. Overridable by "DataSet element"
317 dataset.Namespace = schema.TargetNamespace;
319 foreach (XmlSchemaObject obj in schema.Items) {
320 if (obj is XmlSchemaElement) {
321 XmlSchemaElement el = obj as XmlSchemaElement;
322 if (el.ElementType is XmlSchemaComplexType &&
323 el.ElementType != schemaAnyType)
324 targetElements.Add (obj);
328 // This collection will grow up while processing elements.
329 int globalElementCount = targetElements.Count;
331 for (int i = 0; i < globalElementCount; i++)
332 ProcessGlobalElement ((XmlSchemaElement) targetElements [i]);
334 // Rest are local elements.
335 for (int i = globalElementCount; i < targetElements.Count; i++)
336 ProcessDataTableElement ((XmlSchemaElement) targetElements [i]);
338 // Handle relation definitions written as xs:annotation.
339 // See detail: http://msdn.microsoft.com/library/shared/happyUrl/fnf_msdn.asp?Redirect=%22http://msdn.microsoft.com/404/default.asp%22
340 foreach (XmlSchemaObject obj in schema.Items)
341 if (obj is XmlSchemaAnnotation)
342 HandleAnnotations ((XmlSchemaAnnotation) obj, false);
344 foreach (RelationStructure rs in this.relations)
345 dataset.Relations.Add (GenerateRelationship (rs));
347 if (datasetElement != null) {
348 // Handle constraints in the DataSet element. First keys.
349 foreach (XmlSchemaObject obj in datasetElement.Constraints)
350 if (! (obj is XmlSchemaKeyref))
351 ProcessParentKey ((XmlSchemaIdentityConstraint) obj);
352 // Then keyrefs.
353 foreach (XmlSchemaObject obj in datasetElement.Constraints)
354 if (obj is XmlSchemaKeyref)
355 ProcessReferenceKey (datasetElement, (XmlSchemaKeyref) obj);
359 private bool IsDataSetElement (XmlSchemaElement el)
361 if (schema.Elements.Count != 1)
362 return false;
363 if (!(el.SchemaType is XmlSchemaComplexType))
364 return false;
365 XmlSchemaComplexType ct = (XmlSchemaComplexType) el.SchemaType;
366 if (ct.AttributeUses.Count > 0)
367 return false;
368 XmlSchemaGroupBase gb = ct.ContentTypeParticle as XmlSchemaGroupBase;
369 if (gb == null || gb.Items.Count == 0)
370 return false;
371 foreach (XmlSchemaParticle p in gb.Items) {
372 if (ContainsColumn (p))
373 return false;
375 return true;
378 private bool ContainsColumn (XmlSchemaParticle p)
380 XmlSchemaElement el = p as XmlSchemaElement;
381 if (el != null) {
382 XmlSchemaComplexType ct = el.ElementType as XmlSchemaComplexType;
383 if (ct == null || ct == schemaAnyType)
384 return true; // column element
385 if (ct.AttributeUses.Count > 0)
386 return false; // table element
387 switch (ct.ContentType) {
388 case XmlSchemaContentType.Empty:
389 case XmlSchemaContentType.TextOnly:
390 return true; // column element
391 default:
392 return false; // table element
395 XmlSchemaGroupBase gb = p as XmlSchemaGroupBase;
396 for (int i = 0; i < gb.Items.Count; i++) {
397 if (ContainsColumn ((XmlSchemaParticle) gb.Items [i]))
398 return true;
400 return false;
403 private void ProcessGlobalElement (XmlSchemaElement el)
405 // If it is already registered (by resolving reference
406 // in previously-imported elements), just ignore.
407 if (dataset.Tables.Contains (el.QualifiedName.Name))
408 return;
410 // Check if element is DataSet element
411 if (el.UnhandledAttributes != null) {
412 foreach (XmlAttribute attr in el.UnhandledAttributes) {
413 if (attr.LocalName == "IsDataSet" &&
414 attr.NamespaceURI == XmlConstants.MsdataNamespace) {
415 switch (attr.Value) {
416 case "true": // case sensitive
417 ProcessDataSetElement (el);
418 return;
419 case "false":
420 break;
421 default:
422 throw new DataException (String.Format ("Value {0} is invalid for attribute 'IsDataSet'.", attr.Value));
428 // If type is not complex, just skip this element
429 if (! (el.ElementType is XmlSchemaComplexType && el.ElementType != schemaAnyType))
430 return;
432 if (IsDataSetElement (el)) {
433 ProcessDataSetElement (el);
434 return;
437 // Register as a top-level element
438 topLevelElements.Add (el);
439 // Create DataTable for this element
440 ProcessDataTableElement (el);
443 private void ProcessDataSetElement (XmlSchemaElement el)
445 dataset.DataSetName = el.Name;
446 this.datasetElement = el;
448 // Search for msdata:Locale attribute
449 if (el.UnhandledAttributes != null) {
450 foreach (XmlAttribute attr in el.UnhandledAttributes) {
451 if (attr.LocalName == "Locale" &&
452 attr.NamespaceURI == XmlConstants.MsdataNamespace) {
453 CultureInfo ci = new CultureInfo (attr.Value);
454 dataset.Locale = ci;
459 // Process content type particle (and create DataTable)
460 XmlSchemaComplexType ct = el.ElementType as XmlSchemaComplexType;
461 XmlSchemaParticle p = ct != null ? ct.ContentTypeParticle : null;
462 if (p != null)
463 HandleDataSetContentTypeParticle (p);
466 private void HandleDataSetContentTypeParticle (XmlSchemaParticle p)
468 XmlSchemaElement el = p as XmlSchemaElement;
469 if (el != null) {
470 if (el.ElementType is XmlSchemaComplexType && el.RefName != el.QualifiedName)
471 ProcessDataTableElement (el);
473 else if (p is XmlSchemaGroupBase) {
474 foreach (XmlSchemaParticle pc in ((XmlSchemaGroupBase) p).Items)
475 HandleDataSetContentTypeParticle (pc);
479 private void ProcessDataTableElement (XmlSchemaElement el)
481 string tableName = XmlConvert.DecodeName (el.QualifiedName.Name);
482 // If it is already registered, just ignore.
483 if (dataset.Tables.Contains (tableName))
484 return;
486 DataTable table = new DataTable (tableName);
487 table.Namespace = el.QualifiedName.Namespace;
488 currentTable = new TableStructure (table);
490 dataset.Tables.Add (table);
492 // Find Locale
493 if (el.UnhandledAttributes != null) {
494 foreach (XmlAttribute attr in el.UnhandledAttributes) {
495 if (attr.LocalName == "Locale" &&
496 attr.NamespaceURI == XmlConstants.MsdataNamespace)
497 table.Locale = new CultureInfo (attr.Value);
501 // Handle complex type (NOTE: It is or should be
502 // impossible the type is other than complex type).
503 XmlSchemaComplexType ct = (XmlSchemaComplexType) el.ElementType;
505 // Handle attributes
506 foreach (DictionaryEntry de in ct.AttributeUses)
507 ImportColumnAttribute ((XmlSchemaAttribute) de.Value);
509 // Handle content type particle
510 if (ct.ContentTypeParticle is XmlSchemaElement)
511 ImportColumnElement (el, (XmlSchemaElement) ct.ContentTypeParticle);
512 else if (ct.ContentTypeParticle is XmlSchemaGroupBase)
513 ImportColumnGroupBase (el, (XmlSchemaGroupBase) ct.ContentTypeParticle);
514 // else if null then do nothing.
516 // Handle simple content
517 switch (ct.ContentType) {
518 case XmlSchemaContentType.TextOnly:
519 // case XmlSchemaContentType.Mixed:
520 // LAMESPEC: When reading from XML Schema, it maps to "_text", while on the data inference, it is mapped to "_Text" (case ignorant).
521 string simpleName = el.QualifiedName.Name + "_text";
522 DataColumn simple = new DataColumn (simpleName);
523 simple.Namespace = el.QualifiedName.Namespace;
524 simple.AllowDBNull = (el.MinOccurs == 0);
525 simple.ColumnMapping = MappingType.SimpleContent;
526 simple.DataType = ConvertDatatype (ct.Datatype);
527 currentTable.NonOrdinalColumns.Add (simple);
528 break;
531 // add columns to the table in specified order
532 // (by msdata:Ordinal attributes)
533 SortedList sd = new SortedList ();
534 foreach (DictionaryEntry de in currentTable.OrdinalColumns)
535 sd.Add (de.Value, de.Key);
536 foreach (DictionaryEntry de in sd)
537 table.Columns.Add ((DataColumn) de.Value);
538 foreach (DataColumn dc in currentTable.NonOrdinalColumns)
539 table.Columns.Add (dc);
542 private DataRelation GenerateRelationship (RelationStructure rs)
544 DataTable ptab = dataset.Tables [rs.ParentTableName];
545 DataTable ctab = dataset.Tables [rs.ChildTableName];
546 DataColumn pcol = ptab.Columns [rs.ParentColumnName];
547 DataColumn ccol = ctab.Columns [rs.ChildColumnName];
549 if (ccol == null) {
550 ccol = new DataColumn ();
551 ccol.ColumnName = pcol.ColumnName;
552 ccol.Namespace = String.Empty; // don't copy
553 ccol.ColumnMapping = MappingType.Hidden;
554 ccol.DataType = pcol.DataType;
555 ctab.Columns.Add (ccol);
558 string name = rs.ExplicitName != null ? rs.ExplicitName : XmlConvert.DecodeName (ptab.TableName) + '_' + XmlConvert.DecodeName (ctab.TableName);
559 DataRelation rel = new DataRelation (name, pcol, ccol, rs.CreateConstraint);
560 rel.Nested = rs.IsNested;
561 if (rs.CreateConstraint)
562 rel.ParentTable.PrimaryKey = rel.ParentColumns;
563 return rel;
566 private void ImportColumnGroupBase (XmlSchemaElement parent, XmlSchemaGroupBase gb)
568 foreach (XmlSchemaParticle p in gb.Items) {
569 XmlSchemaElement el = p as XmlSchemaElement;
570 if (el != null)
571 ImportColumnElement (parent, el);
572 else
573 ImportColumnGroupBase (parent, (XmlSchemaGroupBase) p);
577 private XmlSchemaDatatype GetSchemaPrimitiveType (object type)
579 if (type is XmlSchemaComplexType)
580 return null; // It came here, so that maybe it is xs:anyType
581 XmlSchemaDatatype dt = type as XmlSchemaDatatype;
582 if (dt == null && type != null)
583 dt = ((XmlSchemaSimpleType) type).Datatype;
584 return dt;
587 // Note that this column might be Hidden
588 private void ImportColumnAttribute (XmlSchemaAttribute attr)
590 DataColumn col = new DataColumn ();
591 col.ColumnName = attr.QualifiedName.Name;
592 col.Namespace = attr.QualifiedName.Namespace;
593 XmlSchemaDatatype dt = GetSchemaPrimitiveType (attr.AttributeType);
594 // This complicated check comes from the fact that
595 // MS.NET fails to map System.Object to anyType (that
596 // will cause ReadTypedObject() fail on XmlValidatingReader).
597 // ONLY In DataSet context, we set System.String for
598 // simple ur-type.
599 col.DataType = ConvertDatatype (dt);
600 if (col.DataType == typeof (object))
601 col.DataType = typeof (string);
602 // When attribute use="prohibited", then it is regarded as
603 // Hidden column.
604 if (attr.Use == XmlSchemaUse.Prohibited)
605 col.ColumnMapping = MappingType.Hidden;
606 else {
607 col.ColumnMapping = MappingType.Attribute;
608 col.DefaultValue = GetAttributeDefaultValue (attr);
610 if (attr.Use == XmlSchemaUse.Required)
611 col.AllowDBNull = false;
613 FillFacet (col, attr.AttributeType as XmlSchemaSimpleType);
615 // Call this method after filling the name
616 ImportColumnMetaInfo (attr, attr.QualifiedName, col);
617 AddColumn (col);
620 private void ImportColumnElement (XmlSchemaElement parent, XmlSchemaElement el)
622 // FIXME: element nest check
624 DataColumn col = new DataColumn ();
625 col.DefaultValue = GetElementDefaultValue (el);
626 col.AllowDBNull = (el.MinOccurs == 0);
628 if (el.ElementType is XmlSchemaComplexType && el.ElementType != schemaAnyType)
629 FillDataColumnComplexElement (parent, el, col);
630 else if (el.MaxOccurs != 1)
631 FillDataColumnRepeatedSimpleElement (parent, el, col);
632 else
633 FillDataColumnSimpleElement (el, col);
636 // common process for element and attribute
637 private void ImportColumnMetaInfo (XmlSchemaAnnotated obj, XmlQualifiedName name, DataColumn col)
639 int ordinal = -1;
640 if (obj.UnhandledAttributes != null) {
641 foreach (XmlAttribute attr in obj.UnhandledAttributes) {
642 if (attr.NamespaceURI != XmlConstants.MsdataNamespace)
643 continue;
644 switch (attr.LocalName) {
645 case XmlConstants.Caption:
646 col.Caption = attr.Value;
647 break;
648 case XmlConstants.DataType:
649 col.DataType = Type.GetType (attr.Value);
650 break;
651 case XmlConstants.AutoIncrement:
652 col.AutoIncrement = bool.Parse (attr.Value);
653 break;
654 case XmlConstants.AutoIncrementSeed:
655 col.AutoIncrementSeed = int.Parse (attr.Value);
656 break;
657 case XmlConstants.ReadOnly:
658 col.ReadOnly = XmlConvert.ToBoolean (attr.Value);
659 break;
660 case XmlConstants.Ordinal:
661 ordinal = int.Parse (attr.Value);
662 break;
668 private void FillDataColumnComplexElement (XmlSchemaElement parent, XmlSchemaElement el, DataColumn col)
670 if (targetElements.Contains (el))
671 return; // do nothing
673 string elName = XmlConvert.DecodeName (el.QualifiedName.Name);
674 if (elName == dataset.DataSetName)
675 // Well, why it is ArgumentException :-?
676 throw new ArgumentException ("Nested element must not have the same name as DataSet's name.");
678 if (el.Annotation != null)
679 HandleAnnotations (el.Annotation, true);
680 else {
681 AddParentKeyColumn (parent, el, col);
682 DataColumn pkey = currentTable.PrimaryKey;
684 RelationStructure rel = new RelationStructure ();
685 rel.ParentTableName = XmlConvert.DecodeName (parent.QualifiedName.Name);
686 rel.ChildTableName = elName;
687 rel.ParentColumnName = pkey.ColumnName;
688 rel.ChildColumnName = pkey.ColumnName;
689 rel.CreateConstraint = true;
690 rel.IsNested = true;
691 relations.Add (rel);
694 // If the element is not referenced one, the element will be handled later.
695 if (el.RefName == XmlQualifiedName.Empty)
696 targetElements.Add (el);
700 private void AddParentKeyColumn (XmlSchemaElement parent, XmlSchemaElement el, DataColumn col)
702 if (currentTable.PrimaryKey != null)
703 return;
705 // check name identity
706 string name = XmlConvert.DecodeName (parent.QualifiedName.Name) + "_Id";
707 if (currentTable.ContainsColumn (name))
708 throw new DataException (String.Format ("There is already a column that has the same name: {0}", name));
709 // check existing primary key
710 if (currentTable.Table.PrimaryKey.Length > 0)
711 throw new DataException (String.Format ("There is already primary key columns in the table \"{0}\".", currentTable.Table.TableName));
713 col.ColumnName = name;
714 col.ColumnMapping = MappingType.Hidden;
715 col.Namespace = parent.QualifiedName.Namespace;
716 col.DataType = typeof (int);
717 col.Unique = true;
718 col.AutoIncrement = true;
719 col.AllowDBNull = false;
721 ImportColumnMetaInfo (el, el.QualifiedName, col);
722 AddColumn (col);
723 currentTable.PrimaryKey = col;
726 private void FillDataColumnRepeatedSimpleElement (XmlSchemaElement parent, XmlSchemaElement el, DataColumn col)
728 if (targetElements.Contains (el))
729 return; // do nothing
731 AddParentKeyColumn (parent, el, col);
732 DataColumn pkey = currentTable.PrimaryKey;
734 string elName = XmlConvert.DecodeName (el.QualifiedName.Name);
735 string parentName = XmlConvert.DecodeName (parent.QualifiedName.Name);
737 DataTable dt = new DataTable ();
738 dt.TableName = elName;
739 dt.Namespace = el.QualifiedName.Namespace;
740 // reference key column to parent
741 DataColumn cc = new DataColumn ();
742 cc.ColumnName = parentName + "_Id";
743 cc.Namespace = parent.QualifiedName.Namespace;
744 cc.ColumnMapping = MappingType.Hidden;
745 cc.DataType = typeof (int);
747 // repeatable content simple element
748 DataColumn cc2 = new DataColumn ();
749 cc2.ColumnName = elName + "_Column";
750 cc2.Namespace = el.QualifiedName.Namespace;
751 cc2.ColumnMapping = MappingType.SimpleContent;
752 cc2.AllowDBNull = false;
753 cc2.DataType = ConvertDatatype (GetSchemaPrimitiveType (el.ElementType));
755 dt.Columns.Add (cc2);
756 dt.Columns.Add (cc);
757 dataset.Tables.Add (dt);
759 RelationStructure rel = new RelationStructure ();
760 rel.ParentTableName = parentName;
761 rel.ChildTableName = dt.TableName;
762 rel.ParentColumnName = pkey.ColumnName;
763 rel.ChildColumnName = cc.ColumnName;
764 rel.IsNested = true;
765 rel.CreateConstraint = true;
766 relations.Add (rel);
769 private void FillDataColumnSimpleElement (XmlSchemaElement el, DataColumn col)
771 col.ColumnName = XmlConvert.DecodeName (el.QualifiedName.Name);
772 col.Namespace = el.QualifiedName.Namespace;
773 col.ColumnMapping = MappingType.Element;
774 col.DataType = ConvertDatatype (GetSchemaPrimitiveType (el.ElementType));
775 FillFacet (col, el.ElementType as XmlSchemaSimpleType);
777 ImportColumnMetaInfo (el, el.QualifiedName, col);
779 AddColumn (col);
782 private void AddColumn (DataColumn col)
784 if (col.Ordinal < 0)
785 currentTable.NonOrdinalColumns.Add (col);
786 else
787 currentTable.OrdinalColumns.Add (col, col.Ordinal);
790 private void FillFacet (DataColumn col, XmlSchemaSimpleType st)
792 if (st == null || st.Content == null)
793 return;
795 // Handle restriction facets
797 XmlSchemaSimpleTypeRestriction restriction = st == null ? null : st.Content as XmlSchemaSimpleTypeRestriction;
798 if (restriction == null)
799 throw new DataException ("DataSet does not suport 'list' nor 'union' simple type.");
801 foreach (XmlSchemaFacet f in restriction.Facets) {
802 if (f is XmlSchemaMaxLengthFacet)
803 // There is no reason why MaxLength is limited to int, except for the fact that DataColumn.MaxLength property is int.
804 col.MaxLength = int.Parse (f.Value);
808 private Type ConvertDatatype (XmlSchemaDatatype dt)
810 if (dt == null)
811 return typeof (string);
812 else if (dt.ValueType == typeof (decimal)) {
813 // LAMESPEC: MSDN documentation says it is based
814 // on ValueType. However, in the System.Xml.Schema
815 // context, xs:integer is mapped to Decimal, while
816 // in DataSet context it is mapped to Int64.
817 if (dt == schemaDecimalType)
818 return typeof (decimal);
819 else if (dt == schemaIntegerType)
820 return typeof (long);
821 else
822 return typeof (ulong);
824 else
825 return dt.ValueType;
828 // This method cuts out the local name of the last step from XPath.
829 // It is nothing more than hack. However, MS looks to do similar.
830 private string GetSelectorTarget (string xpath)
832 string tableName = xpath;
833 int index = tableName.LastIndexOf ('/');
834 // '>' is enough. If XPath [0] = '/', it is invalid.
835 // Selector can specify only element axes.
836 if (index > 0)
837 tableName = tableName.Substring (index + 1);
839 // Round QName to NSName
840 index = tableName.LastIndexOf (':');
841 if (index > 0)
842 tableName = tableName.Substring (index + 1);
844 return XmlConvert.DecodeName (tableName);
847 private void ProcessParentKey (XmlSchemaIdentityConstraint ic)
849 // Basic concept came from XmlSchemaMapper.cs
851 string tableName = GetSelectorTarget (ic.Selector.XPath);
853 DataTable dt = dataset.Tables [tableName];
854 if (dt == null)
855 throw new DataException (String.Format ("Invalid XPath selection inside selector. Cannot find: {0}", tableName));
857 DataColumn [] cols = new DataColumn [ic.Fields.Count];
858 int i = 0;
859 foreach (XmlSchemaXPath Field in ic.Fields) {
860 string colName = Field.XPath;
861 bool isAttr = colName.Length > 0 && colName [0] == '@';
862 int index = colName.LastIndexOf (':');
863 if (index > 0)
864 colName = colName.Substring (index + 1);
865 else if (isAttr)
866 colName = colName.Substring (1);
868 colName = XmlConvert.DecodeName (colName);
869 DataColumn col = dt.Columns [colName];
870 if (col == null)
871 throw new DataException (String.Format ("Invalid XPath selection inside field. Cannot find: {0}", tableName));
872 if (isAttr && col.ColumnMapping != MappingType.Attribute)
873 throw new DataException ("The XPath specified attribute field, but mapping type is not attribute.");
874 if (!isAttr && col.ColumnMapping != MappingType.Element)
875 throw new DataException ("The XPath specified simple element field, but mapping type is not simple element.");
877 cols [i] = dt.Columns [colName];
878 i++;
881 bool isPK = false;
882 // find if there is an attribute with the constraint name
883 // if not use the XmlSchemaConstraint's name.
884 string constraintName = ic.Name;
885 if (ic.UnhandledAttributes != null) {
886 foreach (XmlAttribute attr in ic.UnhandledAttributes) {
887 if (attr.NamespaceURI != XmlConstants.MsdataNamespace)
888 continue;
889 switch (attr.LocalName) {
890 case XmlConstants.ConstraintName:
891 constraintName = attr.Value;
892 break;
893 case XmlConstants.PrimaryKey:
894 isPK = bool.Parse(attr.Value);
895 break;
899 UniqueConstraint c = new UniqueConstraint (constraintName, cols, isPK);
900 dt.Constraints.Add (c);
903 private void ProcessReferenceKey (XmlSchemaElement element, XmlSchemaKeyref keyref)
905 // Basic concept came from XmlSchemaMapper.cs
907 string tableName = GetSelectorTarget (keyref.Selector.XPath);
909 DataColumn [] cols;
910 DataTable dt = dataset.Tables [tableName];
911 if (dt == null)
912 throw new DataException (String.Format ("Invalid XPath selection inside selector. Cannot find: {0}", tableName));
914 cols = new DataColumn [keyref.Fields.Count];
915 int i = 0;
916 foreach (XmlSchemaXPath Field in keyref.Fields) {
917 string colName = Field.XPath;
918 bool isAttr = colName.Length > 0 && colName [0] == '@';
919 int index = colName.LastIndexOf (':');
920 if (index > 0)
921 colName = colName.Substring (index + 1);
922 else if (isAttr)
923 colName = colName.Substring (1);
925 colName = XmlConvert.DecodeName (colName);
926 DataColumn col = dt.Columns [colName];
927 if (isAttr && col.ColumnMapping != MappingType.Attribute)
928 throw new DataException ("The XPath specified attribute field, but mapping type is not attribute.");
929 if (!isAttr && col.ColumnMapping != MappingType.Element)
930 throw new DataException ("The XPath specified simple element field, but mapping type is not simple element.");
931 cols [i] = col;
932 i++;
934 string name = keyref.Refer.Name;
935 // get the unique constraint for the releation
936 UniqueConstraint uniq = FindConstraint (name, element);
937 // generate the FK.
938 ForeignKeyConstraint fkc = new ForeignKeyConstraint(keyref.Name, uniq.Columns, cols);
939 dt.Constraints.Add (fkc);
940 // generate the relation.
941 DataRelation rel = new DataRelation (keyref.Name, uniq.Columns, cols, false);
942 if (keyref.UnhandledAttributes != null) {
943 foreach (XmlAttribute attr in keyref.UnhandledAttributes)
944 if (attr.LocalName == "IsNested" && attr.Value == "true" && attr.NamespaceURI == XmlConstants.MsdataNamespace)
945 rel.Nested = true;
947 rel.SetParentKeyConstraint (uniq);
948 rel.SetChildKeyConstraint (fkc);
950 dataset.Relations.Add (rel);
953 // get the unique constraint for the relation.
954 // name - the name of the XmlSchemaUnique element
955 private UniqueConstraint FindConstraint (string name, XmlSchemaElement element)
957 // Copied from XmlSchemaMapper.cs
959 // find the element in the constraint collection.
960 foreach (XmlSchemaIdentityConstraint c in element.Constraints) {
961 if (c is XmlSchemaKeyref)
962 continue;
964 if (c.Name == name) {
965 string tableName = GetSelectorTarget (c.Selector.XPath);
967 // find the table in the dataset.
968 DataTable dt = dataset.Tables [tableName];
970 string constraintName = c.Name;
971 // find if there is an attribute with the constraint name
972 // if not use the XmlSchemaUnique name.
973 if (c.UnhandledAttributes != null)
974 foreach (XmlAttribute attr in c.UnhandledAttributes)
975 if (attr.LocalName == "ConstraintName" && attr.NamespaceURI == XmlConstants.MsdataNamespace)
976 constraintName = attr.Value;
977 return (UniqueConstraint) dt.Constraints [constraintName];
980 throw new DataException ("Target identity constraint was not found: " + name);
983 private void HandleAnnotations (XmlSchemaAnnotation an, bool nested)
985 foreach (XmlSchemaObject content in an.Items) {
986 XmlSchemaAppInfo ai = content as XmlSchemaAppInfo;
987 if (ai != null) {
988 foreach (XmlNode n in ai.Markup) {
989 XmlElement el = n as XmlElement;
990 if (el != null && el.LocalName == "Relationship" && el.NamespaceURI == XmlConstants.MsdataNamespace)
991 HandleRelationshipAnnotation (el, nested);
997 private void HandleRelationshipAnnotation (XmlElement el, bool nested)
999 string name = el.GetAttribute ("name");
1000 string ptn = el.GetAttribute ("parent", XmlConstants.MsdataNamespace);
1001 string ctn = el.GetAttribute ("child", XmlConstants.MsdataNamespace);
1002 string pkn = el.GetAttribute ("parentkey", XmlConstants.MsdataNamespace);
1003 string fkn = el.GetAttribute ("childkey", XmlConstants.MsdataNamespace);
1005 RelationStructure rel = new RelationStructure ();
1006 rel.ExplicitName = name;
1007 rel.ParentTableName = ptn;
1008 rel.ChildTableName = ctn;
1009 rel.ParentColumnName = pkn;
1010 rel.ChildColumnName = fkn;
1011 rel.IsNested = nested;
1012 rel.CreateConstraint = false; // by default?
1013 relations.Add (rel);
1016 private object GetElementDefaultValue (XmlSchemaElement elem)
1018 // Unlike attribute, element cannot have a default value.
1019 if (elem.RefName == XmlQualifiedName.Empty)
1020 return elem.DefaultValue;
1021 XmlSchemaElement referenced = schema.Elements [elem.RefName] as XmlSchemaElement;
1022 if (referenced == null) // considering missing sub components
1023 return null;
1024 return referenced.DefaultValue;
1027 private object GetAttributeDefaultValue (XmlSchemaAttribute attr)
1029 #if BUGGY_MS_COMPATIBLE
1030 if (attr == null)
1031 return null;
1032 else if (attr.RefName != XmlQualifiedName.Empty) {
1033 XmlSchemaAttribute referenced = schema.Attributes [attr.RefName] as XmlSchemaAttribute;
1034 if (referenced != null)
1035 return referenced.DefaultValue;
1036 else
1037 return null;
1039 if (attr.DefaultValue != null)
1040 return attr.DefaultValue;
1041 return attr.FixedValue;
1042 #else
1043 if (attr.DefaultValue != null)
1044 return attr.DefaultValue;
1045 else if (attr.FixedValue != null)
1046 return attr.FixedValue;
1047 else if (attr.RefName == XmlQualifiedName.Empty)
1048 return null;
1049 XmlSchemaAttribute referenced = schema.Attributes [attr.RefName] as XmlSchemaAttribute;
1050 if (referenced == null) // considering missing sub components
1051 return null;
1052 if (referenced.DefaultValue != null)
1053 return referenced.DefaultValue;
1054 return referenced.FixedValue;
1055 #endif