2004-04-13 Gonzalo Paniagua Javier <gonzalo@ximian.com>
[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 //
13 // (c)copyright 2002 Ville Palo
14 //
15 // XmlDataLoader is included within the Mono Class Library.
16 //
17
18 using System;
19 using System.Data;
20 using System.Xml;
21 using System.Xml.XPath;
22 using System.Collections;
23 using System.Globalization;
24
25 namespace System.Data 
26 {
27
28         internal class XmlDataLoader
29         {
30         
31                 private DataSet DSet;
32                 Hashtable DiffGrRows = new Hashtable ();
33
34                 public XmlDataLoader (DataSet set) 
35                 {
36                         DSet = set;
37                 }
38
39                 public XmlReadMode LoadData (XmlReader reader, XmlReadMode mode)
40                 {
41                         XmlReadMode Result = XmlReadMode.Auto;
42
43                         switch (mode) {
44
45                                 case XmlReadMode.Fragment:
46                                         break;
47                                 case XmlReadMode.ReadSchema:
48                                 case XmlReadMode.IgnoreSchema:
49                                 case XmlReadMode.InferSchema:
50                                         Result = mode;
51                                         ReadModeSchema (reader, mode);
52                                         break;
53                                 default:
54                                         break;
55                         }
56
57                         return Result;
58                 }
59
60                 #region reading
61
62                 // Read information from the reader.
63                 private void ReadModeSchema (XmlReader reader, XmlReadMode mode)
64                 {
65                         bool inferSchema = mode == XmlReadMode.InferSchema ? true : false;
66                         //check if the current element is schema.
67                         if (String.Compare (reader.LocalName, "schema", true) == 0) {
68                                 
69                                 if (mode == XmlReadMode.InferSchema || mode == XmlReadMode.IgnoreSchema)
70                                         reader.Skip(); // skip the schema node.
71                                 else
72                                         DSet.ReadXmlSchema(reader);
73                                 
74                                 reader.MoveToContent();
75                         }
76                         // load an XmlDocument from the reader.
77                         XmlDocument doc = BuildXmlDocument(reader);
78
79                         // treatment for .net compliancy :
80                         // if xml representing dataset has exactly depth of 2 elements,
81                         // than the root element actually represents datatable and not dataset
82                         // so we add new root element to doc 
83                         // in order to create an element representing dataset.
84                         int rootNodeDepth = XmlNodeElementsDepth(doc.DocumentElement);
85                         if (rootNodeDepth == 2) {
86                                 // new dataset name
87                                 String newDataSetName = "NewDataSet";
88                                 // create new document
89                                 XmlDocument newDoc = new XmlDocument();
90                                 // create element for dataset
91                                 XmlElement datasetElement = newDoc.CreateElement(newDataSetName);
92                                 // make the new created element to be the new doc root
93                                 newDoc.AppendChild(datasetElement);
94                                 // import all the elements from doc and insert them into new doc
95                                 XmlNode root = newDoc.ImportNode(doc.DocumentElement,true);
96                                 datasetElement.AppendChild(root);
97                                 doc = newDoc;
98                                 // update dataset name
99                                 DSet.DataSetName = newDataSetName;                      
100                         }
101
102                         // set EnforceConstraint to false - we do not want any validation during 
103                         // load time.
104                         bool origEnforceConstraint = DSet.EnforceConstraints;
105                         DSet.EnforceConstraints = false;
106
107                         // The childs are tables.
108                         XmlNodeList nList = doc.DocumentElement.ChildNodes;
109
110                         for (int i = 0; i < nList.Count; i++) {
111                                 XmlNode node = nList[i];
112                                 // node represents a table onky if it is of type XmlNodeType.Element
113                                 if (node.NodeType == XmlNodeType.Element) {
114                                         AddRowToTable(node, null, inferSchema);
115                                 }
116                         }
117
118                         // set the EnforceConstraints to original value;
119                         DSet.EnforceConstraints = origEnforceConstraint;
120                 }
121
122                 #endregion // reading
123
124                 #region Private helper methods
125                 
126                 private void ReadColumns (XmlReader reader, DataRow row, DataTable table, string TableName)
127                 {
128                         do {
129                                 if (reader.NodeType == XmlNodeType.Element) {
130                                         DataColumn col = table.Columns [reader.LocalName];
131                                         if (col != null) {
132                                                 row [col] = StringToObject (col.DataType, reader.Value);
133                                         }
134                                         reader.Read ();
135                                 }
136                                 else {
137                                         reader.Read ();
138                                 }
139                                 
140                         } while (table.TableName != reader.LocalName 
141                                 || reader.NodeType != XmlNodeType.EndElement);
142                 }
143
144                 internal static object StringToObject (Type type, string value)
145                 {
146                         if (type == null) return value;
147
148                         switch (Type.GetTypeCode (type)) {
149                                 case TypeCode.Boolean: return XmlConvert.ToBoolean (value);
150                                 case TypeCode.Byte: return XmlConvert.ToByte (value);
151                                 case TypeCode.Char: return (char)XmlConvert.ToInt32 (value);
152                                 case TypeCode.DateTime: return XmlConvert.ToDateTime (value);
153                                 case TypeCode.Decimal: return XmlConvert.ToDecimal (value);
154                                 case TypeCode.Double: return XmlConvert.ToDouble (value);
155                                 case TypeCode.Int16: return XmlConvert.ToInt16 (value);
156                                 case TypeCode.Int32: return XmlConvert.ToInt32 (value);
157                                 case TypeCode.Int64: return XmlConvert.ToInt64 (value);
158                                 case TypeCode.SByte: return XmlConvert.ToSByte (value);
159                                 case TypeCode.Single: return XmlConvert.ToSingle (value);
160                                 case TypeCode.UInt16: return XmlConvert.ToUInt16 (value);
161                                 case TypeCode.UInt32: return XmlConvert.ToUInt32 (value);
162                                 case TypeCode.UInt64: return XmlConvert.ToUInt64 (value);
163                         }
164
165                         if (type == typeof (TimeSpan)) return XmlConvert.ToTimeSpan (value);
166                         if (type == typeof (Guid)) return XmlConvert.ToGuid (value);
167                         if (type == typeof (byte[])) return Convert.FromBase64String (value);
168
169                         return Convert.ChangeType (value, type);
170                 }
171
172                 private void AddRowToTable(XmlNode tableNode, DataColumn relationColumn, bool inferSchema)
173                 {
174                         Hashtable rowValue = new Hashtable();
175                         DataTable table;
176                         
177                         // Check if the table exists in the DataSet. If not create one.
178                         if (DSet.Tables.Contains(tableNode.LocalName))
179                                 table = DSet.Tables[tableNode.LocalName];
180                         else if (inferSchema) {
181                                 table = new DataTable(tableNode.LocalName);
182                                 DSet.Tables.Add(table);
183                         }
184                         else
185                                 return;
186
187                         // For elements that are inferred as tables and that contain text 
188                         // but have no child elements, a new column named "TableName_Text" 
189                         // is created for the text of each of the elements. 
190                         // If an element is inferred as a table and has text, but also has child elements,
191                         // the text is ignored.
192                         // Note : if an element is inferred as a table and has text 
193                         // and has no child elements, 
194                         // but the repeated ements of this table have child elements, 
195                         // then the text is ignored.
196                         if(!HaveChildElements(tableNode) && HaveText(tableNode) &&
197                                 !IsRepeatedHaveChildNodes(tableNode)) {
198                                 string columnName = tableNode.Name + "_Text";
199                                 if (!table.Columns.Contains(columnName)) {
200                                         table.Columns.Add(columnName);
201                                 }
202                                 rowValue.Add(columnName, tableNode.InnerText);
203                         }
204                         
205                         // Get the child nodes of the table. Any child can be one of the following tow:
206                         // 1. DataTable - if there was a relation with another table..
207                         // 2. DataColumn - column of the current table.
208                         XmlNodeList childList = tableNode.ChildNodes;
209                         for (int i = 0; i < childList.Count; i++) {
210                                 XmlNode childNode = childList[i];
211
212                                 // we are looping through elements only
213                                 // Note : if an element is inferred as a table and has text, but also has child elements,
214                                 // the text is ignored.
215                                 if (childNode.NodeType != XmlNodeType.Element)
216                                         continue;
217                                 
218                                 // Elements that have attributes are inferred as tables. 
219                                 // Elements that have child elements are inferred as tables. 
220                                 // Elements that repeat are inferred as a single table. 
221                                 if (IsInferedAsTable(childNode)) {
222                                         // child node infered as table
223                                         if (inferSchema) {
224                                                 // We need to create new column for the relation between the current
225                                                 // table and the new table we found (the child table).
226                                                 string newRelationColumnName = table.TableName + "_Id";
227                                                 if (!table.Columns.Contains(newRelationColumnName)) {
228                                                         DataColumn newRelationColumn = new DataColumn(newRelationColumnName, typeof(int));
229                                                         newRelationColumn.AutoIncrement = true;
230                                                         // we do not want to serialize this column so MappingType is Hidden.
231                                                         newRelationColumn.ColumnMapping = MappingType.Hidden;
232                                                         table.Columns.Add(newRelationColumn);
233                                                 }
234                                                 // Add a row to the new table we found.
235                                                 AddRowToTable(childNode, table.Columns[newRelationColumnName], inferSchema);
236                                         }
237                                         else
238                                                 AddRowToTable(childNode, null, inferSchema);
239                                         
240                                 }
241                                 else {
242                                         // Elements that have no attributes or child elements, and do not repeat, 
243                                         // are inferred as columns.
244                                         object val = null;
245                                         if (childNode.FirstChild != null)
246                                                 val = childNode.FirstChild.Value;
247                                         else
248                                                 val = "";
249                                         if (table.Columns.Contains(childNode.LocalName))
250                                                 rowValue.Add(childNode.LocalName, val);
251                                         else if (inferSchema) {
252                                                 table.Columns.Add(childNode.LocalName);
253                                                 rowValue.Add(childNode.LocalName, val);
254                                         }
255                                 }
256                                                 
257                         }
258
259                         // Column can be attribute of the table element.
260                         XmlAttributeCollection aCollection = tableNode.Attributes;
261                         for (int i = 0; i < aCollection.Count; i++) {
262                                 XmlAttribute attr = aCollection[i];
263                                 //the atrribute can be the namespace.
264                                 if (attr.Prefix.Equals("xmlns"))
265                                         table.Namespace = attr.Value;
266                                 else { // the attribute is a column.
267                                         if (!table.Columns.Contains(attr.LocalName)) {
268                                                 DataColumn col = table.Columns.Add(attr.LocalName);
269                                                 col.ColumnMapping = MappingType.Attribute;
270                                         }
271                                         table.Columns[attr.LocalName].Namespace = table.Namespace;
272
273                                         rowValue.Add(attr.LocalName, attr.Value);
274                                 }
275                         }
276
277                         // If the current table is a child table we need to add a new column for the relation
278                         // and add a new relation to the DataSet.
279                         if (relationColumn != null) {
280                                 if (!table.Columns.Contains(relationColumn.ColumnName)) {
281                                         DataColumn dc = new DataColumn(relationColumn.ColumnName, typeof(int));
282                                         // we do not want to serialize this column so MappingType is Hidden.
283                                         dc.ColumnMapping = MappingType.Hidden;
284                                         table.Columns.Add(dc);
285                                         // Convention of relation name is: ParentTableName_ChildTableName
286                                         DataRelation dr = new DataRelation(relationColumn.Table.TableName + "_" + dc.Table.TableName, relationColumn, dc);
287                                         dr.Nested = true;
288                                         DSet.Relations.Add(dr);
289                                 }
290                                 rowValue.Add (relationColumn.ColumnName, relationColumn.GetAutoIncrementValue());
291                         }
292
293                         // Create new row and add all values to the row.
294                         // then add it to the table.
295                         DataRow row = table.NewRow ();
296                                         
297                         IDictionaryEnumerator enumerator = rowValue.GetEnumerator ();
298                         while (enumerator.MoveNext ()) {
299                                 row [enumerator.Key.ToString ()] = StringToObject (table.Columns[enumerator.Key.ToString ()].DataType, enumerator.Value.ToString ());
300                         }
301
302                         table.Rows.Add (row);
303                         
304                 }
305                 
306                 // bulid the document from the reader.
307                 private XmlDocument BuildXmlDocument(XmlReader reader)
308                 {
309                         XmlDocument doc = new XmlDocument();
310                         // Create the root element. This is the DataSet element.
311                         XmlElement dataSetElement = doc.CreateElement(DSet.DataSetName);
312                         
313                         do {
314                                 XmlNode n = doc.ReadNode (reader);
315                                 if(n == null) break;
316                                 // Add the table nodes to the DataSet node.
317                                 dataSetElement.AppendChild (n);
318                         } while (reader.IsStartElement());
319                         
320                         // Add the DataSet element to the document.
321                         doc.AppendChild(dataSetElement);
322                         return doc;
323                 }
324
325                 // this method calculates the depth of child nodes tree
326                 // and it counts nodes of type XmlNodeType.Element only
327                 private static int XmlNodeElementsDepth(XmlNode node)
328                 {
329                         int maxDepth = -1;
330             if ((node != null)) {
331                                 if  ((node.HasChildNodes) && (node.FirstChild.NodeType == XmlNodeType.Element)) {
332                                         for (int i=0; i<node.ChildNodes.Count; i++) {
333                                                 if (node.ChildNodes[i].NodeType == XmlNodeType.Element) {
334                                                         int childDepth = XmlNodeElementsDepth(node.ChildNodes[i]);
335                                                         maxDepth = (maxDepth < childDepth) ? childDepth : maxDepth;
336                                                 }
337                                         }
338                                 }
339                                 else {
340                                         return 1;
341                                 }
342                         }
343                         else {
344                                 return -1;
345                         }
346
347                         return (maxDepth + 1);
348                 }
349
350                 private bool HaveChildElements(XmlNode node)
351                 {
352                         bool haveChildElements = true;
353                         if(node.ChildNodes.Count > 0) {
354                                 foreach(XmlNode childNode in node.ChildNodes) {
355                                         if (childNode.NodeType != XmlNodeType.Element) {
356                                                 haveChildElements = false;
357                                                 break;
358                                         }
359                                 }
360                         }
361                         else {
362                                 haveChildElements = false;
363                         }
364                         return haveChildElements;
365                 }
366
367                 private bool HaveText(XmlNode node)
368                 {
369                         bool haveText = true;
370                         if(node.ChildNodes.Count > 0) {
371                                 foreach(XmlNode childNode in node.ChildNodes) {
372                                         if (childNode.NodeType != XmlNodeType.Text) {
373                                                 haveText = false;
374                                                 break;
375                                         }
376                                 }
377                         }
378                         else {
379                                 haveText = false;
380                         }
381                         return haveText;
382                 }
383
384                 private bool IsRepeat(XmlNode node)
385                 {
386                         bool isRepeat = false;
387                         if(node.ParentNode != null) {
388                                 foreach(XmlNode childNode in node.ParentNode.ChildNodes) {
389                                         if(childNode != node && childNode.Name == node.Name) {
390                                                 isRepeat = true;
391                                                 break;
392                                         }
393                                 }
394                         }
395                         return isRepeat;
396                 }
397
398                 private bool HaveAttributes(XmlNode node)
399                 {
400                         return (node.Attributes != null && node.Attributes.Count > 0);
401                 }
402
403                 private bool IsInferedAsTable(XmlNode node)
404                 {
405                         // Elements that have attributes are inferred as tables. 
406                         // Elements that have child elements are inferred as tables. 
407                         // Elements that repeat are inferred as a single table. 
408                         return (HaveChildElements(node) || HaveAttributes(node) ||
409                                         IsRepeat(node));
410                 }
411
412                 /// <summary>
413                 /// Returns true is any node that is repeated node for the node supplied
414                 /// (i.e. is child node of node's parent, have the same name and is not the node itself)
415                 /// have child elements
416                 /// </summary>
417                 private bool IsRepeatedHaveChildNodes(XmlNode node)
418                 {
419                         bool isRepeatedHaveChildElements = false;
420                         if(node.ParentNode != null) {
421                                 foreach(XmlNode childNode in node.ParentNode.ChildNodes) {
422                                         if(childNode != node && childNode.Name == node.Name) {
423                                                 if (HaveChildElements(childNode)) {
424                                                         isRepeatedHaveChildElements = true;
425                                                         break;
426                                                 }
427                                         }
428                                 }
429                         }
430                         return isRepeatedHaveChildElements;
431                 }
432
433                 #endregion // Private helper methods
434
435                 
436         }
437
438 }