// // XmlSchemaDataImporter.cs // // Author: // Atsushi Enomoto // // (C)2004 Novell Inc. // // // ***** The design note became somewhat obsolete. Should be rewritten. ***** // // * Design Notes // // ** Abstract // // This class is used to import an XML Schema into a DataSet schema. // // Only XmlReader is acceptable as the input to the class. // This class is not expected to read XML Schema multi time. // // ** Targetable Schema Components // // Only global global elements that hold complex type are converted // into a table. // // The components of the type of the element are subsequently converted // into a table, BUT there is an exception. As for "DataSet elements", // the type is just ignored (see "DataSet Element definition" below). // // The components of the type of the element are subsequently converted // into a table. As for "DataSet elements", its complex type is also // handled. // // // Unused complex types are never be converted. // // Global simple types and global attributes are never converted. // They cannot be a table. // Local complex types are also converted into a table. // // Local elements are converted into either a table or a column in // the "context DataTable". Simple-typed element is not always converted // into a DataColumn; if maxOccurs > 1, it will be converted as a table. // // ** Name Convention // // Ignore this section. Microsoft.NET was buggy enough to confuse // against these name conflicts. // // Since local complex types are anonymous, we have to name for each // component. Thus, and since complex types and elements can have the // same name each other, we have to manage a table for mappings from // a name to a component. The names must be also used in DataRelation // definitions correctly. // // ** DataSet element definition // // "DataSet element" is 1) such element that has an attribute // msdata:IsDataSet (where prefix "msdata" is bound to // urn:schemas-microsoft-com:xml-msdata), or 2) the only one // element definition in the schema. // // There is another complicated rule. 1) If there is only one element EL // in the schema, and 2) if the type of EL is complex named CT, and 3) // the content of the CT is a group base, and 4) the group base contains // an element EL2, and finally 5) if EL2 is complex, THEN the element is // the DataSet element. // // Only the first global element that matches the condition above is // regarded as DataSet element (by necessary design or just a bug?) // instead of handling as an error. // // All global elements are considered as an alternative in the dataset // element. // // For local elements, msdata:IsDataSet are just ignored. // // ** Importing Complex Types as Columns // // When an xs:element is going to be mapped, its complex type (remember // that only complex-typed elements are targettable) are expanded to // DataColumn. // // DataColumn has a property MappingType that shows whether this column // came from attribute or element. // // [Question: How about MappingType.Simple? How is it used?] // // Additionally, for particle elements, it might also create another // DataTable (but for the particle elements in context DataTable, it // will create an index to the new table). // // For group base particles (XmlSchemaGroupBase; sequence, choice, all) // each component in those groups are mapped to a column. Even if you // import "choice" or "all" components, DataSet.WriteXmlSchema() will // output them just as a "sequence". // // Columns cannot be added directly to current context DataTable; they // need to be added after processing all the columns, because they may // have msdata:Ordinal attribute that specifies the order of the columns // in the DataTable. // // "Nested elements" are not allowed. (Clarification required?) // // ** Identity Constraints and DataRelations // // *** DataRelations from element identity constraints // // Only constraints on "DataSet element" is considered. All other // constraint definitions are ignored. Note that it is DataSet that has // the property Relations (of type DataRelationCollection). // // xs:key and xs:unique are handled as the same (then both will be // serialized as xs:unique). // // The XPath expressions in the constraints are strictly limited; they // are expected to be expandable enough to be mappable for each // // * selector to "any_valid_XPath/is/OK/blah" // where "blah" is one of the DataTable name. It looks that // only the last QName section is significant and any heading // XPath step is OK (even if the mapped node does not exist). // * field to QName that is mapped to DataColumn in the DataTable // (even ./QName is not allowed) // // *** DataRelations from annotations // // 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 // // ** Informative References // // Generating DataSet Relational Structure from XML Schema (XSD) // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/_generating_dataset_relational_structure_from_xsd.asp // // // Copyright (C) 2004 Novell, Inc (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections; using System.Data; using System.Data.Common; using System.Globalization; using System.Xml; using System.Xml.Schema; namespace System.Data { internal class TableStructureCollection : CollectionBase { public void Add (TableStructure table) { List.Add (table); } public TableStructure this [int i] { get { return List [i] as TableStructure; } } public TableStructure this [string name] { get { foreach (TableStructure ts in List) if (ts.Table.TableName == name) return ts; return null; } } } internal class RelationStructureCollection : CollectionBase { public void Add (RelationStructure rel) { List.Add (rel); } public RelationStructure this [int i] { get { return List [i] as RelationStructure; } } public RelationStructure this [string parent, string child] { get { foreach (RelationStructure rel in List) if (rel.ParentTableName == parent && rel.ChildTableName == child) return rel; return null; } } } internal class TableStructure { public TableStructure (DataTable table) { this.Table = table; } // The columns and orders which will be added to the context // table (See design notes; Because of the ordinal problem) public DataTable Table; public Hashtable OrdinalColumns = new Hashtable (); public ArrayList NonOrdinalColumns = new ArrayList (); public DataColumn PrimaryKey; public bool ContainsColumn (string name) { foreach (DataColumn col in NonOrdinalColumns) if (col.ColumnName == name) return true; foreach (DataColumn col in OrdinalColumns.Keys) if (col.ColumnName == name) return true; return false; } } internal class RelationStructure { public string ExplicitName; public string ParentTableName; public string ChildTableName; public string ParentColumnName; public string ChildColumnName; public bool IsNested; public bool CreateConstraint; } internal class ConstraintStructure { public readonly string TableName; public readonly string [] Columns; public readonly bool [] IsAttribute; public readonly string ConstraintName; public readonly bool IsPrimaryKey; public readonly string ReferName; public readonly bool IsNested; public readonly bool IsConstraintOnly; public ConstraintStructure (string tname, string [] cols, bool [] isAttr, string cname, bool isPK, string refName, bool isNested, bool isConstraintOnly) { TableName = tname; Columns = cols; IsAttribute = isAttr; ConstraintName = XmlHelper.Decode (cname); IsPrimaryKey = isPK; ReferName = refName; IsNested = isNested; IsConstraintOnly = isConstraintOnly; } } internal class XmlSchemaDataImporter { static readonly XmlSchemaDatatype schemaIntegerType; static readonly XmlSchemaDatatype schemaDecimalType; static readonly XmlSchemaComplexType schemaAnyType; static XmlSchemaDataImporter () { XmlSchema s = new XmlSchema (); XmlSchemaAttribute a = new XmlSchemaAttribute (); a.Name = "foo"; a.SchemaTypeName = new XmlQualifiedName ("integer", XmlSchema.Namespace); s.Items.Add (a); XmlSchemaAttribute b = new XmlSchemaAttribute (); b.Name = "bar"; b.SchemaTypeName = new XmlQualifiedName ("decimal", XmlSchema.Namespace); s.Items.Add (b); XmlSchemaElement e = new XmlSchemaElement (); e.Name = "bar"; s.Items.Add (e); s.Compile (null); #if NET_2_0 schemaIntegerType = ((XmlSchemaSimpleType) a.AttributeSchemaType).Datatype; schemaDecimalType = ((XmlSchemaSimpleType) b.AttributeSchemaType).Datatype; schemaAnyType = e.ElementSchemaType as XmlSchemaComplexType; #else schemaIntegerType = a.AttributeType as XmlSchemaDatatype; schemaDecimalType = b.AttributeType as XmlSchemaDatatype; schemaAnyType = e.ElementType as XmlSchemaComplexType; #endif } #region Fields DataSet dataset; bool forDataSet; XmlSchema schema; ArrayList relations = new ArrayList (); Hashtable reservedConstraints = new Hashtable (); // such element that has an attribute msdata:IsDataSet="true" XmlSchemaElement datasetElement; // choice alternatives in the "dataset element" ArrayList topLevelElements = new ArrayList (); // import target elements ArrayList targetElements = new ArrayList (); TableStructure currentTable; #if NET_2_0 // TODO: Do we need a collection here? TableAdapterSchemaInfo currentAdapter; #endif #endregion // .ctor() public XmlSchemaDataImporter (DataSet dataset, XmlReader reader, bool forDataSet) { this.dataset = dataset; this.forDataSet = forDataSet; dataset.DataSetName = "NewDataSet"; // Initialize always schema = XmlSchema.Read (reader, null); if (reader.NodeType == XmlNodeType.EndElement && reader.LocalName == "schema" && reader.NamespaceURI == XmlSchema.Namespace) reader.ReadEndElement (); schema.Compile (null); } #if NET_2_0 // properties internal TableAdapterSchemaInfo CurrentAdapter { get { return currentAdapter; } } #endif // methods public void Process () { if (schema.Id != null) dataset.DataSetName = schema.Id; // default. Overridable by "DataSet element" dataset.Namespace = schema.TargetNamespace; // Find dataset element foreach (XmlSchemaObject obj in schema.Items) { XmlSchemaElement el = obj as XmlSchemaElement; if (el != null) { if (datasetElement == null && IsDataSetElement (el)) datasetElement = el; #if NET_2_0 if (el.ElementSchemaType is XmlSchemaComplexType && el.ElementSchemaType != schemaAnyType) #else if (el.ElementType is XmlSchemaComplexType && el.ElementType != schemaAnyType) #endif targetElements.Add (obj); } } // make reservation of identity constraints if (datasetElement != null) { // keys/uniques. foreach (XmlSchemaObject obj in datasetElement.Constraints) if (! (obj is XmlSchemaKeyref)) ReserveSelfIdentity ((XmlSchemaIdentityConstraint) obj); // keyrefs. foreach (XmlSchemaObject obj in datasetElement.Constraints) if (obj is XmlSchemaKeyref) ReserveRelationIdentity (datasetElement, (XmlSchemaKeyref) obj); } foreach (XmlSchemaObject obj in schema.Items) { if (obj is XmlSchemaElement) { XmlSchemaElement el = obj as XmlSchemaElement; #if NET_2_0 if (el.ElementSchemaType is XmlSchemaComplexType && el.ElementSchemaType != schemaAnyType) #else if (el.ElementType is XmlSchemaComplexType && el.ElementType != schemaAnyType) #endif targetElements.Add (obj); } } // This collection will grow up while processing elements. int globalElementCount = targetElements.Count; for (int i = 0; i < globalElementCount; i++) ProcessGlobalElement ((XmlSchemaElement) targetElements [i]); // Rest are local elements. for (int i = globalElementCount; i < targetElements.Count; i++) ProcessDataTableElement ((XmlSchemaElement) targetElements [i]); // Handle relation definitions written as xs:annotation. // See detail: http://msdn.microsoft.com/library/shared/happyUrl/fnf_msdn.asp?Redirect=%22http://msdn.microsoft.com/404/default.asp%22 foreach (XmlSchemaObject obj in schema.Items) if (obj is XmlSchemaAnnotation) HandleAnnotations ((XmlSchemaAnnotation) obj, false); if (datasetElement != null) { // Handle constraints in the DataSet element. First keys. foreach (XmlSchemaObject obj in datasetElement.Constraints) if (! (obj is XmlSchemaKeyref)) ProcessSelfIdentity (reservedConstraints [obj] as ConstraintStructure); // Then keyrefs. foreach (XmlSchemaObject obj in datasetElement.Constraints) if (obj is XmlSchemaKeyref) ProcessRelationIdentity (datasetElement, reservedConstraints [obj] as ConstraintStructure); } foreach (RelationStructure rs in this.relations) dataset.Relations.Add (GenerateRelationship (rs)); } private bool IsDataSetElement (XmlSchemaElement el) { if (el.UnhandledAttributes != null) { foreach (XmlAttribute attr in el.UnhandledAttributes) { if (attr.LocalName == "IsDataSet" && attr.NamespaceURI == XmlConstants.MsdataNamespace) { switch (attr.Value) { case "true": // case sensitive return true; case "false": break; default: throw new DataException (String.Format ("Value {0} is invalid for attribute 'IsDataSet'.", attr.Value)); } } } } if (schema.Elements.Count != 1) return false; if (!(el.SchemaType is XmlSchemaComplexType)) return false; XmlSchemaComplexType ct = (XmlSchemaComplexType) el.SchemaType; if (ct.AttributeUses.Count > 0) return false; XmlSchemaGroupBase gb = ct.ContentTypeParticle as XmlSchemaGroupBase; if (gb == null || gb.Items.Count == 0) return false; foreach (XmlSchemaParticle p in gb.Items) { if (ContainsColumn (p)) return false; } return true; } private bool ContainsColumn (XmlSchemaParticle p) { XmlSchemaElement el = p as XmlSchemaElement; if (el != null) { XmlSchemaComplexType ct = null; #if NET_2_0 ct = el.ElementSchemaType as XmlSchemaComplexType; #else ct = el.ElementType as XmlSchemaComplexType; #endif if (ct == null || ct == schemaAnyType) return true; // column element if (ct.AttributeUses.Count > 0) return false; // table element if (ct.ContentType == XmlSchemaContentType.TextOnly) return true; // column element else return false; // table element } XmlSchemaGroupBase gb = p as XmlSchemaGroupBase; for (int i = 0; i < gb.Items.Count; i++) { if (ContainsColumn ((XmlSchemaParticle) gb.Items [i])) return true; } return false; } private void ProcessGlobalElement (XmlSchemaElement el) { // If it is already registered (by resolving reference // in previously-imported elements), just ignore. if (dataset.Tables.Contains (el.QualifiedName.Name)) return; // If type is not complex, just skip this element #if NET_2_0 if (! (el.ElementSchemaType is XmlSchemaComplexType && el.ElementSchemaType != schemaAnyType)) #else if (! (el.ElementType is XmlSchemaComplexType && el.ElementType != schemaAnyType)) #endif return; if (IsDataSetElement (el)) { ProcessDataSetElement (el); return; } else dataset.Locale = CultureInfo.CurrentCulture; // Register as a top-level element topLevelElements.Add (el); // Create DataTable for this element ProcessDataTableElement (el); } private void ProcessDataSetElement (XmlSchemaElement el) { dataset.DataSetName = el.Name; this.datasetElement = el; // Search for locale attributes bool useCurrent = false; if (el.UnhandledAttributes != null) { foreach (XmlAttribute attr in el.UnhandledAttributes) { #if NET_2_0 if (attr.LocalName == "UseCurrentLocale" && attr.NamespaceURI == XmlConstants.MsdataNamespace) useCurrent = true; #endif if (attr.LocalName == "Locale" && attr.NamespaceURI == XmlConstants.MsdataNamespace) { CultureInfo ci = new CultureInfo (attr.Value); dataset.Locale = ci; } } } #if NET_2_0 if (!useCurrent && !dataset.LocaleSpecified) // then set current culture instance _explicitly_ dataset.Locale = CultureInfo.CurrentCulture; #endif // Process content type particle (and create DataTable) XmlSchemaComplexType ct = null; #if NET_2_0 ct = el.ElementSchemaType as XmlSchemaComplexType; #else ct = el.ElementType as XmlSchemaComplexType; #endif XmlSchemaParticle p = ct != null ? ct.ContentTypeParticle : null; if (p != null) HandleDataSetContentTypeParticle (p); } private void HandleDataSetContentTypeParticle (XmlSchemaParticle p) { XmlSchemaElement el = p as XmlSchemaElement; if (el != null) { #if NET_2_0 if (el.ElementSchemaType is XmlSchemaComplexType && el.RefName != el.QualifiedName) #else if (el.ElementType is XmlSchemaComplexType && el.RefName != el.QualifiedName) #endif ProcessDataTableElement (el); } else if (p is XmlSchemaGroupBase) { foreach (XmlSchemaParticle pc in ((XmlSchemaGroupBase) p).Items) HandleDataSetContentTypeParticle (pc); } } private void ProcessDataTableElement (XmlSchemaElement el) { string tableName = XmlHelper.Decode (el.QualifiedName.Name); // If it is already registered, just ignore. if (dataset.Tables.Contains (tableName)) return; DataTable table = new DataTable (tableName); table.Namespace = el.QualifiedName.Namespace; TableStructure oldTable = currentTable; currentTable = new TableStructure (table); dataset.Tables.Add (table); // Find Locale if (el.UnhandledAttributes != null) { foreach (XmlAttribute attr in el.UnhandledAttributes) { if (attr.LocalName == "Locale" && attr.NamespaceURI == XmlConstants.MsdataNamespace) table.Locale = new CultureInfo (attr.Value); } } // Handle complex type (NOTE: It is (or should be) // impossible the type is other than complex type). XmlSchemaComplexType ct = null; #if NET_2_0 ct = (XmlSchemaComplexType) el.ElementSchemaType; #else ct = (XmlSchemaComplexType) el.ElementType; #endif // Handle attributes foreach (DictionaryEntry de in ct.AttributeUses) ImportColumnAttribute ((XmlSchemaAttribute) de.Value); // Handle content type particle if (ct.ContentTypeParticle is XmlSchemaElement) ImportColumnElement (el, (XmlSchemaElement) ct.ContentTypeParticle); else if (ct.ContentTypeParticle is XmlSchemaGroupBase) ImportColumnGroupBase (el, (XmlSchemaGroupBase) ct.ContentTypeParticle); // else if null then do nothing. // Handle simple content switch (ct.ContentType) { case XmlSchemaContentType.TextOnly: // case XmlSchemaContentType.Mixed: // LAMESPEC: When reading from XML Schema, it maps to "_text", while on the data inference, it is mapped to "_Text" (case ignorant). string simpleName = el.QualifiedName.Name + "_text"; DataColumn simple = new DataColumn (simpleName); simple.Namespace = el.QualifiedName.Namespace; simple.AllowDBNull = (el.MinOccurs == 0); simple.ColumnMapping = MappingType.SimpleContent; simple.DataType = ConvertDatatype (ct.Datatype); currentTable.NonOrdinalColumns.Add (simple); break; } // add columns to the table in specified order // (by msdata:Ordinal attributes) SortedList sd = new SortedList (); foreach (DictionaryEntry de in currentTable.OrdinalColumns) sd.Add (de.Value, de.Key); foreach (DictionaryEntry de in sd) table.Columns.Add ((DataColumn) de.Value); foreach (DataColumn dc in currentTable.NonOrdinalColumns) table.Columns.Add (dc); currentTable = oldTable; } private DataRelation GenerateRelationship (RelationStructure rs) { DataTable ptab = dataset.Tables [rs.ParentTableName]; DataTable ctab = dataset.Tables [rs.ChildTableName]; DataRelation rel ; string name = rs.ExplicitName != null ? rs.ExplicitName : XmlHelper.Decode (ptab.TableName) + '_' + XmlHelper.Decode (ctab.TableName); // Annotation Relations belonging to a DataSet can contain multiple colnames // in parentkey and childkey. if (datasetElement != null) { String[] pcolnames = rs.ParentColumnName.Split (null); String[] ccolnames = rs.ChildColumnName.Split (null); DataColumn[] pcol = new DataColumn [pcolnames.Length]; for (int i=0; i