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 // 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.
160 using System.Collections;
162 using System.Globalization;
164 using System.Xml.Schema;
167 namespace System.Data
169 internal class TableStructureCollection : CollectionBase
171 public void Add (TableStructure table)
176 public TableStructure this [int i] {
177 get { return List [i] as TableStructure; }
180 public TableStructure this [string name] {
182 foreach (TableStructure ts in List)
183 if (ts.Table.TableName == name)
190 internal class RelationStructureCollection : CollectionBase
192 public void Add (RelationStructure rel)
197 public RelationStructure this [int i] {
198 get { return List [i] as RelationStructure; }
201 public RelationStructure this [string parent, string child] {
203 foreach (RelationStructure rel in List)
204 if (rel.ParentTableName == parent && rel.ChildTableName == child)
211 internal class TableStructure
213 public TableStructure (DataTable 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)
230 foreach (DataColumn col in OrdinalColumns.Keys)
231 if (col.ColumnName == name)
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 ConstraintStructure
250 public readonly string TableName;
251 public readonly string [] Columns;
252 public readonly bool [] IsAttribute;
253 public readonly string ConstraintName;
254 public readonly bool IsPrimaryKey;
255 public readonly string ReferName;
256 public readonly bool IsNested;
257 public readonly bool IsConstraintOnly;
259 public ConstraintStructure (string tname, string [] cols, bool [] isAttr, string cname, bool isPK, string refName, bool isNested, bool isConstraintOnly)
263 IsAttribute = isAttr;
264 ConstraintName = XmlHelper.Decode (cname);
268 IsConstraintOnly = isConstraintOnly;
272 internal class XmlSchemaDataImporter
274 static readonly XmlSchemaDatatype schemaIntegerType;
275 static readonly XmlSchemaDatatype schemaDecimalType;
276 static readonly XmlSchemaComplexType schemaAnyType;
278 static XmlSchemaDataImporter ()
280 XmlSchema s = new XmlSchema ();
281 XmlSchemaAttribute a = new XmlSchemaAttribute ();
283 a.SchemaTypeName = new XmlQualifiedName ("integer", XmlSchema.Namespace);
285 XmlSchemaAttribute b = new XmlSchemaAttribute ();
287 b.SchemaTypeName = new XmlQualifiedName ("decimal", XmlSchema.Namespace);
289 XmlSchemaElement e = new XmlSchemaElement ();
294 schemaIntegerType = ((XmlSchemaSimpleType) a.AttributeSchemaType).Datatype;
295 schemaDecimalType = ((XmlSchemaSimpleType) b.AttributeSchemaType).Datatype;
296 schemaAnyType = e.ElementSchemaType as XmlSchemaComplexType;
298 schemaIntegerType = a.AttributeType as XmlSchemaDatatype;
299 schemaDecimalType = b.AttributeType as XmlSchemaDatatype;
300 schemaAnyType = e.ElementType as XmlSchemaComplexType;
310 ArrayList relations = new ArrayList ();
311 Hashtable reservedConstraints = new Hashtable ();
313 // such element that has an attribute msdata:IsDataSet="true"
314 XmlSchemaElement datasetElement;
316 // choice alternatives in the "dataset element"
317 ArrayList topLevelElements = new ArrayList ();
319 // import target elements
320 ArrayList targetElements = new ArrayList ();
322 TableStructure currentTable;
328 public XmlSchemaDataImporter (DataSet dataset, XmlReader reader, bool forDataSet)
330 this.dataset = dataset;
331 this.forDataSet = forDataSet;
332 dataset.DataSetName = "NewDataSet"; // Initialize always
333 schema = XmlSchema.Read (reader, null);
334 if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "schema" && reader.NamespaceURI == XmlSchema.Namespace)
335 reader.ReadEndElement ();
336 schema.Compile (null);
341 public void Process ()
343 if (schema.Id != null)
344 dataset.DataSetName = schema.Id; // default. Overridable by "DataSet element"
345 dataset.Namespace = schema.TargetNamespace;
347 // Find dataset element
348 foreach (XmlSchemaObject obj in schema.Items) {
349 XmlSchemaElement el = obj as XmlSchemaElement;
351 if (datasetElement == null &&
352 IsDataSetElement (el))
355 if (el.ElementSchemaType is XmlSchemaComplexType &&
356 el.ElementSchemaType != schemaAnyType)
358 if (el.ElementType is XmlSchemaComplexType &&
359 el.ElementType != schemaAnyType)
361 targetElements.Add (obj);
365 // make reservation of identity constraints
366 if (datasetElement != null) {
368 foreach (XmlSchemaObject obj in datasetElement.Constraints)
369 if (! (obj is XmlSchemaKeyref))
370 ReserveSelfIdentity ((XmlSchemaIdentityConstraint) obj);
372 foreach (XmlSchemaObject obj in datasetElement.Constraints)
373 if (obj is XmlSchemaKeyref)
374 ReserveRelationIdentity (datasetElement, (XmlSchemaKeyref) obj);
377 foreach (XmlSchemaObject obj in schema.Items) {
378 if (obj is XmlSchemaElement) {
379 XmlSchemaElement el = obj as XmlSchemaElement;
381 if (el.ElementSchemaType is XmlSchemaComplexType &&
382 el.ElementSchemaType != schemaAnyType)
384 if (el.ElementType is XmlSchemaComplexType &&
385 el.ElementType != schemaAnyType)
387 targetElements.Add (obj);
391 // This collection will grow up while processing elements.
392 int globalElementCount = targetElements.Count;
394 for (int i = 0; i < globalElementCount; i++)
395 ProcessGlobalElement ((XmlSchemaElement) targetElements [i]);
397 // Rest are local elements.
398 for (int i = globalElementCount; i < targetElements.Count; i++)
399 ProcessDataTableElement ((XmlSchemaElement) targetElements [i]);
401 // Handle relation definitions written as xs:annotation.
402 // See detail: http://msdn.microsoft.com/library/shared/happyUrl/fnf_msdn.asp?Redirect=%22http://msdn.microsoft.com/404/default.asp%22
403 foreach (XmlSchemaObject obj in schema.Items)
404 if (obj is XmlSchemaAnnotation)
405 HandleAnnotations ((XmlSchemaAnnotation) obj, false);
407 if (datasetElement != null) {
408 // Handle constraints in the DataSet element. First keys.
409 foreach (XmlSchemaObject obj in datasetElement.Constraints)
410 if (! (obj is XmlSchemaKeyref))
411 ProcessSelfIdentity (reservedConstraints [obj] as ConstraintStructure);
413 foreach (XmlSchemaObject obj in datasetElement.Constraints)
414 if (obj is XmlSchemaKeyref)
415 ProcessRelationIdentity (datasetElement, reservedConstraints [obj] as ConstraintStructure);
418 foreach (RelationStructure rs in this.relations)
419 dataset.Relations.Add (GenerateRelationship (rs));
422 private bool IsDataSetElement (XmlSchemaElement el)
424 if (el.UnhandledAttributes != null) {
425 foreach (XmlAttribute attr in el.UnhandledAttributes) {
426 if (attr.LocalName == "IsDataSet" &&
427 attr.NamespaceURI == XmlConstants.MsdataNamespace) {
428 switch (attr.Value) {
429 case "true": // case sensitive
434 throw new DataException (String.Format ("Value {0} is invalid for attribute 'IsDataSet'.", attr.Value));
440 if (schema.Elements.Count != 1)
442 if (!(el.SchemaType is XmlSchemaComplexType))
444 XmlSchemaComplexType ct = (XmlSchemaComplexType) el.SchemaType;
445 if (ct.AttributeUses.Count > 0)
447 XmlSchemaGroupBase gb = ct.ContentTypeParticle as XmlSchemaGroupBase;
448 if (gb == null || gb.Items.Count == 0)
450 foreach (XmlSchemaParticle p in gb.Items) {
451 if (ContainsColumn (p))
457 private bool ContainsColumn (XmlSchemaParticle p)
459 XmlSchemaElement el = p as XmlSchemaElement;
461 XmlSchemaComplexType ct = null;
463 ct = el.ElementSchemaType as XmlSchemaComplexType;
465 ct = el.ElementType as XmlSchemaComplexType;
467 if (ct == null || ct == schemaAnyType)
468 return true; // column element
469 if (ct.AttributeUses.Count > 0)
470 return false; // table element
471 if (ct.ContentType == XmlSchemaContentType.TextOnly)
472 return true; // column element
474 return false; // table element
476 XmlSchemaGroupBase gb = p as XmlSchemaGroupBase;
477 for (int i = 0; i < gb.Items.Count; i++) {
478 if (ContainsColumn ((XmlSchemaParticle) gb.Items [i]))
484 private void ProcessGlobalElement (XmlSchemaElement el)
486 // If it is already registered (by resolving reference
487 // in previously-imported elements), just ignore.
488 if (dataset.Tables.Contains (el.QualifiedName.Name))
491 // If type is not complex, just skip this element
493 if (! (el.ElementSchemaType is XmlSchemaComplexType && el.ElementSchemaType != schemaAnyType))
495 if (! (el.ElementType is XmlSchemaComplexType && el.ElementType != schemaAnyType))
499 if (IsDataSetElement (el)) {
500 ProcessDataSetElement (el);
504 dataset.Locale = CultureInfo.CurrentCulture;
506 // Register as a top-level element
507 topLevelElements.Add (el);
508 // Create DataTable for this element
509 ProcessDataTableElement (el);
512 private void ProcessDataSetElement (XmlSchemaElement el)
514 dataset.DataSetName = el.Name;
515 this.datasetElement = el;
517 // Search for locale attributes
518 bool useCurrent = false;
519 if (el.UnhandledAttributes != null) {
520 foreach (XmlAttribute attr in el.UnhandledAttributes) {
522 if (attr.LocalName == "UseCurrentLocale" &&
523 attr.NamespaceURI == XmlConstants.MsdataNamespace)
526 if (attr.LocalName == "Locale" &&
527 attr.NamespaceURI == XmlConstants.MsdataNamespace) {
528 CultureInfo ci = new CultureInfo (attr.Value);
534 if (!useCurrent && !dataset.LocaleSpecified) // then set current culture instance _explicitly_
535 dataset.Locale = CultureInfo.CurrentCulture;
538 // Process content type particle (and create DataTable)
539 XmlSchemaComplexType ct = null;
541 ct = el.ElementSchemaType as XmlSchemaComplexType;
543 ct = el.ElementType as XmlSchemaComplexType;
545 XmlSchemaParticle p = ct != null ? ct.ContentTypeParticle : null;
547 HandleDataSetContentTypeParticle (p);
550 private void HandleDataSetContentTypeParticle (XmlSchemaParticle p)
552 XmlSchemaElement el = p as XmlSchemaElement;
555 if (el.ElementSchemaType is XmlSchemaComplexType && el.RefName != el.QualifiedName)
557 if (el.ElementType is XmlSchemaComplexType && el.RefName != el.QualifiedName)
559 ProcessDataTableElement (el);
561 else if (p is XmlSchemaGroupBase) {
562 foreach (XmlSchemaParticle pc in ((XmlSchemaGroupBase) p).Items)
563 HandleDataSetContentTypeParticle (pc);
567 private void ProcessDataTableElement (XmlSchemaElement el)
569 string tableName = XmlHelper.Decode (el.QualifiedName.Name);
570 // If it is already registered, just ignore.
571 if (dataset.Tables.Contains (tableName))
574 DataTable table = new DataTable (tableName);
575 table.Namespace = el.QualifiedName.Namespace;
576 TableStructure oldTable = currentTable;
577 currentTable = new TableStructure (table);
579 dataset.Tables.Add (table);
582 if (el.UnhandledAttributes != null) {
583 foreach (XmlAttribute attr in el.UnhandledAttributes) {
584 if (attr.LocalName == "Locale" &&
585 attr.NamespaceURI == XmlConstants.MsdataNamespace)
586 table.Locale = new CultureInfo (attr.Value);
590 // Handle complex type (NOTE: It is (or should be)
591 // impossible the type is other than complex type).
592 XmlSchemaComplexType ct = null;
594 ct = (XmlSchemaComplexType) el.ElementSchemaType;
596 ct = (XmlSchemaComplexType) el.ElementType;
600 foreach (DictionaryEntry de in ct.AttributeUses)
601 ImportColumnAttribute ((XmlSchemaAttribute) de.Value);
603 // Handle content type particle
604 if (ct.ContentTypeParticle is XmlSchemaElement)
605 ImportColumnElement (el, (XmlSchemaElement) ct.ContentTypeParticle);
606 else if (ct.ContentTypeParticle is XmlSchemaGroupBase)
607 ImportColumnGroupBase (el, (XmlSchemaGroupBase) ct.ContentTypeParticle);
608 // else if null then do nothing.
610 // Handle simple content
611 switch (ct.ContentType) {
612 case XmlSchemaContentType.TextOnly:
613 // case XmlSchemaContentType.Mixed:
614 // LAMESPEC: When reading from XML Schema, it maps to "_text", while on the data inference, it is mapped to "_Text" (case ignorant).
615 string simpleName = el.QualifiedName.Name + "_text";
616 DataColumn simple = new DataColumn (simpleName);
617 simple.Namespace = el.QualifiedName.Namespace;
618 simple.AllowDBNull = (el.MinOccurs == 0);
619 simple.ColumnMapping = MappingType.SimpleContent;
620 simple.DataType = ConvertDatatype (ct.Datatype);
621 currentTable.NonOrdinalColumns.Add (simple);
625 // add columns to the table in specified order
626 // (by msdata:Ordinal attributes)
627 SortedList sd = new SortedList ();
628 foreach (DictionaryEntry de in currentTable.OrdinalColumns)
629 sd.Add (de.Value, de.Key);
630 foreach (DictionaryEntry de in sd)
631 table.Columns.Add ((DataColumn) de.Value);
632 foreach (DataColumn dc in currentTable.NonOrdinalColumns)
633 table.Columns.Add (dc);
635 currentTable = oldTable;
638 private DataRelation GenerateRelationship (RelationStructure rs)
640 DataTable ptab = dataset.Tables [rs.ParentTableName];
641 DataTable ctab = dataset.Tables [rs.ChildTableName];
644 string name = rs.ExplicitName != null ? rs.ExplicitName : XmlHelper.Decode (ptab.TableName) + '_' + XmlHelper.Decode (ctab.TableName);
646 // Annotation Relations belonging to a DataSet can contain multiple colnames
647 // in parentkey and childkey.
648 if (datasetElement != null) {
649 String[] pcolnames = rs.ParentColumnName.Split (null);
650 String[] ccolnames = rs.ChildColumnName.Split (null);
652 DataColumn[] pcol = new DataColumn [pcolnames.Length];
653 for (int i=0; i<pcol.Length; ++i)
654 pcol [i] = ptab.Columns [XmlHelper.Decode (pcolnames [i])];
656 DataColumn[] ccol = new DataColumn [ccolnames.Length];
657 for (int i=0; i < ccol.Length; ++i) {
658 ccol [i] = ctab.Columns [XmlHelper.Decode (ccolnames [i])];
659 if (ccol [i] == null)
660 ccol [i] = CreateChildColumn (pcol [i], ctab);
662 rel = new DataRelation (name, pcol, ccol, rs.CreateConstraint);
664 DataColumn pcol = ptab.Columns [XmlHelper.Decode (rs.ParentColumnName)];
665 DataColumn ccol = ctab.Columns [XmlHelper.Decode (rs.ChildColumnName)];
667 ccol = CreateChildColumn (pcol, ctab);
668 rel = new DataRelation (name, pcol, ccol, rs.CreateConstraint);
670 rel.Nested = rs.IsNested;
671 if (rs.CreateConstraint)
672 rel.ParentTable.PrimaryKey = rel.ParentColumns;
676 private DataColumn CreateChildColumn (DataColumn parentColumn, DataTable childTable)
678 DataColumn col = childTable.Columns.Add (parentColumn.ColumnName,
679 parentColumn.DataType);
680 col.Namespace = String.Empty;
681 col.ColumnMapping = MappingType.Hidden;
685 private void ImportColumnGroupBase (XmlSchemaElement parent, XmlSchemaGroupBase gb)
687 foreach (XmlSchemaParticle p in gb.Items) {
688 XmlSchemaElement el = p as XmlSchemaElement;
690 ImportColumnElement (parent, el);
691 else if (p is XmlSchemaGroupBase)
692 ImportColumnGroupBase (parent, (XmlSchemaGroupBase) p);
693 // otherwise p is xs:any
697 private XmlSchemaDatatype GetSchemaPrimitiveType (object type)
699 if (type is XmlSchemaComplexType)
700 return null; // It came here, so that maybe it is xs:anyType
701 XmlSchemaDatatype dt = type as XmlSchemaDatatype;
702 if (dt == null && type != null)
703 dt = ((XmlSchemaSimpleType) type).Datatype;
707 // Note that this column might be Hidden
708 private void ImportColumnAttribute (XmlSchemaAttribute attr)
710 DataColumn col = new DataColumn ();
711 col.ColumnName = attr.QualifiedName.Name;
712 col.Namespace = attr.QualifiedName.Namespace;
713 XmlSchemaDatatype dt = null;
715 dt = GetSchemaPrimitiveType (((XmlSchemaSimpleType) attr.AttributeSchemaType).Datatype);
717 dt = GetSchemaPrimitiveType (attr.AttributeType);
719 // This complicated check comes from the fact that
720 // MS.NET fails to map System.Object to anyType (that
721 // will cause ReadTypedObject() fail on XmlValidatingReader).
722 // ONLY In DataSet context, we set System.String for
724 col.DataType = ConvertDatatype (dt);
725 if (col.DataType == typeof (object))
726 col.DataType = typeof (string);
727 // When attribute use="prohibited", then it is regarded as
729 if (attr.Use == XmlSchemaUse.Prohibited)
730 col.ColumnMapping = MappingType.Hidden;
732 col.ColumnMapping = MappingType.Attribute;
733 col.DefaultValue = GetAttributeDefaultValue (attr);
735 if (attr.Use == XmlSchemaUse.Required)
736 col.AllowDBNull = false;
739 FillFacet (col, attr.AttributeSchemaType as XmlSchemaSimpleType);
741 FillFacet (col, attr.AttributeType as XmlSchemaSimpleType);
744 // Call this method after filling the name
745 ImportColumnMetaInfo (attr, attr.QualifiedName, col);
749 private void ImportColumnElement (XmlSchemaElement parent, XmlSchemaElement el)
751 // FIXME: element nest check
753 DataColumn col = new DataColumn ();
754 col.DefaultValue = GetElementDefaultValue (el);
755 col.AllowDBNull = (el.MinOccurs == 0);
758 if (el.ElementSchemaType is XmlSchemaComplexType && el.ElementSchemaType != schemaAnyType)
760 if (el.ElementType is XmlSchemaComplexType && el.ElementType != schemaAnyType)
762 FillDataColumnComplexElement (parent, el, col);
763 else if (el.MaxOccurs != 1)
764 FillDataColumnRepeatedSimpleElement (parent, el, col);
766 FillDataColumnSimpleElement (el, col);
769 // common process for element and attribute
770 private void ImportColumnMetaInfo (XmlSchemaAnnotated obj, XmlQualifiedName name, DataColumn col)
772 if (obj.UnhandledAttributes != null) {
773 foreach (XmlAttribute attr in obj.UnhandledAttributes) {
774 if (attr.NamespaceURI != XmlConstants.MsdataNamespace)
776 switch (attr.LocalName) {
777 case XmlConstants.Caption:
778 col.Caption = attr.Value;
780 case XmlConstants.DataType:
781 col.DataType = Type.GetType (attr.Value);
783 case XmlConstants.AutoIncrement:
784 col.AutoIncrement = bool.Parse (attr.Value);
786 case XmlConstants.AutoIncrementSeed:
787 col.AutoIncrementSeed = int.Parse (attr.Value);
789 case XmlConstants.AutoIncrementStep:
790 col.AutoIncrementStep = int.Parse (attr.Value);
792 case XmlConstants.ReadOnly:
793 col.ReadOnly = XmlConvert.ToBoolean (attr.Value);
795 case XmlConstants.Ordinal:
796 int ordinal = int.Parse (attr.Value);
803 private void FillDataColumnComplexElement (XmlSchemaElement parent, XmlSchemaElement el, DataColumn col)
805 if (targetElements.Contains (el))
806 return; // do nothing
808 string elName = XmlHelper.Decode (el.QualifiedName.Name);
809 if (elName == dataset.DataSetName)
810 // Well, why it is ArgumentException :-?
811 throw new ArgumentException ("Nested element must not have the same name as DataSet's name.");
813 if (el.Annotation != null)
814 HandleAnnotations (el.Annotation, true);
815 // If xsd:keyref xsd:key for this table exists, then don't add
816 // relation here manually.
817 else if (!DataSetDefinesKey (elName)) {
818 AddParentKeyColumn (parent, el, col);
820 RelationStructure rel = new RelationStructure ();
821 rel.ParentTableName = XmlHelper.Decode (parent.QualifiedName.Name);
822 rel.ChildTableName = elName;
823 rel.ParentColumnName = col.ColumnName;
824 rel.ChildColumnName = col.ColumnName;
825 rel.CreateConstraint = true;
830 // If the element is not referenced one, the element will be handled later.
831 if (el.RefName == XmlQualifiedName.Empty)
832 ProcessDataTableElement (el);
836 private bool DataSetDefinesKey (string name)
838 foreach (ConstraintStructure c in reservedConstraints.Values)
839 if (c.TableName == name && (c.IsPrimaryKey || c.IsNested))
844 private void AddParentKeyColumn (XmlSchemaElement parent, XmlSchemaElement el, DataColumn col)
846 if (currentTable.PrimaryKey != null)
849 // check name identity
850 string name = XmlHelper.Decode (parent.QualifiedName.Name) + "_Id";
852 while (currentTable.ContainsColumn (name))
853 name = String.Format ("{0}_{1}", name, count++);
854 // check existing primary key
855 if (currentTable.Table.PrimaryKey.Length > 0)
856 throw new DataException (String.Format ("There is already primary key columns in the table \"{0}\".", currentTable.Table.TableName));
858 col.ColumnName = name;
859 col.ColumnMapping = MappingType.Hidden;
860 col.Namespace = parent.QualifiedName.Namespace;
861 col.DataType = typeof (int);
862 col.AutoIncrement = true;
863 col.AllowDBNull = false;
865 ImportColumnMetaInfo (el, el.QualifiedName, col);
867 currentTable.PrimaryKey = col;
870 private void FillDataColumnRepeatedSimpleElement (XmlSchemaElement parent, XmlSchemaElement el, DataColumn col)
872 if (targetElements.Contains (el))
873 return; // do nothing
875 AddParentKeyColumn (parent, el, col);
876 DataColumn pkey = currentTable.PrimaryKey;
878 string elName = XmlHelper.Decode (el.QualifiedName.Name);
879 string parentName = XmlHelper.Decode (parent.QualifiedName.Name);
881 DataTable dt = new DataTable ();
882 dt.TableName = elName;
883 dt.Namespace = el.QualifiedName.Namespace;
884 // reference key column to parent
885 DataColumn cc = new DataColumn ();
886 cc.ColumnName = parentName + "_Id";
887 cc.Namespace = parent.QualifiedName.Namespace;
888 cc.ColumnMapping = MappingType.Hidden;
889 cc.DataType = typeof (int);
891 // repeatable content simple element
892 DataColumn cc2 = new DataColumn ();
893 cc2.ColumnName = elName + "_Column";
894 cc2.Namespace = el.QualifiedName.Namespace;
895 cc2.ColumnMapping = MappingType.SimpleContent;
896 cc2.AllowDBNull = false;
898 cc2.DataType = ConvertDatatype (GetSchemaPrimitiveType (el.ElementSchemaType));
900 cc2.DataType = ConvertDatatype (GetSchemaPrimitiveType (el.ElementType));
903 dt.Columns.Add (cc2);
905 dataset.Tables.Add (dt);
907 RelationStructure rel = new RelationStructure ();
908 rel.ParentTableName = parentName;
909 rel.ChildTableName = dt.TableName;
910 rel.ParentColumnName = pkey.ColumnName;
911 rel.ChildColumnName = cc.ColumnName;
913 rel.CreateConstraint = true;
917 private void FillDataColumnSimpleElement (XmlSchemaElement el, DataColumn col)
919 col.ColumnName = XmlHelper.Decode (el.QualifiedName.Name);
920 col.Namespace = el.QualifiedName.Namespace;
921 col.ColumnMapping = MappingType.Element;
923 col.DataType = ConvertDatatype (GetSchemaPrimitiveType (el.ElementSchemaType));
924 FillFacet (col, el.ElementSchemaType as XmlSchemaSimpleType);
926 col.DataType = ConvertDatatype (GetSchemaPrimitiveType (el.ElementType));
927 FillFacet (col, el.ElementType as XmlSchemaSimpleType);
930 ImportColumnMetaInfo (el, el.QualifiedName, col);
935 private void AddColumn (DataColumn col)
938 currentTable.NonOrdinalColumns.Add (col);
940 currentTable.OrdinalColumns.Add (col, col.Ordinal);
943 private void FillFacet (DataColumn col, XmlSchemaSimpleType st)
945 if (st == null || st.Content == null)
948 // Handle restriction facets
950 XmlSchemaSimpleTypeRestriction restriction = st == null ? null : st.Content as XmlSchemaSimpleTypeRestriction;
951 if (restriction == null)
952 throw new DataException ("DataSet does not suport 'list' nor 'union' simple type.");
954 foreach (XmlSchemaFacet f in restriction.Facets) {
955 if (f is XmlSchemaMaxLengthFacet)
956 // There is no reason why MaxLength is limited to int, except for the fact that DataColumn.MaxLength property is int.
957 col.MaxLength = int.Parse (f.Value);
961 private Type ConvertDatatype (XmlSchemaDatatype dt)
964 return typeof (string);
965 else if (dt.ValueType == typeof (decimal)) {
966 // LAMESPEC: MSDN documentation says it is based
967 // on ValueType. However, in the System.Xml.Schema
968 // context, xs:integer is mapped to Decimal, while
969 // in DataSet context it is mapped to Int64.
970 if (dt == schemaDecimalType)
971 return typeof (decimal);
972 else if (dt == schemaIntegerType)
973 return typeof (long);
975 return typeof (ulong);
981 // This method cuts out the local name of the last step from XPath.
982 // It is nothing more than hack. However, MS looks to do similar.
983 private string GetSelectorTarget (string xpath)
985 string tableName = xpath;
986 int index = tableName.LastIndexOf ('/');
987 // '>' is enough. If XPath [0] = '/', it is invalid.
988 // Selector can specify only element axes.
990 tableName = tableName.Substring (index + 1);
992 // Round QName to NSName
993 index = tableName.LastIndexOf (':');
995 tableName = tableName.Substring (index + 1);
997 return XmlHelper.Decode (tableName);
1000 private void ReserveSelfIdentity (XmlSchemaIdentityConstraint ic)
1002 string tableName = GetSelectorTarget (ic.Selector.XPath);
1004 string [] cols = new string [ic.Fields.Count];
1005 bool [] isAttrSpec = new bool [cols.Length];
1008 foreach (XmlSchemaXPath Field in ic.Fields) {
1009 string colName = Field.XPath;
1010 bool isAttr = colName.Length > 0 && colName [0] == '@';
1011 int index = colName.LastIndexOf (':');
1013 colName = colName.Substring (index + 1);
1015 colName = colName.Substring (1);
1017 colName = XmlHelper.Decode (colName);
1019 isAttrSpec [i] = isAttr;
1024 // find if there is an attribute with the constraint name
1025 // if not use the XmlSchemaConstraint's name.
1026 string constraintName = ic.Name;
1027 if (ic.UnhandledAttributes != null) {
1028 foreach (XmlAttribute attr in ic.UnhandledAttributes) {
1029 if (attr.NamespaceURI != XmlConstants.MsdataNamespace)
1031 switch (attr.LocalName) {
1032 case XmlConstants.ConstraintName:
1033 constraintName = attr.Value;
1035 case XmlConstants.PrimaryKey:
1036 isPK = bool.Parse(attr.Value);
1041 reservedConstraints.Add (ic,
1042 new ConstraintStructure (tableName, cols,
1043 isAttrSpec, constraintName, isPK, null, false, false));
1046 private void ProcessSelfIdentity (ConstraintStructure c)
1048 // Basic concept came from XmlSchemaMapper.cs
1050 string tableName = c.TableName;
1052 DataTable dt = dataset.Tables [tableName];
1055 throw new DataException (String.Format ("Invalid XPath selection inside selector. Cannot find: {0}", tableName));
1057 // nonexistent table name. .NET ignores it for DataTable.ReadXmlSchema().
1061 DataColumn [] cols = new DataColumn [c.Columns.Length];
1062 for (int i = 0; i < cols.Length; i++) {
1063 string colName = c.Columns [i];
1064 bool isAttr = c.IsAttribute [i];
1065 DataColumn col = dt.Columns [colName];
1067 throw new DataException (String.Format ("Invalid XPath selection inside field. Cannot find: {0}", tableName));
1068 if (isAttr && col.ColumnMapping != MappingType.Attribute)
1069 throw new DataException ("The XPath specified attribute field, but mapping type is not attribute.");
1070 if (!isAttr && col.ColumnMapping != MappingType.Element)
1071 throw new DataException ("The XPath specified simple element field, but mapping type is not simple element.");
1073 cols [i] = dt.Columns [colName];
1076 bool isPK = c.IsPrimaryKey;
1077 string constraintName = c.ConstraintName;
1078 dt.Constraints.Add (new UniqueConstraint (
1079 constraintName, cols, isPK));
1082 private void ReserveRelationIdentity (XmlSchemaElement element, XmlSchemaKeyref keyref)
1084 // Basic concept came from XmlSchemaMapper.cs
1086 string tableName = GetSelectorTarget (keyref.Selector.XPath);
1088 string [] cols = new string [keyref.Fields.Count];
1089 bool [] isAttrSpec = new bool [cols.Length];
1091 foreach (XmlSchemaXPath Field in keyref.Fields) {
1092 string colName = Field.XPath;
1093 bool isAttr = colName.Length > 0 && colName [0] == '@';
1094 int index = colName.LastIndexOf (':');
1096 colName = colName.Substring (index + 1);
1098 colName = colName.Substring (1);
1100 colName = XmlHelper.Decode (colName);
1102 isAttrSpec [i] = isAttr;
1105 string constraintName = keyref.Name;
1106 bool isNested = false;
1107 bool isConstraintOnly = false;
1108 if (keyref.UnhandledAttributes != null) {
1109 foreach (XmlAttribute attr in keyref.UnhandledAttributes) {
1110 if (attr.NamespaceURI != XmlConstants.MsdataNamespace)
1112 switch (attr.LocalName) {
1113 case XmlConstants.ConstraintName:
1114 constraintName = attr.Value;
1116 case XmlConstants.IsNested:
1117 if (attr.Value == "true")
1120 case XmlConstants.ConstraintOnly:
1121 if (attr.Value == "true")
1122 isConstraintOnly = true;
1128 reservedConstraints.Add (keyref, new ConstraintStructure (
1129 tableName, cols, isAttrSpec, constraintName,
1130 false, keyref.Refer.Name, isNested, isConstraintOnly));
1133 private void ProcessRelationIdentity (XmlSchemaElement element, ConstraintStructure c)
1135 // Basic concept came from XmlSchemaMapper.cs
1137 string tableName = c.TableName;
1140 DataTable dt = dataset.Tables [tableName];
1142 throw new DataException (String.Format ("Invalid XPath selection inside selector. Cannot find: {0}", tableName));
1144 cols = new DataColumn [c.Columns.Length];
1145 for (int i = 0; i < cols.Length; i++) {
1146 string colName = c.Columns [i];
1147 bool isAttr = c.IsAttribute [i];
1148 DataColumn col = dt.Columns [colName];
1149 if (isAttr && col.ColumnMapping != MappingType.Attribute)
1150 throw new DataException ("The XPath specified attribute field, but mapping type is not attribute.");
1151 if (!isAttr && col.ColumnMapping != MappingType.Element)
1152 throw new DataException ("The XPath specified simple element field, but mapping type is not simple element.");
1155 string name = c.ReferName;
1156 // get the unique constraint for the releation
1157 UniqueConstraint uniq = FindConstraint (name, element);
1159 ForeignKeyConstraint fkc = new ForeignKeyConstraint(c.ConstraintName, uniq.Columns, cols);
1160 dt.Constraints.Add (fkc);
1162 if (!c.IsConstraintOnly) {
1163 // generate the relation.
1164 DataRelation rel = new DataRelation (c.ConstraintName, uniq.Columns, cols, true);
1165 rel.Nested = c.IsNested;
1166 rel.SetParentKeyConstraint (uniq);
1167 rel.SetChildKeyConstraint (fkc);
1169 dataset.Relations.Add (rel);
1173 // get the unique constraint for the relation.
1174 // name - the name of the XmlSchemaUnique element
1175 private UniqueConstraint FindConstraint (string name, XmlSchemaElement element)
1177 // Copied from XmlSchemaMapper.cs
1179 // find the element in the constraint collection.
1180 foreach (XmlSchemaIdentityConstraint c in element.Constraints) {
1181 if (c is XmlSchemaKeyref)
1184 if (c.Name == name) {
1185 string tableName = GetSelectorTarget (c.Selector.XPath);
1187 // find the table in the dataset.
1188 DataTable dt = dataset.Tables [tableName];
1190 string constraintName = c.Name;
1191 // find if there is an attribute with the constraint name
1192 // if not use the XmlSchemaUnique name.
1193 if (c.UnhandledAttributes != null)
1194 foreach (XmlAttribute attr in c.UnhandledAttributes)
1195 if (attr.LocalName == "ConstraintName" && attr.NamespaceURI == XmlConstants.MsdataNamespace)
1196 constraintName = attr.Value;
1197 return (UniqueConstraint) dt.Constraints [constraintName];
1200 throw new DataException ("Target identity constraint was not found: " + name);
1203 private void HandleAnnotations (XmlSchemaAnnotation an, bool nested)
1205 foreach (XmlSchemaObject content in an.Items) {
1206 XmlSchemaAppInfo ai = content as XmlSchemaAppInfo;
1208 foreach (XmlNode n in ai.Markup) {
1209 XmlElement el = n as XmlElement;
1210 if (el != null && el.LocalName == "Relationship" && el.NamespaceURI == XmlConstants.MsdataNamespace)
1211 HandleRelationshipAnnotation (el, nested);
1217 private void HandleRelationshipAnnotation (XmlElement el, bool nested)
1219 string name = el.GetAttribute ("name");
1220 string ptn = el.GetAttribute ("parent", XmlConstants.MsdataNamespace);
1221 string ctn = el.GetAttribute ("child", XmlConstants.MsdataNamespace);
1222 string pkn = el.GetAttribute ("parentkey", XmlConstants.MsdataNamespace);
1223 string fkn = el.GetAttribute ("childkey", XmlConstants.MsdataNamespace);
1225 RelationStructure rel = new RelationStructure ();
1226 rel.ExplicitName = XmlHelper.Decode (name);
1227 rel.ParentTableName = XmlHelper.Decode (ptn);
1228 rel.ChildTableName = XmlHelper.Decode (ctn);
1229 // ColumnNames will be decoded wherever they are used as they can
1230 // contain 'space' separated list of column-names.
1231 rel.ParentColumnName = pkn;
1232 rel.ChildColumnName = fkn;
1233 rel.IsNested = nested;
1234 rel.CreateConstraint = false; // by default?
1235 relations.Add (rel);
1238 private object GetElementDefaultValue (XmlSchemaElement elem)
1240 // Unlike attribute, element cannot have a default value.
1241 if (elem.RefName == XmlQualifiedName.Empty)
1242 return elem.DefaultValue;
1243 XmlSchemaElement referenced = schema.Elements [elem.RefName] as XmlSchemaElement;
1244 if (referenced == null) // considering missing sub components
1246 return referenced.DefaultValue;
1249 private object GetAttributeDefaultValue (XmlSchemaAttribute attr)
1251 #if BUGGY_MS_COMPATIBLE
1254 else if (attr.RefName != XmlQualifiedName.Empty) {
1255 XmlSchemaAttribute referenced = schema.Attributes [attr.RefName] as XmlSchemaAttribute;
1256 if (referenced != null)
1257 return referenced.DefaultValue;
1261 if (attr.DefaultValue != null)
1262 return attr.DefaultValue;
1263 return attr.FixedValue;
1265 if (attr.DefaultValue != null)
1266 return attr.DefaultValue;
1267 else if (attr.FixedValue != null)
1268 return attr.FixedValue;
1269 else if (attr.RefName == XmlQualifiedName.Empty)
1271 XmlSchemaAttribute referenced = schema.Attributes [attr.RefName] as XmlSchemaAttribute;
1272 if (referenced == null) // considering missing sub components
1274 if (referenced.DefaultValue != null)
1275 return referenced.DefaultValue;
1276 return referenced.FixedValue;