2 // mcs/class/System.Data/System.Data/XmlSchemaMapper.cs
4 // Purpose: Maps XmlSchema to DataSet
6 // class: XmlSchemaMapper
7 // assembly: System.Data.dll
8 // namespace: System.Data
11 // Ville Palo <vi64pa@koti.soon.fi>
13 // (C) 2002 Ville Palo
20 using System.Xml.Schema;
21 using System.Collections;
22 using System.Globalization;
24 namespace System.Data {
26 internal class XmlSchemaMapper
31 private DataTable table;
32 enum ElementType {ELEMENT_UNDEFINED, ELEMENT_TABLE, ELEMENT_COLUMN};
33 private Hashtable TypeCollection = new Hashtable ();
34 private Hashtable ElementCollection = new Hashtable ();
40 public XmlSchemaMapper (DataSet dataset)
45 public XmlSchemaMapper (DataTable datatable)
50 #endregion // Constructors
52 #region Public methods
54 public void Read (XmlReader Reader)
56 XmlSchema Schema = XmlSchema.Read (Reader, new ValidationEventHandler (OnXmlSchemaValidation));
57 if (Reader.NodeType == XmlNodeType.Element)
58 Reader.ReadEndElement ();
59 if (DSet != null) DSet.Namespace = Schema.TargetNamespace;
62 foreach (XmlSchemaObject Item in Schema.Items)
63 ReadXmlSchemaItem (Item);
66 #endregion // Public methods
68 #region Private methods
70 private void ReadXmlSchemaItem (XmlSchemaObject Item)
72 XmlSchemaObject SchemaObject;
74 if (Item is XmlSchemaType)
75 ReadXmlSchemaType ((XmlSchemaType)Item);
76 else if (Item is XmlSchemaElement)
77 ReadXmlSchemaElement (Item as XmlSchemaElement, ElementType.ELEMENT_UNDEFINED);
80 private void ReadXmlSchemaSequence (XmlSchemaSequence Sequence)
82 ReadXmlSchemaSequence (Sequence, null);
85 private void ReadXmlSchemaSequence (XmlSchemaSequence Sequence, DataTable Table)
87 foreach (XmlSchemaObject TempObj in Sequence.Items) {
88 if (TempObj is XmlSchemaElement){
89 XmlSchemaElement schemaElement = (XmlSchemaElement)TempObj;
90 // the element can be a Column or a Table
91 // tables do not have a type.
92 if (schemaElement.SchemaTypeName.Name.Length > 0 || (schemaElement.SchemaType is XmlSchemaSimpleType))
93 ReadXmlSchemaElement (schemaElement, ElementType.ELEMENT_COLUMN, Table);
95 ReadXmlSchemaElement (schemaElement, ElementType.ELEMENT_TABLE, Table);
100 private void ReadXmlSchemaChoice (XmlSchemaChoice Choice)
102 XmlSchemaObject SchemaObject;
103 foreach (XmlSchemaObject TempObject in Choice.Items) {
104 if ((SchemaObject = TempObject as XmlSchemaElement) != null)
105 ReadXmlSchemaElement ((XmlSchemaElement)SchemaObject, ElementType.ELEMENT_TABLE);
109 private void ReadXmlSchemaElement (XmlSchemaElement Element)
111 ReadXmlSchemaElement (Element, ElementType.ELEMENT_UNDEFINED);
114 private void ReadXmlSchemaElement (XmlSchemaElement Element, ElementType ElType)
116 ReadXmlSchemaElement (Element, ElType, null);
119 private void ReadXmlSchemaElement (XmlSchemaElement Element, ElementType ElType, DataTable Table)
121 Hashtable Attributes = ReadUnhandledAttributes (Element.UnhandledAttributes);
122 DataTable Table2 = null;
124 if (Attributes.Contains (XmlConstants.IsDataSet)) { // DataSet -elemt
126 if (String.Compare (Attributes [XmlConstants.IsDataSet].ToString (), "true", true) == 0 && DSet != null)
127 DSet.DataSetName = Element.Name;
129 if (Attributes.Contains (XmlConstants.Locale)) {
130 CultureInfo cinfo = new CultureInfo((String)Attributes [XmlConstants.Locale]);
131 if (DSet != null) DSet.Locale = cinfo;
132 else table.Locale = cinfo;
135 else if (Element.SchemaTypeName != null && Element.SchemaTypeName.Namespace != XmlConstants.SchemaNamespace
136 && Element.SchemaTypeName.Name != String.Empty) {
139 // If type is not standard type
142 if (DSet == null) throw new InvalidOperationException ("Schema not valid for a DataTable");
144 DataTable TempTable = new DataTable (Element.Name);
145 DSet.Tables.Add (TempTable);
147 // If type is already defined in schema read it...
148 if (TypeCollection.Contains (Element.SchemaTypeName.ToString ()))
149 ReadXmlSchemaType ((XmlSchemaType)TypeCollection [Element.SchemaTypeName.ToString ()], TempTable);
150 else // but if it's not yet defined put it safe to wait if we need it later.
151 ElementCollection.Add (Element.SchemaTypeName.Name, TempTable);
154 else if (Element.RefName != null && Element.RefName.Name != string.Empty) { // if there is a ref=
156 if (ElementCollection.Contains (Element.RefName.Name))
157 ReadXmlSchemaElement ((XmlSchemaElement)ElementCollection [Element.RefName.Name], ElementType.ELEMENT_TABLE);
159 else if (ElementType.ELEMENT_UNDEFINED != ElType) {
161 if (ElType == ElementType.ELEMENT_TABLE){
163 // we have to return else all child element of the table will be computed again.
166 else if (ElType == ElementType.ELEMENT_COLUMN && Table != null)
167 ReadColumn (Element, Table);
170 // this element is undefined, for now
171 ElementCollection.Add (Element.Name, Element);
175 if (Element.SchemaType != null)
176 ReadXmlSchemaType (Element.SchemaType);
178 // Read possible constraints
179 if (Element.Constraints != null && Element.Constraints.Count > 0){
180 ReadXmlSchemaConstraints (Element.Constraints);
184 private void ReadTable (XmlSchemaElement Element)
186 DataTable TempTable = null;
188 // Add the table to the DataSet only if it is not already in there.
190 if (DSet.Tables.Contains (Element.Name)) return;
191 TempTable = new DataTable (Element.Name);
192 DSet.Tables.Add (TempTable);
195 if (table.TableName.Length != 0)
196 throw new InvalidOperationException ("More than one table is defined in this schema");
197 table.TableName = Element.Name;
200 ReadXmlSchemaType (Element.SchemaType, TempTable);
203 private void ReadColumn (XmlSchemaElement Element, DataTable Table)
205 DataColumn Column = new DataColumn (Element.Name);
206 Column.DataType = GetColumnType(Element.SchemaTypeName.Name);
207 Table.Columns.Add (Column);
209 if (Element.UnhandledAttributes != null) {
211 foreach (XmlAttribute Attr in Element.UnhandledAttributes) {
212 switch (Attr.LocalName) {
214 case XmlConstants.Caption:
215 Column.Caption = Attr.Value;
217 case XmlConstants.DataType:
218 Column.DataType = Type.GetType (Attr.Value);
220 case XmlConstants.AutoIncrement:
221 Column.AutoIncrement = bool.Parse(Attr.Value);
223 case XmlConstants.AutoIncrementSeed:
224 Column.AutoIncrementSeed = int.Parse(Attr.Value);
233 // Handel rest of the parameters
236 if (Column.DataType == null)
237 Column.DataType = Type.GetType ("System.String");
239 if (Element.DefaultValue != null)
240 Column.DefaultValue = Element.DefaultValue;
242 // If Element have type
243 if (Element.SchemaType != null)
244 ReadXmlSchemaType (Element.SchemaType, Column);
248 private Type GetColumnType (String typeName)
250 if (typeName == null || typeName.Length == 0)
251 return typeof (string);
263 case "unsignedByte" :
272 case "usignedShort" :
278 case "unsignedLong" :
291 t = typeof (decimal);
294 t = typeof (DateTime);
297 t = typeof (TimeSpan);
299 case "base64Binary" :
310 // Makes new Hashtable of the attributes.
311 private Hashtable ReadUnhandledAttributes (XmlAttribute [] Attributes)
313 Hashtable Result = new Hashtable ();
315 if (Attributes == null)
318 foreach (XmlAttribute attribute in Attributes) {
319 Result.Add (attribute.LocalName, attribute.Value);
325 private void ReadXmlSchemaConstraints (XmlSchemaObjectCollection Constraints)
327 foreach (XmlSchemaObject Constraint in Constraints) {
328 if (Constraint is XmlSchemaUnique)
329 ReadXmlSchemaUnique ((XmlSchemaUnique)Constraint);
330 if (Constraint is XmlSchemaKeyref)
331 ReadXmlSchemaKeyref ((XmlSchemaKeyref)Constraint, Constraints);
336 private void ReadXmlSchemaUnique (XmlSchemaUnique Unique)
338 // FIXME: Parsing XPath
339 string TableName = Unique.Selector.XPath;
340 int index = TableName.IndexOf(':');
342 TableName = TableName.Substring (index + 1);
343 else if(TableName.StartsWith (".//"))
344 TableName = TableName.Substring (3);
346 DataColumn [] Columns;
347 DataTable Table = GetTable (TableName);
349 Columns = new DataColumn [Unique.Fields.Count];
351 foreach (XmlSchemaXPath Field in Unique.Fields) {
352 string columnName = Field.XPath;
353 index = columnName.IndexOf (':');
355 columnName = columnName.Substring (index + 1);
356 if (Table.Columns.Contains (columnName)) {
357 Columns [i] = Table.Columns [columnName];
363 // find if there is an attribute with the constraint name
364 // if not use the XmlSchemaUnique name.
365 string constraintName = Unique.Name;
366 if (Unique.UnhandledAttributes != null){
367 foreach (XmlAttribute attr in Unique.UnhandledAttributes){
368 if (attr.LocalName == "ConstraintName"){
369 constraintName = attr.Value;
371 else if (attr.LocalName == XmlConstants.PrimaryKey){
372 isPK = bool.Parse(attr.Value);
377 UniqueConstraint Constraint = new UniqueConstraint (constraintName, Columns, isPK);
378 Table.Constraints.Add (Constraint);
383 private void ReadXmlSchemaKeyref (XmlSchemaKeyref KeyRef, XmlSchemaObjectCollection collection) {
385 if (DSet == null) return; // Ignore relations for table-only schemas
387 string TableName = KeyRef.Selector.XPath;
388 int index = TableName.IndexOf(':');
390 TableName = TableName.Substring (index + 1);
391 else if (TableName.StartsWith (".//"))
392 TableName = TableName.Substring (3);
393 DataColumn [] Columns;
394 DataTable Table = GetTable (TableName);
396 Columns = new DataColumn [KeyRef.Fields.Count];
398 foreach (XmlSchemaXPath Field in KeyRef.Fields) {
399 string columnName = Field.XPath;
400 index = columnName.IndexOf (':');
402 columnName = columnName.Substring (index + 1);
403 if (Table.Columns.Contains (columnName)) {
404 Columns [i] = Table.Columns [columnName];
408 string name = KeyRef.Refer.Name;
409 // get the unique constraint for the releation
410 UniqueConstraint constraint = GetDSConstraint(name, collection);
412 ForeignKeyConstraint fkConstraint = new ForeignKeyConstraint(KeyRef.Name, constraint.Columns, Columns);
413 Table.Constraints.Add (fkConstraint);
414 // generate the relation.
415 DataRelation relation = new DataRelation(KeyRef.Name, constraint.Columns, Columns, false);
416 if (KeyRef.UnhandledAttributes != null){
417 foreach (XmlAttribute attr in KeyRef.UnhandledAttributes){
418 if (attr.LocalName == "IsNested"){
419 if (attr.Value == "true")
420 relation.Nested = true;
424 relation.SetParentKeyConstraint (constraint);
425 relation.SetChildKeyConstraint (fkConstraint);
427 DSet.Relations.Add(relation);
431 // get the unique constraint for the relation.
432 // name - the name of the XmlSchemaUnique element
433 private UniqueConstraint GetDSConstraint(string name, XmlSchemaObjectCollection collection)
435 // find the element in the constraint collection.
436 foreach (XmlSchemaObject shemaObj in collection){
437 if (shemaObj is XmlSchemaUnique){
438 XmlSchemaUnique unique = (XmlSchemaUnique) shemaObj;
439 if (unique.Name == name){
440 string tableName = unique.Selector.XPath;
441 int index = tableName.IndexOf (':');
443 tableName = tableName.Substring (index + 1);
444 else if (tableName.StartsWith (".//"))
445 tableName = tableName.Substring (3);
447 // find the table in the dataset.
448 DataTable table = GetTable (tableName);
451 string constraintName = unique.Name;
452 // find if there is an attribute with the constraint name
453 // if not use the XmlSchemaUnique name.
454 if (unique.UnhandledAttributes != null){
455 foreach (XmlAttribute attr in unique.UnhandledAttributes){
456 if (attr.LocalName == "ConstraintName"){
457 constraintName = attr.Value;
462 if (table.Constraints.Contains(constraintName))
463 return (UniqueConstraint)table.Constraints[constraintName];
473 #endregion // Private methods
475 #region Private listeners
477 private void OnXmlSchemaValidation (object sender, ValidationEventArgs args)
482 #endregion // Private listeners
484 #region Private TypeReaders
486 // Reads XmlSchemaType
487 private void ReadXmlSchemaType (XmlSchemaType SchemaType)
489 ReadXmlSchemaType (SchemaType, (DataTable)null);
492 // Reads XmlSchemaType and decides is it Complex or Simple and continue reading those types
493 private void ReadXmlSchemaType (XmlSchemaType SchemaType, DataTable Table)
495 if (SchemaType is XmlSchemaComplexType)
496 ReadXmlSchemaComplexType ((XmlSchemaComplexType)SchemaType, Table);
497 else if (SchemaType is XmlSchemaSimpleType)
498 ReadXmlSchemaSimpleType ((XmlSchemaSimpleType)SchemaType, Table);
501 // Same as above but with DataColumn
502 private void ReadXmlSchemaType (XmlSchemaType SchemaType, DataColumn Column)
504 if (SchemaType is XmlSchemaComplexType)
505 ReadXmlSchemaComplexType ((XmlSchemaComplexType)SchemaType, Column);
506 else if (SchemaType is XmlSchemaSimpleType)
507 ReadXmlSchemaSimpleType ((XmlSchemaSimpleType)SchemaType, Column);
510 #endregion // PrivateTypeReader
512 #region TypeReaderHelppers
514 private void ReadXmlSchemaSimpleType (XmlSchemaSimpleType SimpleType, DataColumn Column)
517 if (SimpleType.Content is XmlSchemaSimpleTypeRestriction)
518 ReadXmlSchemaSimpleTypeRestriction ((XmlSchemaSimpleTypeRestriction)SimpleType.Content, Column);
522 private void ReadXmlSchemaSimpleType (XmlSchemaSimpleType SimpleType, DataTable Table)
524 // TODO: Is it possible that Table-element have simpletype???
528 private void ReadXmlSchemaSimpleTypeRestriction (XmlSchemaSimpleTypeRestriction Restriction, DataColumn Column)
530 foreach (XmlSchemaObject Facet in Restriction.Facets) {
532 // FIXME: I dont know are everyone of these needed but, let them be here for now
533 if (Facet is XmlSchemaMaxLengthFacet)
534 Column.MaxLength = Int32.Parse(((XmlSchemaFacet)Facet).Value);
535 //else if (Facet is XmlSchemaMinLengthFacet)
537 //else if (Facet is XmlSchemaLengthFacet)
539 //else if (Facet is XmlSchemaPatternFacet)
541 //else if (Facet is XmlSchemaEnumerationFacet)
543 //else if (Facet is XmlSchemaMaxInclusiveFacet)
545 //else if (Facet is XmlSchemaMaxExclusiveFacet)
547 //else if (Facet is XmlSchemaMinInclusiveFacet)
549 //else if (Facet is XmlSchemaMinExclusiveFacet)
551 //else if (Facet is XmlSchemaFractionDigitsFacet)
553 //else if (Facet is XmlSchemaTotalDigitsFacet)
555 //else if (Facet is XmlSchemaWhiteSpaceFacet)
561 private void ReadXmlSchemaComplexType (XmlSchemaComplexType Type, DataColumn Column)
563 // TODO: is it possible that column-element have complextype
566 // Reads XmlSchemaComplexType with DataTable
567 private void ReadXmlSchemaComplexType (XmlSchemaComplexType Type, DataTable Table)
569 XmlSchemaComplexType ComplexType = Type as XmlSchemaComplexType;
571 if (ComplexType.Name != null && ComplexType.Name != string.Empty) {
573 if (ElementCollection.Contains (ComplexType.Name)) {
575 if (ComplexType.Particle is XmlSchemaChoice) {
576 ReadXmlSchemaChoice (ComplexType.Particle as XmlSchemaChoice);
578 else if (ComplexType.Particle is XmlSchemaSequence) {
580 DataTable TempTable = ElementCollection [ComplexType.Name] as DataTable;
581 ElementCollection.Remove (ComplexType.Name);
582 ReadXmlSchemaSequence (ComplexType.Particle as XmlSchemaSequence, TempTable);
585 else if (ComplexType.Name != null && !TypeCollection.Contains (ComplexType.Name)) {
586 TypeCollection.Add (ComplexType.Name, ComplexType);
590 // If we are here it means that types of elements are Tables :-P
591 if (ComplexType.Particle is XmlSchemaSequence)
592 ReadXmlSchemaSequence (ComplexType.Particle as XmlSchemaSequence, Table);
597 XmlSchemaParticle Particle;
598 if ((Particle = ComplexType.Particle as XmlSchemaChoice) != null) {
599 ReadXmlSchemaChoice (Particle as XmlSchemaChoice);
601 else if ((Particle = ComplexType.Particle as XmlSchemaSequence) != null) {
602 ReadXmlSchemaSequence (Particle as XmlSchemaSequence, Table);
605 // read columns if they were written as simplecontent.
606 XmlSchemaSimpleContent simpleContent;
607 if ((simpleContent = ComplexType.ContentModel as XmlSchemaSimpleContent) != null){
608 ReadColumn (simpleContent, Table);
611 // read columns if they were written as attributes
612 if (ComplexType.Attributes != null) {
614 foreach (XmlSchemaObject sobj in ComplexType.Attributes) {
615 if (sobj is XmlSchemaAttribute)
616 ReadColumn ((XmlSchemaAttribute)sobj, Table);
622 private void ReadColumn (XmlSchemaSimpleContent simpleContent, DataTable Table)
624 DataColumn Column = new DataColumn ();
627 if (simpleContent.UnhandledAttributes != null) {
629 foreach (XmlAttribute Attr in simpleContent.UnhandledAttributes) {
630 switch (Attr.LocalName) {
632 case XmlConstants.ColumnName:
633 Column.ColumnName = Attr.Value;
642 XmlSchemaSimpleContentExtension extension;
\r
643 if ((extension = simpleContent.Content as XmlSchemaSimpleContentExtension) != null) {
\r
644 Column.DataType = GetColumnType (extension.BaseTypeName.Name);
\r
647 Table.Columns.Add (Column);
650 private void ReadColumn (XmlSchemaAttribute schemaAttribute, DataTable table)
652 DataColumn column = new DataColumn (schemaAttribute.Name);
653 column.DataType = GetColumnType (schemaAttribute.SchemaTypeName.Name);
654 table.Columns.Add (column);
657 DataTable GetTable (string name)
660 return DSet.Tables [name];
661 else if (name == table.TableName)
664 throw new InvalidOperationException ("Schema not valid for table '" + table.TableName + "'");
667 #endregion // TypeReaderHelppers