2 // XmlSchemaDataImporter.cs
5 // Atsushi Enomoto <atsushi@ximian.com>
10 // ***** The design note became somewhat obsolete. Should be rewritten. *****
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
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).
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
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.
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
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
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
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 using System.Collections;
139 using System.Globalization;
141 using System.Xml.Schema;
144 namespace System.Data
146 internal class TableStructureCollection : CollectionBase
148 public void Add (TableStructure table)
153 public TableStructure this [int i] {
154 get { return List [i] as TableStructure; }
157 public TableStructure this [string name] {
159 foreach (TableStructure ts in List)
160 if (ts.Table.TableName == name)
167 internal class RelationStructureCollection : CollectionBase
169 public void Add (RelationStructure rel)
174 public RelationStructure this [int i] {
175 get { return List [i] as RelationStructure; }
178 public RelationStructure this [string parent, string child] {
180 foreach (RelationStructure rel in List)
181 if (rel.ParentTableName == parent && rel.ChildTableName == child)
188 internal class TableStructure
190 public TableStructure (DataTable table)
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;
202 public bool ContainsColumn (string name)
204 foreach (DataColumn col in NonOrdinalColumns)
205 if (col.ColumnName == name)
207 foreach (DataColumn col in OrdinalColumns.Keys)
208 if (col.ColumnName == name)
214 internal class RelationStructure
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;
225 internal class XmlSchemaDataImporter
227 static readonly XmlSchemaDatatype schemaIntegerType;
228 static readonly XmlSchemaDatatype schemaDecimalType;
229 static readonly XmlSchemaComplexType schemaAnyType;
231 static XmlSchemaDataImporter ()
233 XmlSchema s = new XmlSchema ();
234 XmlSchemaAttribute a = new XmlSchemaAttribute ();
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);
240 XmlSchemaAttribute b = new XmlSchemaAttribute ();
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);
246 XmlSchemaElement e = new XmlSchemaElement ();
250 schemaIntegerType = a.AttributeType as XmlSchemaDatatype;
251 schemaDecimalType = b.AttributeType as XmlSchemaDatatype;
252 schemaAnyType = e.ElementType as XmlSchemaComplexType;
260 ArrayList relations = new ArrayList ();
262 // such element that has an attribute msdata:IsDataSet="true"
263 XmlSchemaElement datasetElement;
265 // choice alternatives in the "dataset element"
266 ArrayList topLevelElements = new ArrayList ();
268 // import target elements
269 ArrayList targetElements = new ArrayList ();
271 TableStructure currentTable;
277 public XmlSchemaDataImporter (DataSet dataset, XmlReader reader)
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);
290 public void Process ()
292 if (schema.Id != null)
293 dataset.DataSetName = schema.Id; // default. Overridable by "DataSet element"
294 dataset.Namespace = schema.TargetNamespace;
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);
305 // This collection will grow up while processing elements.
306 int globalElementCount = targetElements.Count;
308 for (int i = 0; i < globalElementCount; i++)
309 ProcessGlobalElement ((XmlSchemaElement) targetElements [i]);
311 // Rest are local elements.
312 for (int i = globalElementCount; i < targetElements.Count; i++)
313 ProcessDataTableElement ((XmlSchemaElement) targetElements [i]);
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);
321 foreach (RelationStructure rs in this.relations)
322 dataset.Relations.Add (GenerateRelationship (rs));
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);
330 foreach (XmlSchemaObject obj in datasetElement.Constraints)
331 if (obj is XmlSchemaKeyref)
332 ProcessReferenceKey (datasetElement, (XmlSchemaKeyref) obj);
336 private bool IsDataSetElement (XmlSchemaElement el)
338 if (schema.Elements.Count != 1)
340 if (!(el.SchemaType is XmlSchemaComplexType))
342 XmlSchemaComplexType ct = (XmlSchemaComplexType) el.SchemaType;
343 if (ct.AttributeUses.Count > 0)
345 XmlSchemaGroupBase gb = ct.ContentTypeParticle as XmlSchemaGroupBase;
346 if (gb == null || gb.Items.Count == 0)
348 foreach (XmlSchemaParticle p in gb.Items) {
349 if (ContainsColumn (p))
355 private bool ContainsColumn (XmlSchemaParticle p)
357 XmlSchemaElement el = p as XmlSchemaElement;
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
369 return false; // table element
372 XmlSchemaGroupBase gb = p as XmlSchemaGroupBase;
373 for (int i = 0; i < gb.Items.Count; i++) {
374 if (ContainsColumn ((XmlSchemaParticle) gb.Items [i]))
380 private void ProcessGlobalElement (XmlSchemaElement el)
382 // If it is already registered (by resolving reference
383 // in previously-imported elements), just ignore.
384 if (dataset.Tables.Contains (el.QualifiedName.Name))
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);
399 throw new DataException (String.Format ("Value {0} is invalid for attribute 'IsDataSet'.", attr.Value));
405 // If type is not complex, just skip this element
406 if (! (el.ElementType is XmlSchemaComplexType && el.ElementType != schemaAnyType))
409 if (IsDataSetElement (el)) {
410 ProcessDataSetElement (el);
414 // Register as a top-level element
415 topLevelElements.Add (el);
416 // Create DataTable for this element
417 ProcessDataTableElement (el);
420 private void ProcessDataSetElement (XmlSchemaElement el)
422 dataset.DataSetName = el.Name;
423 this.datasetElement = el;
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);
436 // Process content type particle (and create DataTable)
437 XmlSchemaComplexType ct = el.ElementType as XmlSchemaComplexType;
438 XmlSchemaParticle p = ct != null ? ct.ContentTypeParticle : null;
440 HandleDataSetContentTypeParticle (p);
443 private void HandleDataSetContentTypeParticle (XmlSchemaParticle p)
445 XmlSchemaElement el = p as XmlSchemaElement;
447 if (el.ElementType is XmlSchemaComplexType && el.RefName != el.QualifiedName)
448 ProcessDataTableElement (el);
450 else if (p is XmlSchemaGroupBase) {
451 foreach (XmlSchemaParticle pc in ((XmlSchemaGroupBase) p).Items)
452 HandleDataSetContentTypeParticle (pc);
456 private void ProcessDataTableElement (XmlSchemaElement el)
458 string tableName = XmlConvert.DecodeName (el.QualifiedName.Name);
459 // If it is already registered, just ignore.
460 if (dataset.Tables.Contains (tableName))
463 DataTable table = new DataTable (tableName);
464 currentTable = new TableStructure (table);
466 dataset.Tables.Add (table);
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);
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;
482 foreach (DictionaryEntry de in ct.AttributeUses)
483 ImportColumnAttribute ((XmlSchemaAttribute) de.Value);
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.
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);
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);
518 private DataRelation GenerateRelationship (RelationStructure rs)
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];
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);
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;
542 private void ImportColumnGroupBase (XmlSchemaElement parent, XmlSchemaGroupBase gb)
544 foreach (XmlSchemaParticle p in gb.Items) {
545 XmlSchemaElement el = p as XmlSchemaElement;
547 ImportColumnElement (parent, el);
549 ImportColumnGroupBase (parent, (XmlSchemaGroupBase) p);
553 private XmlSchemaDatatype GetSchemaPrimitiveType (object type)
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;
563 // Note that this column might be Hidden
564 private void ImportColumnAttribute (XmlSchemaAttribute attr)
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
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
580 if (attr.Use == XmlSchemaUse.Prohibited)
581 col.ColumnMapping = MappingType.Hidden;
583 col.ColumnMapping = MappingType.Attribute;
584 col.DefaultValue = GetAttributeDefaultValue (attr);
586 if (attr.Use == XmlSchemaUse.Required)
587 col.AllowDBNull = false;
589 FillFacet (col, attr.AttributeType as XmlSchemaSimpleType);
591 // Call this method after filling the name
592 ImportColumnMetaInfo (attr, attr.QualifiedName, col);
596 private void ImportColumnElement (XmlSchemaElement parent, XmlSchemaElement el)
598 // FIXME: element nest check
600 DataColumn col = new DataColumn ();
601 col.DefaultValue = GetElementDefaultValue (el);
602 col.AllowDBNull = (el.MinOccurs == 0);
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);
609 FillDataColumnSimpleElement (el, col);
612 // common process for element and attribute
613 private void ImportColumnMetaInfo (XmlSchemaAnnotated obj, XmlQualifiedName name, DataColumn col)
616 if (obj.UnhandledAttributes != null) {
617 foreach (XmlAttribute attr in obj.UnhandledAttributes) {
618 if (attr.NamespaceURI != XmlConstants.MsdataNamespace)
620 switch (attr.LocalName) {
621 case XmlConstants.Caption:
622 col.Caption = attr.Value;
624 case XmlConstants.DataType:
625 col.DataType = Type.GetType (attr.Value);
627 case XmlConstants.AutoIncrement:
628 col.AutoIncrement = bool.Parse (attr.Value);
630 case XmlConstants.AutoIncrementSeed:
631 col.AutoIncrementSeed = int.Parse (attr.Value);
633 case XmlConstants.Ordinal:
634 ordinal = int.Parse (attr.Value);
641 private void FillDataColumnComplexElement (XmlSchemaElement parent, XmlSchemaElement el, DataColumn col)
643 if (targetElements.Contains (el))
644 return; // do nothing
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.");
651 if (el.Annotation != null)
652 HandleAnnotations (el.Annotation, true);
654 AddParentKeyColumn (parent, el, col);
655 DataColumn pkey = currentTable.PrimaryKey;
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;
667 // If the element is not referenced one, the element will be handled later.
668 if (el.RefName == XmlQualifiedName.Empty)
669 targetElements.Add (el);
673 private void AddParentKeyColumn (XmlSchemaElement parent, XmlSchemaElement el, DataColumn col)
675 if (currentTable.PrimaryKey != null)
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));
686 col.ColumnName = name;
687 col.ColumnMapping = MappingType.Hidden;
688 col.Namespace = parent.QualifiedName.Namespace;
689 col.DataType = typeof (int);
691 col.AutoIncrement = true;
692 col.AllowDBNull = false;
694 ImportColumnMetaInfo (el, el.QualifiedName, col);
696 currentTable.PrimaryKey = col;
699 private void FillDataColumnRepeatedSimpleElement (XmlSchemaElement parent, XmlSchemaElement el, DataColumn col)
701 if (targetElements.Contains (el))
702 return; // do nothing
704 AddParentKeyColumn (parent, el, col);
705 DataColumn pkey = currentTable.PrimaryKey;
707 string elName = XmlConvert.DecodeName (el.QualifiedName.Name);
708 string parentName = XmlConvert.DecodeName (parent.QualifiedName.Name);
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);
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));
727 dt.Columns.Add (cc2);
729 dataset.Tables.Add (dt);
731 RelationStructure rel = new RelationStructure ();
732 rel.ParentTableName = parentName;
733 rel.ChildTableName = dt.TableName;
734 rel.ParentColumnName = pkey.ColumnName;
735 rel.ChildColumnName = cc.ColumnName;
737 rel.CreateConstraint = true;
741 private void FillDataColumnSimpleElement (XmlSchemaElement el, DataColumn col)
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);
749 ImportColumnMetaInfo (el, el.QualifiedName, col);
754 private void AddColumn (DataColumn col)
757 currentTable.NonOrdinalColumns.Add (col);
759 currentTable.OrdinalColumns.Add (col, col.Ordinal);
762 private void FillFacet (DataColumn col, XmlSchemaSimpleType st)
764 if (st == null || st.Content == null)
767 // Handle restriction facets
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.");
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);
780 private Type ConvertDatatype (XmlSchemaDatatype dt)
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);
794 return typeof (ulong);
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)
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.
809 tableName = tableName.Substring (index + 1);
811 // Round QName to NSName
812 index = tableName.LastIndexOf (':');
814 tableName = tableName.Substring (index + 1);
816 return XmlConvert.DecodeName (tableName);
819 private void ProcessParentKey (XmlSchemaIdentityConstraint ic)
821 // Basic concept came from XmlSchemaMapper.cs
823 string tableName = GetSelectorTarget (ic.Selector.XPath);
825 DataTable dt = dataset.Tables [tableName];
827 throw new DataException (String.Format ("Invalid XPath selection inside selector. Cannot find: {0}", tableName));
829 DataColumn [] cols = new DataColumn [ic.Fields.Count];
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);
836 colName = colName.Substring (index + 1);
838 colName = colName.Substring (1);
840 colName = XmlConvert.DecodeName (colName);
841 DataColumn col = dt.Columns [colName];
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.");
849 cols [i] = dt.Columns [colName];
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)
861 switch (attr.LocalName) {
862 case XmlConstants.ConstraintName:
863 constraintName = attr.Value;
865 case XmlConstants.PrimaryKey:
866 isPK = bool.Parse(attr.Value);
871 UniqueConstraint c = new UniqueConstraint (constraintName, cols, isPK);
872 dt.Constraints.Add (c);
875 private void ProcessReferenceKey (XmlSchemaElement element, XmlSchemaKeyref keyref)
877 // Basic concept came from XmlSchemaMapper.cs
879 string tableName = GetSelectorTarget (keyref.Selector.XPath);
882 DataTable dt = dataset.Tables [tableName];
884 throw new DataException (String.Format ("Invalid XPath selection inside selector. Cannot find: {0}", tableName));
886 cols = new DataColumn [keyref.Fields.Count];
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);
893 colName = colName.Substring (index + 1);
895 colName = colName.Substring (1);
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.");
906 string name = keyref.Refer.Name;
907 // get the unique constraint for the releation
908 UniqueConstraint uniq = FindConstraint (name, element);
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)
919 rel.SetParentKeyConstraint (uniq);
920 rel.SetChildKeyConstraint (fkc);
922 dataset.Relations.Add (rel);
925 // get the unique constraint for the relation.
926 // name - the name of the XmlSchemaUnique element
927 private UniqueConstraint FindConstraint (string name, XmlSchemaElement element)
929 // Copied from XmlSchemaMapper.cs
931 // find the element in the constraint collection.
932 foreach (XmlSchemaIdentityConstraint c in element.Constraints) {
933 if (c is XmlSchemaKeyref)
936 if (c.Name == name) {
937 string tableName = GetSelectorTarget (c.Selector.XPath);
939 // find the table in the dataset.
940 DataTable dt = dataset.Tables [tableName];
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];
952 throw new DataException ("Target identity constraint was not found: " + name);
955 private void HandleAnnotations (XmlSchemaAnnotation an, bool nested)
957 foreach (XmlSchemaObject content in an.Items) {
958 XmlSchemaAppInfo ai = content as XmlSchemaAppInfo;
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);
969 private void HandleRelationshipAnnotation (XmlElement el, bool nested)
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);
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?
988 private object GetElementDefaultValue (XmlSchemaElement elem)
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
996 return referenced.DefaultValue;
999 private object GetAttributeDefaultValue (XmlSchemaAttribute attr)
1001 #if BUGGY_MS_COMPATIBLE
1004 else if (attr.RefName != XmlQualifiedName.Empty) {
1005 XmlSchemaAttribute referenced = schema.Attributes [attr.RefName] as XmlSchemaAttribute;
1006 if (referenced != null)
1007 return referenced.DefaultValue;
1011 if (attr.DefaultValue != null)
1012 return attr.DefaultValue;
1013 return attr.FixedValue;
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)
1021 XmlSchemaAttribute referenced = schema.Attributes [attr.RefName] as XmlSchemaAttribute;
1022 if (referenced == null) // considering missing sub components
1024 if (referenced.DefaultValue != null)
1025 return referenced.DefaultValue;
1026 return referenced.FixedValue;