**** Merged from MCS ****
[mono-project.git] / mcs / class / System.Data / System.Data / XmlDataLoader.cs
blobe364b858c02dbd39f79c1d92517e29b8c59b10d2
1 //
2 // mcs/class/System.Data/System.Data/XmlDataLoader.cs
3 //
4 // Purpose: Loads XmlDocument to DataSet
5 //
6 // class: XmlDataLoader
7 // assembly: System.Data.dll
8 // namespace: System.Data
9 //
10 // Author:
11 // Ville Palo <vi64pa@koti.soon.fi>
12 // Atsushi Enomoto <atsushi@ximian.com>
14 // (c)copyright 2002 Ville Palo
15 // (C)2004 Novell Inc.
17 // XmlDataLoader is included within the Mono Class Library.
21 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
23 // Permission is hereby granted, free of charge, to any person obtaining
24 // a copy of this software and associated documentation files (the
25 // "Software"), to deal in the Software without restriction, including
26 // without limitation the rights to use, copy, modify, merge, publish,
27 // distribute, sublicense, and/or sell copies of the Software, and to
28 // permit persons to whom the Software is furnished to do so, subject to
29 // the following conditions:
30 //
31 // The above copyright notice and this permission notice shall be
32 // included in all copies or substantial portions of the Software.
33 //
34 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
35 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
36 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
37 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
38 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
39 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
40 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
43 using System;
44 using System.Data;
45 using System.Xml;
46 using System.Collections;
47 using System.Globalization;
49 namespace System.Data
52 internal class XmlDataLoader
55 private DataSet DSet;
57 public XmlDataLoader (DataSet set)
59 DSet = set;
62 public XmlReadMode LoadData (XmlReader reader, XmlReadMode mode)
64 XmlReadMode Result = mode;
66 switch (mode) {
67 case XmlReadMode.Auto:
68 Result = DSet.Tables.Count == 0 ? XmlReadMode.InferSchema : XmlReadMode.IgnoreSchema;
69 ReadModeSchema (reader, DSet.Tables.Count == 0 ? XmlReadMode.Auto : XmlReadMode.IgnoreSchema);
70 break;
71 case XmlReadMode.InferSchema:
72 Result = XmlReadMode.InferSchema;
73 ReadModeSchema (reader, mode);
74 break;
75 case XmlReadMode.IgnoreSchema:
76 Result = XmlReadMode.IgnoreSchema;
77 ReadModeSchema (reader, mode);
78 break;
79 default:
80 reader.Skip ();
81 break;
84 return Result;
87 #region reading
89 // Read information from the reader.
90 private void ReadModeSchema (XmlReader reader, XmlReadMode mode)
92 bool inferSchema = mode == XmlReadMode.InferSchema || mode == XmlReadMode.Auto;
93 bool fillRows = mode != XmlReadMode.InferSchema;
94 // This check is required for full DiffGram.
95 // It is not described in MSDN and it is impossible
96 // with WriteXml(), but when writing XML using
97 // XmlSerializer, the output is like this:
98 // <dataset>
99 // <schema>...</schema>
100 // <diffgram>...</diffgram>
101 // </dataset>
103 // FIXME: This, this check should (also) be done
104 // after reading the top-level element.
106 //check if the current element is schema.
107 if (reader.LocalName == "schema") {
108 if (mode != XmlReadMode.Auto)
109 reader.Skip(); // skip the schema node.
110 else
111 DSet.ReadXmlSchema(reader);
113 reader.MoveToContent();
116 // load an XmlDocument from the reader.
117 XmlDocument doc = new XmlDocument ();
118 doc.Load (reader);
119 if (doc.DocumentElement == null)
120 return;
122 // treatment for .net compliancy :
123 // if xml representing dataset has exactly depth of 2 elements,
124 // than the root element actually represents datatable and not dataset
125 // so we add new root element to doc
126 // in order to create an element representing dataset.
128 // FIXME: Consider attributes.
129 // <root a='1' b='2' /> is regarded as a valid DataTable.
130 int rootNodeDepth = XmlNodeElementsDepth(doc.DocumentElement);
131 switch (rootNodeDepth) {
132 case 1:
133 if (inferSchema) {
134 DSet.DataSetName = doc.DocumentElement.LocalName;
135 DSet.Prefix = doc.DocumentElement.Prefix;
136 DSet.Namespace = doc.DocumentElement.NamespaceURI;
138 return;
139 case 2:
140 // create new document
141 XmlDocument newDoc = new XmlDocument();
142 // create element for dataset
143 XmlElement datasetElement = newDoc.CreateElement("dummy");
144 // make the new created element to be the new doc root
145 newDoc.AppendChild(datasetElement);
146 // import all the elements from doc and insert them into new doc
147 XmlNode root = newDoc.ImportNode(doc.DocumentElement,true);
148 datasetElement.AppendChild(root);
149 doc = newDoc;
150 break;
151 default:
152 if (inferSchema) {
153 DSet.DataSetName = doc.DocumentElement.LocalName;
154 DSet.Prefix = doc.DocumentElement.Prefix;
155 DSet.Namespace = doc.DocumentElement.NamespaceURI;
157 break;
160 // set EnforceConstraint to false - we do not want any validation during
161 // load time.
162 bool origEnforceConstraint = DSet.EnforceConstraints;
163 DSet.EnforceConstraints = false;
165 // The childs are tables.
166 XmlNodeList nList = doc.DocumentElement.ChildNodes;
168 // FIXME: When reading DataTable (not DataSet),
169 // the nodes are column items, not rows.
170 for (int i = 0; i < nList.Count; i++) {
171 XmlNode node = nList[i];
172 // node represents a table onky if it is of type XmlNodeType.Element
173 if (node.NodeType == XmlNodeType.Element) {
174 AddRowToTable(node, null, inferSchema, fillRows);
177 // set the EnforceConstraints to original value;
178 DSet.EnforceConstraints = origEnforceConstraint;
181 #endregion // reading
183 #region Private helper methods
185 private void ReadColumns (XmlReader reader, DataRow row, DataTable table, string TableName)
187 do {
188 if (reader.NodeType == XmlNodeType.Element) {
189 DataColumn col = table.Columns [reader.LocalName];
190 if (col != null) {
191 row [col] = StringToObject (col.DataType, reader.Value);
193 reader.Read ();
195 else {
196 reader.Read ();
199 } while (table.TableName != reader.LocalName
200 || reader.NodeType != XmlNodeType.EndElement);
203 internal static object StringToObject (Type type, string value)
205 if (type == null) return value;
207 switch (Type.GetTypeCode (type)) {
208 case TypeCode.Boolean: return XmlConvert.ToBoolean (value);
209 case TypeCode.Byte: return XmlConvert.ToByte (value);
210 case TypeCode.Char: return (char)XmlConvert.ToInt32 (value);
211 case TypeCode.DateTime: return XmlConvert.ToDateTime (value);
212 case TypeCode.Decimal: return XmlConvert.ToDecimal (value);
213 case TypeCode.Double: return XmlConvert.ToDouble (value);
214 case TypeCode.Int16: return XmlConvert.ToInt16 (value);
215 case TypeCode.Int32: return XmlConvert.ToInt32 (value);
216 case TypeCode.Int64: return XmlConvert.ToInt64 (value);
217 case TypeCode.SByte: return XmlConvert.ToSByte (value);
218 case TypeCode.Single: return XmlConvert.ToSingle (value);
219 case TypeCode.UInt16: return XmlConvert.ToUInt16 (value);
220 case TypeCode.UInt32: return XmlConvert.ToUInt32 (value);
221 case TypeCode.UInt64: return XmlConvert.ToUInt64 (value);
224 if (type == typeof (TimeSpan)) return XmlConvert.ToTimeSpan (value);
225 if (type == typeof (Guid)) return XmlConvert.ToGuid (value);
226 if (type == typeof (byte[])) return Convert.FromBase64String (value);
228 return Convert.ChangeType (value, type);
231 private void AddRowToTable(XmlNode tableNode, DataColumn relationColumn, bool inferSchema, bool fillRows)
233 Hashtable rowValue = new Hashtable();
234 DataTable table;
236 // Check if the table exists in the DataSet. If not create one.
237 if (DSet.Tables.Contains(tableNode.LocalName))
238 table = DSet.Tables[tableNode.LocalName];
239 else if (inferSchema) {
240 table = new DataTable(tableNode.LocalName);
241 DSet.Tables.Add(table);
243 else
244 return;
246 // For elements that are inferred as tables and that contain text
247 // but have no child elements, a new column named "TableName_Text"
248 // is created for the text of each of the elements.
249 // If an element is inferred as a table and has text, but also has child elements,
250 // the text is ignored.
251 // Note : if an element is inferred as a table and has text
252 // and has no child elements,
253 // but the repeated ements of this table have child elements,
254 // then the text is ignored.
255 if(!HaveChildElements(tableNode) && HaveText(tableNode) &&
256 !IsRepeatedHaveChildNodes(tableNode)) {
257 string columnName = tableNode.Name + "_Text";
258 if (!table.Columns.Contains(columnName)) {
259 table.Columns.Add(columnName);
261 rowValue.Add(columnName, tableNode.InnerText);
264 // Get the child nodes of the table. Any child can be one of the following tow:
265 // 1. DataTable - if there was a relation with another table..
266 // 2. DataColumn - column of the current table.
267 XmlNodeList childList = tableNode.ChildNodes;
268 for (int i = 0; i < childList.Count; i++) {
269 XmlNode childNode = childList[i];
271 // we are looping through elements only
272 // Note : if an element is inferred as a table and has text, but also has child elements,
273 // the text is ignored.
274 if (childNode.NodeType != XmlNodeType.Element)
275 continue;
277 // Elements that have attributes are inferred as tables.
278 // Elements that have child elements are inferred as tables.
279 // Elements that repeat are inferred as a single table.
280 if (IsInferedAsTable(childNode)) {
281 // child node infered as table
282 if (inferSchema) {
283 // We need to create new column for the relation between the current
284 // table and the new table we found (the child table).
285 string newRelationColumnName = table.TableName + "_Id";
286 if (!table.Columns.Contains(newRelationColumnName)) {
287 DataColumn newRelationColumn = new DataColumn(newRelationColumnName, typeof(int));
288 newRelationColumn.AllowDBNull = false;
289 newRelationColumn.AutoIncrement = true;
290 // we do not want to serialize this column so MappingType is Hidden.
291 newRelationColumn.ColumnMapping = MappingType.Hidden;
292 table.Columns.Add(newRelationColumn);
294 // Add a row to the new table we found.
295 AddRowToTable(childNode, table.Columns[newRelationColumnName], inferSchema, fillRows);
297 else
298 AddRowToTable(childNode, null, inferSchema, fillRows);
301 else {
302 // Elements that have no attributes or child elements, and do not repeat,
303 // are inferred as columns.
304 object val = null;
305 if (childNode.FirstChild != null)
306 val = childNode.FirstChild.Value;
307 else
308 val = "";
309 if (table.Columns.Contains(childNode.LocalName))
310 rowValue.Add(childNode.LocalName, val);
311 else if (inferSchema) {
312 table.Columns.Add(childNode.LocalName);
313 rowValue.Add(childNode.LocalName, val);
319 // Column can be attribute of the table element.
320 XmlAttributeCollection aCollection = tableNode.Attributes;
321 for (int i = 0; i < aCollection.Count; i++) {
322 XmlAttribute attr = aCollection[i];
323 //the atrribute can be the namespace.
324 if (attr.Prefix.Equals("xmlns"))
325 table.Namespace = attr.Value;
326 else { // the attribute is a column.
327 if (!table.Columns.Contains(attr.LocalName)) {
328 DataColumn col = table.Columns.Add(attr.LocalName);
329 col.ColumnMapping = MappingType.Attribute;
331 table.Columns[attr.LocalName].Namespace = table.Namespace;
333 rowValue.Add(attr.LocalName, attr.Value);
337 // If the current table is a child table we need to add a new column for the relation
338 // and add a new relation to the DataSet.
339 if (relationColumn != null) {
340 if (!table.Columns.Contains(relationColumn.ColumnName)) {
341 DataColumn dc = new DataColumn(relationColumn.ColumnName, typeof(int));
342 // we do not want to serialize this column so MappingType is Hidden.
343 dc.ColumnMapping = MappingType.Hidden;
344 table.Columns.Add(dc);
345 // Convention of relation name is: ParentTableName_ChildTableName
346 DataRelation dr = new DataRelation(relationColumn.Table.TableName + "_" + dc.Table.TableName, relationColumn, dc);
347 dr.Nested = true;
348 DSet.Relations.Add(dr);
349 UniqueConstraint.SetAsPrimaryKey (dr.ParentTable.Constraints, dr.ParentKeyConstraint);
351 rowValue.Add (relationColumn.ColumnName, relationColumn.GetAutoIncrementValue());
354 // Create new row and add all values to the row.
355 // then add it to the table.
356 DataRow row = table.NewRow ();
358 IDictionaryEnumerator enumerator = rowValue.GetEnumerator ();
359 while (enumerator.MoveNext ()) {
360 row [enumerator.Key.ToString ()] = StringToObject (table.Columns[enumerator.Key.ToString ()].DataType, enumerator.Value.ToString ());
363 if (fillRows)
364 table.Rows.Add (row);
368 // this method calculates the depth of child nodes tree
369 // and it counts nodes of type XmlNodeType.Element only
370 private static int XmlNodeElementsDepth(XmlNode node)
372 int maxDepth = -1;
373 if ((node != null)) {
374 if ((node.HasChildNodes) && (node.FirstChild.NodeType == XmlNodeType.Element)) {
375 for (int i=0; i<node.ChildNodes.Count; i++) {
376 if (node.ChildNodes[i].NodeType == XmlNodeType.Element) {
377 int childDepth = XmlNodeElementsDepth(node.ChildNodes[i]);
378 maxDepth = (maxDepth < childDepth) ? childDepth : maxDepth;
382 else {
383 return 1;
386 else {
387 return -1;
390 return (maxDepth + 1);
393 private bool HaveChildElements(XmlNode node)
395 bool haveChildElements = true;
396 if(node.ChildNodes.Count > 0) {
397 foreach(XmlNode childNode in node.ChildNodes) {
398 if (childNode.NodeType != XmlNodeType.Element) {
399 haveChildElements = false;
400 break;
404 else {
405 haveChildElements = false;
407 return haveChildElements;
410 private bool HaveText(XmlNode node)
412 bool haveText = true;
413 if(node.ChildNodes.Count > 0) {
414 foreach(XmlNode childNode in node.ChildNodes) {
415 if (childNode.NodeType != XmlNodeType.Text) {
416 haveText = false;
417 break;
421 else {
422 haveText = false;
424 return haveText;
427 private bool IsRepeat(XmlNode node)
429 bool isRepeat = false;
430 if(node.ParentNode != null) {
431 foreach(XmlNode childNode in node.ParentNode.ChildNodes) {
432 if(childNode != node && childNode.Name == node.Name) {
433 isRepeat = true;
434 break;
438 return isRepeat;
441 private bool HaveAttributes(XmlNode node)
443 return (node.Attributes != null && node.Attributes.Count > 0);
446 private bool IsInferedAsTable(XmlNode node)
448 // Elements that have attributes are inferred as tables.
449 // Elements that have child elements are inferred as tables.
450 // Elements that repeat are inferred as a single table.
451 return (HaveChildElements(node) || HaveAttributes(node) ||
452 IsRepeat(node));
455 /// <summary>
456 /// Returns true is any node that is repeated node for the node supplied
457 /// (i.e. is child node of node's parent, have the same name and is not the node itself)
458 /// have child elements
459 /// </summary>
460 private bool IsRepeatedHaveChildNodes(XmlNode node)
462 bool isRepeatedHaveChildElements = false;
463 if(node.ParentNode != null) {
464 foreach(XmlNode childNode in node.ParentNode.ChildNodes) {
465 if(childNode != node && childNode.Name == node.Name) {
466 if (HaveChildElements(childNode)) {
467 isRepeatedHaveChildElements = true;
468 break;
473 return isRepeatedHaveChildElements;
476 #endregion // Private helper methods