* XmlDataReader.cs, XmlDataInferenceLoader.cs, XmlDiffLoader.cs:
[mono.git] / mcs / class / System.Data / System.Data / XmlDataReader.cs
1 \r
2 //\r
3 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)\r
4 //\r
5 // Permission is hereby granted, free of charge, to any person obtaining\r
6 // a copy of this software and associated documentation files (the\r
7 // "Software"), to deal in the Software without restriction, including\r
8 // without limitation the rights to use, copy, modify, merge, publish,\r
9 // distribute, sublicense, and/or sell copies of the Software, and to\r
10 // permit persons to whom the Software is furnished to do so, subject to\r
11 // the following conditions:\r
12 // \r
13 // The above copyright notice and this permission notice shall be\r
14 // included in all copies or substantial portions of the Software.\r
15 // \r
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
18 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r
20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r
21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r
22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
23 //\r
24 \r
25 using System;\r
26 using System.IO;\r
27 using System.Data;\r
28 using System.Xml;\r
29 \r
30 \r
31 namespace System.Data\r
32 {\r
33 #if STANDALONE_DRIVER_TEST\r
34         public class Driver\r
35         {\r
36                 public static void Main (string [] args)\r
37                 {\r
38                         if (args.Length == 0) {\r
39                                 Console.WriteLine ("usage: mono xmldatareader.exe filename");\r
40                                 return;\r
41                         }\r
42 \r
43                         Console.WriteLine ("Target file: " + args [0]);\r
44 \r
45                         DataSet ds = new DataSet ();\r
46 //                      ds.InferXmlSchema (args [0], null);\r
47 \r
48                         try {\r
49                                 ds.ReadXml (args [0]);\r
50                         } catch (Exception ex) {\r
51                                 Console.WriteLine ("ReadXml() borked: " + ex.Message);\r
52                                 return;\r
53                         }\r
54                         Console.WriteLine ("---- DataSet ----------------");\r
55                         StringWriter sw = new StringWriter ();\r
56                         PrintDataSet (ds, sw);\r
57                         PrintDataSet (ds, Console.Out);\r
58 \r
59                         ds = new DataSet ();\r
60                         ds.InferXmlSchema (args [0], null);\r
61                         XmlDataReader.ReadXml (ds, new XmlTextReader (args [0]));\r
62                         Console.WriteLine ("---- XmlDataReader ----------------");\r
63                         StringWriter sw2 = new StringWriter ();\r
64                         PrintDataSet (ds, sw2);\r
65 \r
66                         if (sw.ToString () == sw2.ToString ())\r
67                                 Console.WriteLine ("Successful.");\r
68                         else\r
69                                 Console.WriteLine ("Different *************************************************\n" + sw2);\r
70                 }\r
71 \r
72                 private static void PrintDataSet (DataSet ds, TextWriter tw)\r
73                 {\r
74                         tw.WriteLine ("DS::" + ds.DataSetName + ", " + ds.Tables.Count + ", " + ds.Relations.Count);\r
75                         foreach (DataTable dt in ds.Tables)\r
76                                 tw.WriteLine ("DT:" + dt.TableName + ", " + dt.Columns.Count + ", " + dt.Rows.Count);\r
77 \r
78                         ds.WriteXml (tw);\r
79                         tw.WriteLine ();\r
80                 }\r
81         }\r
82 #endif\r
83 \r
84         internal class XmlDataReader\r
85         {\r
86                 const string xmlnsNS = "http://www.w3.org/2000/xmlns/";\r
87 \r
88                 public static void ReadXml (\r
89                         DataSet dataset, XmlReader reader, XmlReadMode mode)\r
90                 {\r
91                         new XmlDataReader (dataset, reader, mode).Process ();\r
92                 }\r
93 \r
94                 DataSet dataset;\r
95                 XmlReader reader;\r
96                 XmlReadMode mode;\r
97 \r
98                 public XmlDataReader (DataSet ds, XmlReader xr, XmlReadMode m)\r
99                 {\r
100                         dataset = ds;\r
101                         reader =xr;\r
102                         mode = m;\r
103                 }\r
104 \r
105                 private void Process ()\r
106                 {\r
107                         bool savedEnforceConstraints =\r
108                                 dataset.EnforceConstraints;\r
109                         try {\r
110                         dataset.EnforceConstraints = false;\r
111                         reader.MoveToContent ();\r
112 \r
113                         if (mode == XmlReadMode.Fragment) {\r
114                                         while (reader.NodeType == XmlNodeType.Element && !reader.EOF) {\r
115                                                 ReadTopLevelElement ();\r
116                                         }\r
117                                 }\r
118                                 else\r
119                                         ReadTopLevelElement ();\r
120                         } finally {\r
121                                 dataset.EnforceConstraints = \r
122                                         savedEnforceConstraints;\r
123                         }\r
124                 }\r
125 \r
126                 private bool IsTopLevelDataSet ()\r
127                 {\r
128                         string local = XmlHelper.Decode (reader.LocalName);\r
129 \r
130                         // No need to check DataSetName. In fact, it is ignored.\r
131 \r
132                         bool ambiguous = false;\r
133                         DataTable dt = dataset.Tables [local];\r
134                         if (dt == null)\r
135                                 return true;\r
136 \r
137                         XmlDocument doc = new XmlDocument ();\r
138                         XmlElement el = (XmlElement) doc.ReadNode (reader);\r
139                         doc.AppendChild (el);\r
140                         reader = new XmlNodeReader (el);\r
141                         reader.MoveToContent ();\r
142 \r
143                         return !XmlDataInferenceLoader.IsDocumentElementTable (\r
144                                 el, null);\r
145                 }\r
146 \r
147                 private void ReadTopLevelElement ()\r
148                 {\r
149                         if (mode == XmlReadMode.Fragment &&\r
150                                 (XmlHelper.Decode (reader.LocalName) !=\r
151                                 dataset.DataSetName ||\r
152                                 reader.NamespaceURI != dataset.Namespace))\r
153                                 reader.Skip ();\r
154                         else if (mode == XmlReadMode.Fragment ||\r
155                                 IsTopLevelDataSet ()) {\r
156                         int depth = reader.Depth;\r
157                         reader.Read ();\r
158                         reader.MoveToContent ();\r
159                         do {\r
160                                 ReadDataSetContent ();\r
161                         } while (reader.Depth > depth && !reader.EOF);\r
162                         if (reader.NodeType == XmlNodeType.EndElement)\r
163                                 reader.ReadEndElement ();\r
164                         reader.MoveToContent ();\r
165                 }\r
166                         else\r
167                                 ReadDataSetContent ();\r
168                 }\r
169 \r
170                 private void ReadDataSetContent ()\r
171                 {\r
172                         DataTable table = dataset.Tables [XmlHelper.Decode (reader.LocalName)];\r
173                         if (table == null || table.Namespace != reader.NamespaceURI) {\r
174                                 reader.Skip ();\r
175                                 reader.MoveToContent ();\r
176                                 return; // skip if there is no matching table\r
177                         }\r
178 \r
179                         // skip if namespace does not match.\r
180                         if (table.Namespace != reader.NamespaceURI) {\r
181                                 reader.Skip ();\r
182                                 reader.MoveToContent ();\r
183                                 return; // skip if there is no matching table\r
184                         }\r
185 \r
186                         DataRow row = table.NewRow ();\r
187                         ReadElement (row);\r
188                         table.Rows.Add (row);\r
189                 }\r
190 \r
191                 private void ReadElement (DataRow row)\r
192                 {\r
193                         // Consume attributes\r
194                         if (reader.MoveToFirstAttribute ()) {\r
195                                 do {\r
196                                         if (reader.NamespaceURI == XmlConstants.XmlnsNS\r
197 #if NET_2_0\r
198                                         || reader.NamespaceURI == XmlConstants.XmlNS\r
199 #endif\r
200                                                 )\r
201                                                 continue;\r
202                                         ReadElementAttribute (row);\r
203                                 } while (reader.MoveToNextAttribute ());\r
204                                 reader.MoveToElement ();\r
205                         }\r
206 \r
207                         // If not empty element, read content.\r
208                         if (reader.IsEmptyElement) {\r
209                                 reader.Skip ();\r
210                                 reader.MoveToContent ();\r
211                         } else {\r
212                                 int depth = reader.Depth;\r
213                                 reader.Read ();\r
214                                 reader.MoveToContent ();\r
215                                 do {\r
216                                         ReadElementContent (row);\r
217                                 } while (reader.Depth > depth && !reader.EOF);\r
218                                 if (reader.IsEmptyElement)\r
219                                         reader.Read ();\r
220                                 if (reader.NodeType == XmlNodeType.EndElement)\r
221                                         reader.ReadEndElement ();\r
222                                 reader.MoveToContent ();\r
223                         }\r
224                 }\r
225 \r
226                 private void ReadElementAttribute (DataRow row)\r
227                 {\r
228                         DataColumn col = row.Table.Columns [XmlHelper.Decode (reader.LocalName)];\r
229                         if (col == null || col.Namespace != reader.NamespaceURI)\r
230                                 return;\r
231                         row [col] = StringToObject (col.DataType, reader.Value);\r
232                 }\r
233 \r
234                 private void ReadElementContent (DataRow row)\r
235                 {\r
236                         switch (reader.NodeType) {\r
237 \r
238                         case XmlNodeType.EndElement:\r
239                                 // This happens when the content was only whitespace (and skipped by MoveToContent()).\r
240                                 return;\r
241 \r
242                         case XmlNodeType.Element:\r
243                                 ReadElementElement (row);\r
244                                 break;\r
245 \r
246                         case XmlNodeType.Text:\r
247                         case XmlNodeType.CDATA:\r
248                         case XmlNodeType.SignificantWhitespace:\r
249                                 DataColumn simple = null;\r
250                                 DataColumnCollection cols = row.Table.Columns;\r
251                                 for (int i = 0; i < cols.Count; i++) {\r
252                                         DataColumn col = cols [i];\r
253                                         if (col.ColumnMapping ==\r
254                                                 MappingType.SimpleContent) {\r
255                                                 simple = col;\r
256                                                 break;\r
257                                         }\r
258                                 }\r
259                                 string s = reader.ReadString ();\r
260                                 reader.MoveToContent ();\r
261 #if SILLY_MS_COMPATIBLE\r
262 // As to MS, "test string" and "test <!-- comment -->string" are different :P\r
263                                 if (simple != null && row.IsNull (simple))\r
264                                         row [simple] = StringToObject (simple.DataType, s);\r
265 #else\r
266 // But it does not mean we support "123<!-- comment -->456". just allowed for string\r
267                                 if (simple != null)\r
268                                         row [simple] += s;\r
269 #endif\r
270                                 break;\r
271                         case XmlNodeType.Whitespace:\r
272                                 reader.ReadString ();\r
273                                 break;\r
274                         }\r
275                 }\r
276 \r
277                 private void ReadElementElement (DataRow row)\r
278                 {\r
279                         // This child element (for row) might be either simple\r
280                         // content element, or child element\r
281 \r
282                         // MS.NET crashes here... but it seems just a bug.\r
283                         DataColumn col = row.Table.Columns [XmlHelper.Decode (reader.LocalName)];\r
284                         if (col == null ||  col.Namespace != reader.NamespaceURI)\r
285                                 col = null;\r
286 \r
287                         // if col exists, then it should be MappingType.Element\r
288                         if (col != null\r
289                                 && col.ColumnMapping == MappingType.Element) {\r
290 \r
291                                 // TODO: This part is suspicious for\r
292                                 // MS compatibility (test required)\r
293                                 if (col.Namespace != reader.NamespaceURI) {\r
294                                         reader.Skip ();\r
295                                         return;\r
296                                 }\r
297 \r
298                                 bool wasEmpty = reader.IsEmptyElement;\r
299                                 int depth = reader.Depth;\r
300                                 row [col] = StringToObject (col.DataType, reader.ReadElementString ());\r
301                                 if (!wasEmpty && reader.Depth > depth) {\r
302                                 // This means, instance does not match with\r
303                                 // the schema (because the instance element\r
304                                 // contains complex content, while specified as\r
305                                 // simple), so just skip to the end of the\r
306                                 // element.\r
307                                         while (reader.Depth > depth)\r
308                                                 reader.Read ();\r
309                                         reader.Read ();\r
310                                 }\r
311                                 reader.MoveToContent ();\r
312                                 return;\r
313                         } else if (col != null) {\r
314                                 // Mismatch column type. Just skip\r
315                                 reader.Skip ();\r
316                                 reader.MoveToContent ();\r
317                                 return;\r
318                         }\r
319 \r
320                         // Otherwise, it might be child table element\r
321                         DataRelationCollection rels = row.Table.ChildRelations;\r
322                         for (int i = 0; i < rels.Count; i++) {\r
323                                 DataRelation rel = rels [i];\r
324                                 if (!rel.Nested)\r
325                                         continue;\r
326                                 DataTable ct = rel.ChildTable;\r
327                                 if (ct.TableName != XmlHelper.Decode (reader.LocalName) || ct.Namespace != reader.NamespaceURI)\r
328                                         continue;\r
329 \r
330                                 DataRow childRow = rel.ChildTable.NewRow ();\r
331                                 ReadElement (childRow);\r
332 \r
333                                 for (int c = 0; c < rel.ChildColumns.Length; c++) {\r
334                                         childRow [rel.ChildColumns [c]]\r
335                                                 = row [rel.ParentColumns [c]];\r
336                                 }\r
337                                 rel.ChildTable.Rows.Add (childRow);\r
338                                 return;\r
339                         }\r
340 \r
341                         // Matched neither of the above: just skip\r
342                         reader.Skip ();\r
343                         reader.MoveToContent ();\r
344                 }\r
345 \r
346                 internal static object StringToObject (Type type, string value)\r
347                 {\r
348                         if (type == null) return value;\r
349 \r
350                         switch (Type.GetTypeCode (type)) {\r
351                                 case TypeCode.Boolean: return XmlConvert.ToBoolean (value);\r
352                                 case TypeCode.Byte: return XmlConvert.ToByte (value);\r
353                                 case TypeCode.Char: return (char)XmlConvert.ToInt32 (value);\r
354 #if NET_2_0\r
355                                 case TypeCode.DateTime: return XmlConvert.ToDateTime (value, XmlDateTimeSerializationMode.Unspecified);\r
356 #else\r
357                                 case TypeCode.DateTime: return XmlConvert.ToDateTime (value);\r
358 #endif\r
359                                 case TypeCode.Decimal: return XmlConvert.ToDecimal (value);\r
360                                 case TypeCode.Double: return XmlConvert.ToDouble (value);\r
361                                 case TypeCode.Int16: return XmlConvert.ToInt16 (value);\r
362                                 case TypeCode.Int32: return XmlConvert.ToInt32 (value);\r
363                                 case TypeCode.Int64: return XmlConvert.ToInt64 (value);\r
364                                 case TypeCode.SByte: return XmlConvert.ToSByte (value);\r
365                                 case TypeCode.Single: return XmlConvert.ToSingle (value);\r
366                                 case TypeCode.UInt16: return XmlConvert.ToUInt16 (value);\r
367                                 case TypeCode.UInt32: return XmlConvert.ToUInt32 (value);\r
368                                 case TypeCode.UInt64: return XmlConvert.ToUInt64 (value);\r
369                         }\r
370 \r
371                         if (type == typeof (TimeSpan)) return XmlConvert.ToTimeSpan (value);\r
372                         if (type == typeof (Guid)) return XmlConvert.ToGuid (value);\r
373                         if (type == typeof (byte[])) return Convert.FromBase64String (value);\r
374 \r
375                         return Convert.ChangeType (value, type);\r
376                 }\r
377         }\r
378 }\r