// // mcs/class/System.Data/System.Data/XmlDataLoader.cs // // Purpose: Loads XmlDocument to DataSet // // class: XmlDataLoader // assembly: System.Data.dll // namespace: System.Data // // Author: // Ville Palo // Atsushi Enomoto // // (c)copyright 2002 Ville Palo // (C)2004 Novell Inc. // // XmlDataLoader is included within the Mono Class Library. // // // 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.Data; using System.Xml; using System.Collections; using System.Globalization; namespace System.Data { internal class XmlDataLoader { private DataSet DSet; public XmlDataLoader (DataSet set) { DSet = set; } public XmlReadMode LoadData (XmlReader reader, XmlReadMode mode) { XmlReadMode Result = mode; switch (mode) { case XmlReadMode.Auto: Result = DSet.Tables.Count == 0 ? XmlReadMode.InferSchema : XmlReadMode.IgnoreSchema; ReadModeSchema (reader, DSet.Tables.Count == 0 ? XmlReadMode.Auto : XmlReadMode.IgnoreSchema); break; case XmlReadMode.InferSchema: Result = XmlReadMode.InferSchema; ReadModeSchema (reader, mode); break; case XmlReadMode.IgnoreSchema: Result = XmlReadMode.IgnoreSchema; ReadModeSchema (reader, mode); break; default: reader.Skip (); break; } return Result; } #region reading // Read information from the reader. private void ReadModeSchema (XmlReader reader, XmlReadMode mode) { bool inferSchema = mode == XmlReadMode.InferSchema || mode == XmlReadMode.Auto; bool fillRows = mode != XmlReadMode.InferSchema; // This check is required for full DiffGram. // It is not described in MSDN and it is impossible // with WriteXml(), but when writing XML using // XmlSerializer, the output is like this: // // ... // ... // // // FIXME: This, this check should (also) be done // after reading the top-level element. //check if the current element is schema. if (reader.LocalName == "schema") { if (mode != XmlReadMode.Auto) reader.Skip(); // skip the schema node. else DSet.ReadXmlSchema(reader); reader.MoveToContent(); } // load an XmlDocument from the reader. XmlDocument doc = new XmlDocument (); doc.Load (reader); if (doc.DocumentElement == null) return; // treatment for .net compliancy : // if xml representing dataset has exactly depth of 2 elements, // than the root element actually represents datatable and not dataset // so we add new root element to doc // in order to create an element representing dataset. // // FIXME: Consider attributes. // is regarded as a valid DataTable. int rootNodeDepth = XmlNodeElementsDepth(doc.DocumentElement); switch (rootNodeDepth) { case 1: if (inferSchema) { DSet.DataSetName = doc.DocumentElement.LocalName; DSet.Prefix = doc.DocumentElement.Prefix; DSet.Namespace = doc.DocumentElement.NamespaceURI; } return; case 2: // create new document XmlDocument newDoc = new XmlDocument(); // create element for dataset XmlElement datasetElement = newDoc.CreateElement("dummy"); // make the new created element to be the new doc root newDoc.AppendChild(datasetElement); // import all the elements from doc and insert them into new doc XmlNode root = newDoc.ImportNode(doc.DocumentElement,true); datasetElement.AppendChild(root); doc = newDoc; break; default: if (inferSchema) { DSet.DataSetName = doc.DocumentElement.LocalName; DSet.Prefix = doc.DocumentElement.Prefix; DSet.Namespace = doc.DocumentElement.NamespaceURI; } break; } // set EnforceConstraint to false - we do not want any validation during // load time. bool origEnforceConstraint = DSet.EnforceConstraints; DSet.EnforceConstraints = false; // The childs are tables. XmlNodeList nList = doc.DocumentElement.ChildNodes; // FIXME: When reading DataTable (not DataSet), // the nodes are column items, not rows. for (int i = 0; i < nList.Count; i++) { XmlNode node = nList[i]; // node represents a table onky if it is of type XmlNodeType.Element if (node.NodeType == XmlNodeType.Element) { AddRowToTable(node, null, inferSchema, fillRows); } } // set the EnforceConstraints to original value; DSet.EnforceConstraints = origEnforceConstraint; } #endregion // reading #region Private helper methods /* private void ReadColumns (XmlReader reader, DataRow row, DataTable table, string TableName) { do { if (reader.NodeType == XmlNodeType.Element) { DataColumn col = table.Columns [reader.LocalName]; if (col != null) { row [col] = StringToObject (col.DataType, reader.Value); } reader.Read (); } else { reader.Read (); } } while (table.TableName != reader.LocalName || reader.NodeType != XmlNodeType.EndElement); } */ internal static object StringToObject (Type type, string value) { if (type == null) return value; switch (Type.GetTypeCode (type)) { case TypeCode.Boolean: return XmlConvert.ToBoolean (value); case TypeCode.Byte: return XmlConvert.ToByte (value); case TypeCode.Char: return (char)XmlConvert.ToInt32 (value); #if NET_2_0 case TypeCode.DateTime: return XmlConvert.ToDateTime (value, XmlDateTimeSerializationMode.Unspecified); #else case TypeCode.DateTime: return XmlConvert.ToDateTime (value); #endif case TypeCode.Decimal: return XmlConvert.ToDecimal (value); case TypeCode.Double: return XmlConvert.ToDouble (value); case TypeCode.Int16: return XmlConvert.ToInt16 (value); case TypeCode.Int32: return XmlConvert.ToInt32 (value); case TypeCode.Int64: return XmlConvert.ToInt64 (value); case TypeCode.SByte: return XmlConvert.ToSByte (value); case TypeCode.Single: return XmlConvert.ToSingle (value); case TypeCode.UInt16: return XmlConvert.ToUInt16 (value); case TypeCode.UInt32: return XmlConvert.ToUInt32 (value); case TypeCode.UInt64: return XmlConvert.ToUInt64 (value); } if (type == typeof (TimeSpan)) return XmlConvert.ToTimeSpan (value); if (type == typeof (Guid)) return XmlConvert.ToGuid (value); if (type == typeof (byte[])) return Convert.FromBase64String (value); if (type == typeof (System.Type)) return System.Type.GetType (value); return Convert.ChangeType (value, type); } private void AddRowToTable(XmlNode tableNode, DataColumn relationColumn, bool inferSchema, bool fillRows) { Hashtable rowValue = new Hashtable(); DataTable table; // Check if the table exists in the DataSet. If not create one. if (DSet.Tables.Contains(tableNode.LocalName)) table = DSet.Tables[tableNode.LocalName]; else if (inferSchema) { table = new DataTable(tableNode.LocalName); DSet.Tables.Add(table); } else return; // For elements that are inferred as tables and that contain text // but have no child elements, a new column named "TableName_Text" // is created for the text of each of the elements. // If an element is inferred as a table and has text, but also has child elements, // the text is ignored. // Note : if an element is inferred as a table and has text // and has no child elements, // but the repeated ements of this table have child elements, // then the text is ignored. if(!HaveChildElements(tableNode) && HaveText(tableNode) && !IsRepeatedHaveChildNodes(tableNode)) { string columnName = tableNode.Name + "_Text"; if (!table.Columns.Contains(columnName)) { table.Columns.Add(columnName); } rowValue.Add(columnName, tableNode.InnerText); } // Get the child nodes of the table. Any child can be one of the following tow: // 1. DataTable - if there was a relation with another table.. // 2. DataColumn - column of the current table. XmlNodeList childList = tableNode.ChildNodes; for (int i = 0; i < childList.Count; i++) { XmlNode childNode = childList[i]; // we are looping through elements only // Note : if an element is inferred as a table and has text, but also has child elements, // the text is ignored. if (childNode.NodeType != XmlNodeType.Element) continue; // Elements that have attributes are inferred as tables. // Elements that have child elements are inferred as tables. // Elements that repeat are inferred as a single table. if (IsInferredAsTable(childNode)) { // child node inferred as table if (inferSchema) { // We need to create new column for the relation between the current // table and the new table we found (the child table). string newRelationColumnName = table.TableName + "_Id"; if (!table.Columns.Contains(newRelationColumnName)) { DataColumn newRelationColumn = new DataColumn(newRelationColumnName, typeof(int)); newRelationColumn.AllowDBNull = false; newRelationColumn.AutoIncrement = true; // we do not want to serialize this column so MappingType is Hidden. newRelationColumn.ColumnMapping = MappingType.Hidden; table.Columns.Add(newRelationColumn); } // Add a row to the new table we found. AddRowToTable(childNode, table.Columns[newRelationColumnName], inferSchema, fillRows); } else AddRowToTable(childNode, null, inferSchema, fillRows); } else { // Elements that have no attributes or child elements, and do not repeat, // are inferred as columns. object val = null; if (childNode.FirstChild != null) val = childNode.FirstChild.Value; else val = ""; if (table.Columns.Contains(childNode.LocalName)) rowValue.Add(childNode.LocalName, val); else if (inferSchema) { table.Columns.Add(childNode.LocalName); rowValue.Add(childNode.LocalName, val); } } } // Column can be attribute of the table element. XmlAttributeCollection aCollection = tableNode.Attributes; for (int i = 0; i < aCollection.Count; i++) { XmlAttribute attr = aCollection[i]; //the atrribute can be the namespace. if (attr.Prefix.Equals("xmlns")) table.Namespace = attr.Value; else { // the attribute is a column. if (!table.Columns.Contains(attr.LocalName)) { DataColumn col = table.Columns.Add(attr.LocalName); col.ColumnMapping = MappingType.Attribute; } table.Columns[attr.LocalName].Namespace = table.Namespace; rowValue.Add(attr.LocalName, attr.Value); } } // If the current table is a child table we need to add a new column for the relation // and add a new relation to the DataSet. if (relationColumn != null) { if (!table.Columns.Contains(relationColumn.ColumnName)) { DataColumn dc = new DataColumn(relationColumn.ColumnName, typeof(int)); // we do not want to serialize this column so MappingType is Hidden. dc.ColumnMapping = MappingType.Hidden; table.Columns.Add(dc); // Convention of relation name is: ParentTableName_ChildTableName DataRelation dr = new DataRelation(relationColumn.Table.TableName + "_" + dc.Table.TableName, relationColumn, dc); dr.Nested = true; DSet.Relations.Add(dr); UniqueConstraint.SetAsPrimaryKey (dr.ParentTable.Constraints, dr.ParentKeyConstraint); } rowValue.Add (relationColumn.ColumnName, relationColumn.GetAutoIncrementValue()); } // Create new row and add all values to the row. // then add it to the table. DataRow row = table.NewRow (); IDictionaryEnumerator enumerator = rowValue.GetEnumerator (); while (enumerator.MoveNext ()) { row [enumerator.Key.ToString ()] = StringToObject (table.Columns[enumerator.Key.ToString ()].DataType, enumerator.Value.ToString ()); } if (fillRows) table.Rows.Add (row); } // this method calculates the depth of child nodes tree // and it counts nodes of type XmlNodeType.Element only private static int XmlNodeElementsDepth(XmlNode node) { int maxDepth = -1; if ((node != null)) { if ((node.HasChildNodes) && (node.FirstChild.NodeType == XmlNodeType.Element)) { for (int i=0; i 0) { foreach(XmlNode childNode in node.ChildNodes) { if (childNode.NodeType != XmlNodeType.Element) { haveChildElements = false; break; } } } else { haveChildElements = false; } return haveChildElements; } private bool HaveText(XmlNode node) { bool haveText = true; if(node.ChildNodes.Count > 0) { foreach(XmlNode childNode in node.ChildNodes) { if (childNode.NodeType != XmlNodeType.Text) { haveText = false; break; } } } else { haveText = false; } return haveText; } private bool IsRepeat(XmlNode node) { bool isRepeat = false; if(node.ParentNode != null) { foreach(XmlNode childNode in node.ParentNode.ChildNodes) { if(childNode != node && childNode.Name == node.Name) { isRepeat = true; break; } } } return isRepeat; } private bool HaveAttributes(XmlNode node) { return (node.Attributes != null && node.Attributes.Count > 0); } private bool IsInferredAsTable(XmlNode node) { // Elements that have attributes are inferred as tables. // Elements that have child elements are inferred as tables. // Elements that repeat are inferred as a single table. return (HaveChildElements(node) || HaveAttributes(node) || IsRepeat(node)); } /// /// Returns true is any node that is repeated node for the node supplied /// (i.e. is child node of node's parent, have the same name and is not the node itself) /// have child elements /// private bool IsRepeatedHaveChildNodes(XmlNode node) { bool isRepeatedHaveChildElements = false; if(node.ParentNode != null) { foreach(XmlNode childNode in node.ParentNode.ChildNodes) { if(childNode != node && childNode.Name == node.Name) { if (HaveChildElements(childNode)) { isRepeatedHaveChildElements = true; break; } } } } return isRepeatedHaveChildElements; } #endregion // Private helper methods } }