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 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 ();
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
);
263 XmlSchemaAttribute b
= new XmlSchemaAttribute ();
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
);
269 XmlSchemaElement e
= new XmlSchemaElement ();
273 schemaIntegerType
= a
.AttributeType
as XmlSchemaDatatype
;
274 schemaDecimalType
= b
.AttributeType
as XmlSchemaDatatype
;
275 schemaAnyType
= e
.ElementType
as XmlSchemaComplexType
;
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
;
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);
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
);
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)
363 if (!(el
.SchemaType
is XmlSchemaComplexType
))
365 XmlSchemaComplexType ct
= (XmlSchemaComplexType
) el
.SchemaType
;
366 if (ct
.AttributeUses
.Count
> 0)
368 XmlSchemaGroupBase gb
= ct
.ContentTypeParticle
as XmlSchemaGroupBase
;
369 if (gb
== null || gb
.Items
.Count
== 0)
371 foreach (XmlSchemaParticle p
in gb
.Items
) {
372 if (ContainsColumn (p
))
378 private bool ContainsColumn (XmlSchemaParticle p
)
380 XmlSchemaElement el
= p
as XmlSchemaElement
;
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
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
]))
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
))
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
);
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
))
432 if (IsDataSetElement (el
)) {
433 ProcessDataSetElement (el
);
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
);
459 // Process content type particle (and create DataTable)
460 XmlSchemaComplexType ct
= el
.ElementType
as XmlSchemaComplexType
;
461 XmlSchemaParticle p
= ct
!= null ? ct
.ContentTypeParticle
: null;
463 HandleDataSetContentTypeParticle (p
);
466 private void HandleDataSetContentTypeParticle (XmlSchemaParticle p
)
468 XmlSchemaElement el
= p
as XmlSchemaElement
;
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
))
486 DataTable table
= new DataTable (tableName
);
487 table
.Namespace
= el
.QualifiedName
.Namespace
;
488 currentTable
= new TableStructure (table
);
490 dataset
.Tables
.Add (table
);
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
;
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
);
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
];
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
;
566 private void ImportColumnGroupBase (XmlSchemaElement parent
, XmlSchemaGroupBase gb
)
568 foreach (XmlSchemaParticle p
in gb
.Items
) {
569 XmlSchemaElement el
= p
as XmlSchemaElement
;
571 ImportColumnElement (parent
, el
);
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
;
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
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
604 if (attr
.Use
== XmlSchemaUse
.Prohibited
)
605 col
.ColumnMapping
= MappingType
.Hidden
;
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
);
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
);
633 FillDataColumnSimpleElement (el
, col
);
636 // common process for element and attribute
637 private void ImportColumnMetaInfo (XmlSchemaAnnotated obj
, XmlQualifiedName name
, DataColumn col
)
640 if (obj
.UnhandledAttributes
!= null) {
641 foreach (XmlAttribute attr
in obj
.UnhandledAttributes
) {
642 if (attr
.NamespaceURI
!= XmlConstants
.MsdataNamespace
)
644 switch (attr
.LocalName
) {
645 case XmlConstants
.Caption
:
646 col
.Caption
= attr
.Value
;
648 case XmlConstants
.DataType
:
649 col
.DataType
= Type
.GetType (attr
.Value
);
651 case XmlConstants
.AutoIncrement
:
652 col
.AutoIncrement
= bool.Parse (attr
.Value
);
654 case XmlConstants
.AutoIncrementSeed
:
655 col
.AutoIncrementSeed
= int.Parse (attr
.Value
);
657 case XmlConstants
.ReadOnly
:
658 col
.ReadOnly
= XmlConvert
.ToBoolean (attr
.Value
);
660 case XmlConstants
.Ordinal
:
661 ordinal
= int.Parse (attr
.Value
);
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);
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;
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)
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);
718 col
.AutoIncrement
= true;
719 col
.AllowDBNull
= false;
721 ImportColumnMetaInfo (el
, el
.QualifiedName
, 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
);
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
;
765 rel
.CreateConstraint
= true;
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
);
782 private void AddColumn (DataColumn col
)
785 currentTable
.NonOrdinalColumns
.Add (col
);
787 currentTable
.OrdinalColumns
.Add (col
, col
.Ordinal
);
790 private void FillFacet (DataColumn col
, XmlSchemaSimpleType st
)
792 if (st
== null || st
.Content
== null)
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
)
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);
822 return typeof (ulong);
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.
837 tableName
= tableName
.Substring (index
+ 1);
839 // Round QName to NSName
840 index
= tableName
.LastIndexOf (':');
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
];
855 throw new DataException (String
.Format ("Invalid XPath selection inside selector. Cannot find: {0}", tableName
));
857 DataColumn
[] cols
= new DataColumn
[ic
.Fields
.Count
];
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 (':');
864 colName
= colName
.Substring (index
+ 1);
866 colName
= colName
.Substring (1);
868 colName
= XmlConvert
.DecodeName (colName
);
869 DataColumn col
= dt
.Columns
[colName
];
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
];
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
)
889 switch (attr
.LocalName
) {
890 case XmlConstants
.ConstraintName
:
891 constraintName
= attr
.Value
;
893 case XmlConstants
.PrimaryKey
:
894 isPK
= bool.Parse(attr
.Value
);
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
);
910 DataTable dt
= dataset
.Tables
[tableName
];
912 throw new DataException (String
.Format ("Invalid XPath selection inside selector. Cannot find: {0}", tableName
));
914 cols
= new DataColumn
[keyref
.Fields
.Count
];
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 (':');
921 colName
= colName
.Substring (index
+ 1);
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.");
934 string name
= keyref
.Refer
.Name
;
935 // get the unique constraint for the releation
936 UniqueConstraint uniq
= FindConstraint (name
, element
);
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
)
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
)
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
;
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
1024 return referenced
.DefaultValue
;
1027 private object GetAttributeDefaultValue (XmlSchemaAttribute attr
)
1029 #if BUGGY_MS_COMPATIBLE
1032 else if (attr
.RefName
!= XmlQualifiedName
.Empty
) {
1033 XmlSchemaAttribute referenced
= schema
.Attributes
[attr
.RefName
] as XmlSchemaAttribute
;
1034 if (referenced
!= null)
1035 return referenced
.DefaultValue
;
1039 if (attr
.DefaultValue
!= null)
1040 return attr
.DefaultValue
;
1041 return attr
.FixedValue
;
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
)
1049 XmlSchemaAttribute referenced
= schema
.Attributes
[attr
.RefName
] as XmlSchemaAttribute
;
1050 if (referenced
== null) // considering missing sub components
1052 if (referenced
.DefaultValue
!= null)
1053 return referenced
.DefaultValue
;
1054 return referenced
.FixedValue
;