Merge remote-tracking branch 'raof/aot-cpu-safety'
[mono.git] / mcs / class / System.Data / System.Data / XmlDataLoader.cs
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>
13 //
14 // (c)copyright 2002 Ville Palo
15 // (C)2004 Novell Inc.
16 //
17 // XmlDataLoader is included within the Mono Class Library.
18 //
19
20 //
21 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
22 //
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.
41 //
42
43 using System;
44 using System.Data;
45 using System.Xml;
46 using System.Collections;
47 using System.Globalization;
48
49 namespace System.Data 
50 {
51
52         internal class XmlDataLoader
53         {
54         
55                 private DataSet DSet;
56
57                 public XmlDataLoader (DataSet set) 
58                 {
59                         DSet = set;
60                 }
61
62                 public XmlReadMode LoadData (XmlReader reader, XmlReadMode mode)
63                 {
64                         XmlReadMode Result = mode;
65
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;
82                         }
83
84                         return Result;
85                 }
86
87                 #region reading
88
89                 // Read information from the reader.
90                 private void ReadModeSchema (XmlReader reader, XmlReadMode mode)
91                 {
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>
102                         //
103                         // FIXME: This, this check should (also) be done
104                         // after reading the top-level element.
105
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);
112                                 
113                                 reader.MoveToContent();
114                         }
115
116                         // load an XmlDocument from the reader.
117                         XmlDocument doc = new XmlDocument ();
118                         doc.Load (reader);
119                         if (doc.DocumentElement == null)
120                                 return;
121
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.
127                         //
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;
137                                 }
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;
156                                 }
157                                 break;
158                         }
159
160                         // set EnforceConstraint to false - we do not want any validation during 
161                         // load time.
162                         bool origEnforceConstraint = DSet.EnforceConstraints;
163                         DSet.EnforceConstraints = false;
164
165                         // The childs are tables.
166                         XmlNodeList nList = doc.DocumentElement.ChildNodes;
167
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);
175                                 }
176                         }
177                         // set the EnforceConstraints to original value;
178                         DSet.EnforceConstraints = origEnforceConstraint;
179                 }
180
181                 #endregion // reading
182
183                 #region Private helper methods
184 /*              
185                 private void ReadColumns (XmlReader reader, DataRow row, DataTable table, string TableName)
186                 {
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);
192                                         }
193                                         reader.Read ();
194                                 }
195                                 else {
196                                         reader.Read ();
197                                 }
198                                 
199                         } while (table.TableName != reader.LocalName 
200                                 || reader.NodeType != XmlNodeType.EndElement);
201                 }
202 */
203                 internal static object StringToObject (Type type, string value)
204                 {
205                         if (type == null) return value;
206
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 #if NET_2_0
212                                 case TypeCode.DateTime: return XmlConvert.ToDateTime (value, XmlDateTimeSerializationMode.Unspecified);
213 #else
214                                 case TypeCode.DateTime: return XmlConvert.ToDateTime (value);
215 #endif
216                                 case TypeCode.Decimal: return XmlConvert.ToDecimal (value);
217                                 case TypeCode.Double: return XmlConvert.ToDouble (value);
218                                 case TypeCode.Int16: return XmlConvert.ToInt16 (value);
219                                 case TypeCode.Int32: return XmlConvert.ToInt32 (value);
220                                 case TypeCode.Int64: return XmlConvert.ToInt64 (value);
221                                 case TypeCode.SByte: return XmlConvert.ToSByte (value);
222                                 case TypeCode.Single: return XmlConvert.ToSingle (value);
223                                 case TypeCode.UInt16: return XmlConvert.ToUInt16 (value);
224                                 case TypeCode.UInt32: return XmlConvert.ToUInt32 (value);
225                                 case TypeCode.UInt64: return XmlConvert.ToUInt64 (value);
226                         }
227
228                         if (type == typeof (TimeSpan)) return XmlConvert.ToTimeSpan (value);
229                         if (type == typeof (Guid)) return XmlConvert.ToGuid (value);
230                         if (type == typeof (byte[])) return Convert.FromBase64String (value);
231                         if (type == typeof (System.Type)) return System.Type.GetType (value);
232
233                         return Convert.ChangeType (value, type);
234                 }
235
236                 private void AddRowToTable(XmlNode tableNode, DataColumn relationColumn, bool inferSchema, bool fillRows)
237                 {
238                         Hashtable rowValue = new Hashtable();
239                         DataTable table;
240
241                         // Check if the table exists in the DataSet. If not create one.
242                         if (DSet.Tables.Contains(tableNode.LocalName))
243                                 table = DSet.Tables[tableNode.LocalName];
244                         else if (inferSchema) {
245                                 table = new DataTable(tableNode.LocalName);
246                                 DSet.Tables.Add(table);
247                         }
248                         else
249                                 return;
250
251                         // For elements that are inferred as tables and that contain text 
252                         // but have no child elements, a new column named "TableName_Text" 
253                         // is created for the text of each of the elements. 
254                         // If an element is inferred as a table and has text, but also has child elements,
255                         // the text is ignored.
256                         // Note : if an element is inferred as a table and has text 
257                         // and has no child elements, 
258                         // but the repeated ements of this table have child elements, 
259                         // then the text is ignored.
260                         if(!HaveChildElements(tableNode) && HaveText(tableNode) &&
261                                 !IsRepeatedHaveChildNodes(tableNode)) {
262                                 string columnName = tableNode.Name + "_Text";
263                                 if (!table.Columns.Contains(columnName)) {
264                                         table.Columns.Add(columnName);
265                                 }
266                                 rowValue.Add(columnName, tableNode.InnerText);
267                         }
268                         
269                         // Get the child nodes of the table. Any child can be one of the following tow:
270                         // 1. DataTable - if there was a relation with another table..
271                         // 2. DataColumn - column of the current table.
272                         XmlNodeList childList = tableNode.ChildNodes;
273                         for (int i = 0; i < childList.Count; i++) {
274                                 XmlNode childNode = childList[i];
275
276                                 // we are looping through elements only
277                                 // Note : if an element is inferred as a table and has text, but also has child elements,
278                                 // the text is ignored.
279                                 if (childNode.NodeType != XmlNodeType.Element)
280                                         continue;
281                                 
282                                 // Elements that have attributes are inferred as tables. 
283                                 // Elements that have child elements are inferred as tables. 
284                                 // Elements that repeat are inferred as a single table. 
285                                 if (IsInferredAsTable(childNode)) {
286                                         // child node inferred as table
287                                         if (inferSchema) {
288                                                 // We need to create new column for the relation between the current
289                                                 // table and the new table we found (the child table).
290                                                 string newRelationColumnName = table.TableName + "_Id";
291                                                 if (!table.Columns.Contains(newRelationColumnName)) {
292                                                         DataColumn newRelationColumn = new DataColumn(newRelationColumnName, typeof(int));
293                                                         newRelationColumn.AllowDBNull = false;
294                                                         newRelationColumn.AutoIncrement = true;
295                                                         // we do not want to serialize this column so MappingType is Hidden.
296                                                         newRelationColumn.ColumnMapping = MappingType.Hidden;
297                                                         table.Columns.Add(newRelationColumn);
298                                                 }
299                                                 // Add a row to the new table we found.
300                                                 AddRowToTable(childNode, table.Columns[newRelationColumnName], inferSchema, fillRows);
301                                         }
302                                         else
303                                                 AddRowToTable(childNode, null, inferSchema, fillRows);
304                                         
305                                 }
306                                 else {
307                                         // Elements that have no attributes or child elements, and do not repeat, 
308                                         // are inferred as columns.
309                                         object val = null;
310                                         if (childNode.FirstChild != null)
311                                                 val = childNode.FirstChild.Value;
312                                         else
313                                                 val = "";
314                                         if (table.Columns.Contains(childNode.LocalName))
315                                                 rowValue.Add(childNode.LocalName, val);
316                                         else if (inferSchema) {
317                                                 table.Columns.Add(childNode.LocalName);
318                                                 rowValue.Add(childNode.LocalName, val);
319                                         }
320                                 }
321                                                 
322                         }
323
324                         // Column can be attribute of the table element.
325                         XmlAttributeCollection aCollection = tableNode.Attributes;
326                         for (int i = 0; i < aCollection.Count; i++) {
327                                 XmlAttribute attr = aCollection[i];
328                                 //the atrribute can be the namespace.
329                                 if (attr.Prefix.Equals("xmlns"))
330                                         table.Namespace = attr.Value;
331                                 else { // the attribute is a column.
332                                         if (!table.Columns.Contains(attr.LocalName)) {
333                                                 DataColumn col = table.Columns.Add(attr.LocalName);
334                                                 col.ColumnMapping = MappingType.Attribute;
335                                         }
336                                         table.Columns[attr.LocalName].Namespace = table.Namespace;
337
338                                         rowValue.Add(attr.LocalName, attr.Value);
339                                 }
340                         }
341
342                         // If the current table is a child table we need to add a new column for the relation
343                         // and add a new relation to the DataSet.
344                         if (relationColumn != null) {
345                                 if (!table.Columns.Contains(relationColumn.ColumnName)) {
346                                         DataColumn dc = new DataColumn(relationColumn.ColumnName, typeof(int));
347                                         // we do not want to serialize this column so MappingType is Hidden.
348                                         dc.ColumnMapping = MappingType.Hidden;
349                                         table.Columns.Add(dc);
350                                         // Convention of relation name is: ParentTableName_ChildTableName
351                                         DataRelation dr = new DataRelation(relationColumn.Table.TableName + "_" + dc.Table.TableName, relationColumn, dc);
352                                         dr.Nested = true;
353                                         DSet.Relations.Add(dr);
354                                         UniqueConstraint.SetAsPrimaryKey (dr.ParentTable.Constraints, dr.ParentKeyConstraint);
355                                 }
356                                 rowValue.Add (relationColumn.ColumnName, relationColumn.GetAutoIncrementValue());
357                         }
358
359                         // Create new row and add all values to the row.
360                         // then add it to the table.
361                         DataRow row = table.NewRow ();
362                                         
363                         IDictionaryEnumerator enumerator = rowValue.GetEnumerator ();
364                         while (enumerator.MoveNext ()) {
365                                 row [enumerator.Key.ToString ()] = StringToObject (table.Columns[enumerator.Key.ToString ()].DataType, enumerator.Value.ToString ());
366                         }
367
368                         if (fillRows)
369                                 table.Rows.Add (row);
370                         
371                 }
372
373                 // this method calculates the depth of child nodes tree
374                 // and it counts nodes of type XmlNodeType.Element only
375                 private static int XmlNodeElementsDepth(XmlNode node)
376                 {
377                         int maxDepth = -1;
378             if ((node != null)) {
379                                 if  ((node.HasChildNodes) && (node.FirstChild.NodeType == XmlNodeType.Element)) {
380                                         for (int i=0; i<node.ChildNodes.Count; i++) {
381                                                 if (node.ChildNodes[i].NodeType == XmlNodeType.Element) {
382                                                         int childDepth = XmlNodeElementsDepth(node.ChildNodes[i]);
383                                                         maxDepth = (maxDepth < childDepth) ? childDepth : maxDepth;
384                                                 }
385                                         }
386                                 }
387                                 else {
388                                         return 1;
389                                 }
390                         }
391                         else {
392                                 return -1;
393                         }
394
395                         return (maxDepth + 1);
396                 }
397
398                 private bool HaveChildElements(XmlNode node)
399                 {
400                         bool haveChildElements = true;
401                         if(node.ChildNodes.Count > 0) {
402                                 foreach(XmlNode childNode in node.ChildNodes) {
403                                         if (childNode.NodeType != XmlNodeType.Element) {
404                                                 haveChildElements = false;
405                                                 break;
406                                         }
407                                 }
408                         }
409                         else {
410                                 haveChildElements = false;
411                         }
412                         return haveChildElements;
413                 }
414
415                 private bool HaveText(XmlNode node)
416                 {
417                         bool haveText = true;
418                         if(node.ChildNodes.Count > 0) {
419                                 foreach(XmlNode childNode in node.ChildNodes) {
420                                         if (childNode.NodeType != XmlNodeType.Text) {
421                                                 haveText = false;
422                                                 break;
423                                         }
424                                 }
425                         }
426                         else {
427                                 haveText = false;
428                         }
429                         return haveText;
430                 }
431
432                 private bool IsRepeat(XmlNode node)
433                 {
434                         bool isRepeat = false;
435                         if(node.ParentNode != null) {
436                                 foreach(XmlNode childNode in node.ParentNode.ChildNodes) {
437                                         if(childNode != node && childNode.Name == node.Name) {
438                                                 isRepeat = true;
439                                                 break;
440                                         }
441                                 }
442                         }
443                         return isRepeat;
444                 }
445
446                 private bool HaveAttributes(XmlNode node)
447                 {
448                         return (node.Attributes != null && node.Attributes.Count > 0);
449                 }
450
451                 private bool IsInferredAsTable(XmlNode node)
452                 {
453                         // Elements that have attributes are inferred as tables. 
454                         // Elements that have child elements are inferred as tables. 
455                         // Elements that repeat are inferred as a single table. 
456                         return (HaveChildElements(node) || HaveAttributes(node) ||
457                                         IsRepeat(node));
458                 }
459
460                 /// <summary>
461                 /// Returns true is any node that is repeated node for the node supplied
462                 /// (i.e. is child node of node's parent, have the same name and is not the node itself)
463                 /// have child elements
464                 /// </summary>
465                 private bool IsRepeatedHaveChildNodes(XmlNode node)
466                 {
467                         bool isRepeatedHaveChildElements = false;
468                         if(node.ParentNode != null) {
469                                 foreach(XmlNode childNode in node.ParentNode.ChildNodes) {
470                                         if(childNode != node && childNode.Name == node.Name) {
471                                                 if (HaveChildElements(childNode)) {
472                                                         isRepeatedHaveChildElements = true;
473                                                         break;
474                                                 }
475                                         }
476                                 }
477                         }
478                         return isRepeatedHaveChildElements;
479                 }
480
481                 #endregion // Private helper methods
482
483                 
484         }
485
486 }