In System.Data.OleDb:
[mono.git] / mcs / class / System.Data / System.Data / XmlDataInferenceLoader.cs
1 //\r
2 // XmlDataInferenceLoader.cs\r
3 //\r
4 // Author:\r
5 //\r
6 //      Atsushi Enomoto <atsushi@ximian.com>\r
7 //\r
8 // (C)2004 Novell Inc.\r
9 //\r
10 // Design notes are the bottom of the source.\r
11 //\r
12
13 //
14 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
15 //
16 // Permission is hereby granted, free of charge, to any person obtaining
17 // a copy of this software and associated documentation files (the
18 // "Software"), to deal in the Software without restriction, including
19 // without limitation the rights to use, copy, modify, merge, publish,
20 // distribute, sublicense, and/or sell copies of the Software, and to
21 // permit persons to whom the Software is furnished to do so, subject to
22 // the following conditions:
23 // 
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
26 // 
27 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 //
35 using System;\r
36 using System.Collections;\r
37 using System.Data;\r
38 using System.IO; // for Driver\r
39 using System.Text; // for Driver\r
40 using System.Xml;\r
41 using System.Xml.Schema;\r
42 using System.Xml.Serialization;\r
43 \r
44 namespace System.Data\r
45 {\r
46         internal enum ElementMappingType {\r
47                 Simple,\r
48                 Repeated,\r
49                 Complex\r
50         }\r
51 \r
52         internal class TableMappingCollection : CollectionBase\r
53         {\r
54                 public void Add (TableMapping map)\r
55                 {\r
56                         this.List.Add (map);\r
57                 }\r
58 \r
59                 public TableMapping this [string name] {\r
60                         get {\r
61                                 foreach (TableMapping map in List)\r
62                                         if (map.Table.TableName == name)\r
63                                                 return map;\r
64                                 return null;\r
65                         }\r
66                 }\r
67         }\r
68 \r
69         internal class TableMapping\r
70         {\r
71                 private bool existsInDataSet;\r
72 \r
73                 public DataTable Table;\r
74                 public ArrayList Elements = new ArrayList ();\r
75                 public ArrayList Attributes = new ArrayList ();\r
76                 public DataColumn SimpleContent;\r
77                 public DataColumn PrimaryKey;\r
78                 public DataColumn ReferenceKey;\r
79 \r
80                 // Parent TableMapping\r
81                 public TableMapping ParentTable;\r
82 \r
83                 // decoded LocalName -> TableMapping\r
84                 public TableMappingCollection ChildTables = new TableMappingCollection ();\r
85 \r
86                 public TableMapping (string name, string ns)\r
87                 {\r
88                         Table = new DataTable (name);\r
89                         Table.Namespace = ns;\r
90                 }\r
91 \r
92                 public TableMapping (DataTable dt)\r
93                 {\r
94                         existsInDataSet = true;\r
95                         Table = dt;\r
96                         foreach (DataColumn col in dt.Columns) {\r
97                                 switch (col.ColumnMapping) {\r
98                                 case MappingType.Element:\r
99                                         Elements.Add (col);\r
100                                         break;\r
101                                 case MappingType.Attribute:\r
102                                         Attributes.Add (col);\r
103                                         break;\r
104                                 case MappingType.SimpleContent:\r
105                                         SimpleContent = col;\r
106                                         break;\r
107                                 }\r
108                         }\r
109                         PrimaryKey = dt.PrimaryKey.Length > 0 ? dt.PrimaryKey [0] : null;\r
110                 }\r
111 \r
112                 public bool ExistsInDataSet {\r
113                         get { return existsInDataSet; }\r
114                 }\r
115 \r
116                 public bool ContainsColumn (string name)\r
117                 {\r
118                         return GetColumn (name) != null;\r
119                 }\r
120 \r
121                 public DataColumn GetColumn (string name)\r
122                 {\r
123                         foreach (DataColumn col in Elements)\r
124                                 if (col.ColumnName == name)\r
125                                         return col;\r
126                         foreach (DataColumn col in Attributes)\r
127                                 if (col.ColumnName == name)\r
128                                         return col;\r
129                         if (SimpleContent != null && name == SimpleContent.ColumnName)\r
130                                 return SimpleContent;\r
131                         if (PrimaryKey != null && name == PrimaryKey.ColumnName)\r
132                                 return PrimaryKey;\r
133                         return null;\r
134                 }\r
135 \r
136                 public void RemoveElementColumn (string name)\r
137                 {\r
138                         foreach (DataColumn col in Elements) {\r
139                                 if (col.ColumnName == name) {\r
140                                         Elements.Remove (col);\r
141                                         return;\r
142                                 }\r
143                         }\r
144                 }\r
145         }\r
146 \r
147         internal class XmlDataInferenceLoader\r
148         {\r
149                 public static void Infer (DataSet dataset, XmlDocument document, XmlReadMode mode, string [] ignoredNamespaces)\r
150                 {\r
151                         new XmlDataInferenceLoader (dataset, document, mode, ignoredNamespaces).ReadXml ();\r
152                 }\r
153 \r
154                 private XmlDataInferenceLoader (DataSet ds, XmlDocument doc, XmlReadMode mode, string [] ignoredNamespaces)\r
155                 {\r
156                         dataset = ds;\r
157                         document = doc;\r
158                         this.mode = mode;\r
159                         this.ignoredNamespaces = ignoredNamespaces != null ? new ArrayList (ignoredNamespaces) : new ArrayList ();\r
160 \r
161                         // Fill existing table info\r
162                         foreach (DataTable dt in dataset.Tables)\r
163                                 tables.Add (new TableMapping (dt));\r
164                 }\r
165 \r
166                 DataSet dataset;\r
167                 XmlDocument document;\r
168                 XmlReadMode mode;\r
169                 ArrayList ignoredNamespaces;\r
170                 TableMappingCollection tables = new TableMappingCollection ();\r
171                 RelationStructureCollection relations = new RelationStructureCollection ();\r
172 \r
173                 private void ReadXml ()\r
174                 {\r
175                         if (document.DocumentElement == null)\r
176                                 return;\r
177 \r
178                         // If the root element is not a data table, treat \r
179                         // this element as DataSet.\r
180                         // Read one element. It might be DataSet element.\r
181                         XmlElement el = document.DocumentElement;\r
182 \r
183                         if (el.NamespaceURI == XmlSchema.Namespace)\r
184                                 throw new InvalidOperationException ("DataSet is not designed to handle XML Schema as data content. Please use ReadXmlSchema method instead of InferXmlSchema method.");\r
185 \r
186                         if (IsDocumentElementTable ())\r
187                                 InferTopLevelTable (el);\r
188                         else {\r
189                                 string localName = XmlConvert.DecodeName (el.LocalName);\r
190                                 dataset.DataSetName = localName;\r
191                                 dataset.Namespace = el.NamespaceURI;\r
192                                 dataset.Prefix = el.Prefix;\r
193                                 foreach (XmlNode n in el.ChildNodes) {\r
194                                         if (n.NamespaceURI == XmlSchema.Namespace)\r
195                                                 continue;\r
196                                         if (n.NodeType == XmlNodeType.Element)\r
197                                                 InferTopLevelTable (n as XmlElement);\r
198                                 }\r
199                         }\r
200 \r
201                         foreach (TableMapping map in tables) {\r
202                                 foreach (TableMapping ct in map.ChildTables) {\r
203                                         ct.ReferenceKey = GetMappedColumn (ct, map.Table.TableName + "_Id", map.Table.Prefix, map.Table.Namespace, MappingType.Hidden);\r
204                                 }\r
205                         }\r
206 \r
207                         foreach (TableMapping map in tables) {\r
208                                 if (map.ExistsInDataSet)\r
209                                         continue;\r
210                                 if (map.PrimaryKey != null)\r
211                                         map.Table.Columns.Add (map.PrimaryKey);\r
212                                 foreach (DataColumn col in map.Elements)\r
213                                         map.Table.Columns.Add (col);\r
214                                 foreach (DataColumn col in map.Attributes)\r
215                                         map.Table.Columns.Add (col);\r
216                                 if (map.SimpleContent != null)\r
217                                         map.Table.Columns.Add (map.SimpleContent);\r
218                                 if (map.ReferenceKey != null)\r
219                                         map.Table.Columns.Add (map.ReferenceKey);\r
220                                 dataset.Tables.Add (map.Table);\r
221                         }\r
222 \r
223                         foreach (RelationStructure rs in relations) {\r
224                                 string relName = rs.ExplicitName != null ? rs.ExplicitName : rs.ParentTableName + "_" + rs.ChildTableName;\r
225                                 DataTable pt = dataset.Tables [rs.ParentTableName];\r
226                                 DataTable ct = dataset.Tables [rs.ChildTableName];\r
227                                 DataColumn pc = pt.Columns [rs.ParentColumnName];\r
228                                 DataColumn cc = ct.Columns [rs.ChildColumnName];\r
229                                 if (pt == null)\r
230                                         throw new DataException ("Parent table was not found : " + rs.ParentTableName);\r
231                                 else if (ct == null)\r
232                                         throw new DataException ("Child table was not found : " + rs.ChildTableName);\r
233                                 else if (pc == null)\r
234                                         throw new DataException ("Parent column was not found :" + rs.ParentColumnName);\r
235                                 else if (cc == null)\r
236                                         throw new DataException ("Child column was not found :" + rs.ChildColumnName);\r
237                                 DataRelation rel = new DataRelation (relName, pc, cc, rs.CreateConstraint);\r
238                                 if (rs.IsNested) {\r
239                                         rel.Nested = true;\r
240                                         rel.ParentTable.PrimaryKey = rel.ParentColumns;\r
241                                 }\r
242                                 dataset.Relations.Add (rel);\r
243                         }\r
244                 }\r
245 \r
246                 private void InferTopLevelTable (XmlElement el)\r
247                 {\r
248                         InferTableElement (null, el);\r
249                 }\r
250 \r
251                 private void InferColumnElement (TableMapping table, XmlElement el)\r
252                 {\r
253                         string localName = XmlConvert.DecodeName (el.LocalName);\r
254                         DataColumn col = table.GetColumn (localName);\r
255                         if (col != null) {\r
256                                 if (col.ColumnMapping != MappingType.Element)\r
257                                         throw new DataException (String.Format ("Column {0} is already mapped to {1}.", localName, col.ColumnMapping));\r
258                                 return;\r
259                         }\r
260                         if (table.ChildTables [localName] != null)\r
261                                 // Child is already mapped, or infered as a table\r
262                                 // (in that case, that takes precedence than\r
263                                 // this simple column inference.)\r
264                                 return;\r
265 \r
266                         col = new DataColumn (localName, typeof (string));\r
267                         col.Namespace = el.NamespaceURI;\r
268                         col.Prefix = el.Prefix;\r
269                         table.Elements.Add (col);\r
270                 }\r
271 \r
272                 private void CheckExtraneousElementColumn (TableMapping parentTable, XmlElement el)\r
273                 {\r
274                         if (parentTable == null)\r
275                                 return;\r
276                         string localName = XmlConvert.DecodeName (el.LocalName);\r
277                         DataColumn elc = parentTable.GetColumn (localName);\r
278                         if (elc != null)\r
279                                 parentTable.RemoveElementColumn (localName);\r
280                 }\r
281 \r
282                 private void PopulatePrimaryKey (TableMapping table)\r
283                 {\r
284                         if (table.PrimaryKey != null) {\r
285                                 if (table.PrimaryKey.ColumnName != table.Table.TableName + "_Id")\r
286                                         throw new DataException ("There is already a primary key column.");\r
287                                 return;\r
288                         }\r
289                         DataColumn col = new DataColumn (table.Table.TableName + "_Id");\r
290                         col.ColumnMapping = MappingType.Hidden;\r
291                         col.DataType = typeof (int);\r
292                         col.AllowDBNull = false;\r
293                         col.AutoIncrement = true;\r
294                         col.Namespace = table.Table.Namespace;\r
295                         col.Prefix = table.Table.Prefix;\r
296                         table.PrimaryKey = col;\r
297                 }\r
298 \r
299                 private void PopulateRelationStructure (string parent, string child)\r
300                 {\r
301                         if (relations [parent, child] != null)\r
302                                 return;\r
303 \r
304                         RelationStructure rs = new RelationStructure ();\r
305                         rs.ParentTableName = parent;\r
306                         rs.ChildTableName = child;\r
307                         rs.ParentColumnName = parent + "_Id";\r
308                         rs.ChildColumnName = parent + "_Id";\r
309                         rs.CreateConstraint = true;\r
310                         rs.IsNested = true;\r
311                         relations.Add (rs);\r
312                 }\r
313 \r
314                 private void InferRepeatedElement (TableMapping parentTable, XmlElement el)\r
315                 {\r
316                         string localName = XmlConvert.DecodeName (el.LocalName);\r
317                         // FIXME: can be checked later\r
318                         CheckExtraneousElementColumn (parentTable, el);\r
319                         TableMapping table = GetMappedTable (parentTable, localName, el.NamespaceURI);\r
320 \r
321                         // If the mapping is actually complex type (not simple\r
322                         // repeatable), then ignore it.\r
323                         if (table.Elements.Count > 0)\r
324                                 return;\r
325 \r
326                         // If simple column already exists, do nothing\r
327                         if (table.SimpleContent != null)\r
328                                 return;\r
329 \r
330                         GetMappedColumn (table, localName + "_Column", el.Prefix, el.NamespaceURI, MappingType.SimpleContent);\r
331                 }\r
332 \r
333                 private void InferTableElement (TableMapping parentTable, XmlElement el)\r
334                 {\r
335                         // If parent table already has the same name column but\r
336                         // mapped as Element, that must be removed.\r
337                         // FIXME: This can be done later (doing it here is\r
338                         // loss of performance.\r
339                         CheckExtraneousElementColumn (parentTable, el);\r
340 \r
341                         string localName = XmlConvert.DecodeName (el.LocalName);\r
342                         TableMapping table = GetMappedTable (parentTable, localName, el.NamespaceURI);\r
343 \r
344                         bool hasChildElements = false;\r
345                         bool hasAttributes = false;\r
346                         bool hasText = false;\r
347 \r
348                         foreach (XmlAttribute attr in el.Attributes) {\r
349                                 if (attr.NamespaceURI == XmlConstants.XmlnsNS)\r
350                                         continue;\r
351                                 if (ignoredNamespaces != null &&\r
352                                         ignoredNamespaces.Contains (attr.NamespaceURI))\r
353                                         continue;\r
354 \r
355                                 hasAttributes = true;\r
356                                 DataColumn col = GetMappedColumn (table,\r
357                                         XmlConvert.DecodeName (attr.LocalName),\r
358                                         attr.Prefix,\r
359                                         attr.NamespaceURI,\r
360                                         MappingType.Attribute);\r
361                         }\r
362 \r
363                         foreach (XmlNode n in el.ChildNodes) {\r
364                                 switch (n.NodeType) {\r
365                                 case XmlNodeType.Comment:\r
366                                 case XmlNodeType.ProcessingInstruction: // ignore\r
367                                         continue;\r
368                                 default: // text content\r
369                                         hasText = true;\r
370                                         break;\r
371                                 case XmlNodeType.Element: // child\r
372                                         hasChildElements = true;\r
373                                         XmlElement cel = n as XmlElement;\r
374                                         string childLocalName = XmlConvert.DecodeName (cel.LocalName);\r
375 \r
376                                         switch (GetElementMappingType (cel, ignoredNamespaces, null)) {\r
377                                         case ElementMappingType.Simple:\r
378                                                 InferColumnElement (table, cel);\r
379                                                 break;\r
380                                         case ElementMappingType.Repeated:\r
381                                                 PopulatePrimaryKey (table);\r
382                                                 PopulateRelationStructure (table.Table.TableName, childLocalName);\r
383                                                 InferRepeatedElement (table, cel);\r
384                                                 break;\r
385                                         case ElementMappingType.Complex:\r
386                                                 PopulatePrimaryKey (table);\r
387                                                 PopulateRelationStructure (table.Table.TableName, childLocalName);\r
388                                                 InferTableElement (table, cel);\r
389                                                 break;\r
390                                         }\r
391                                         break;\r
392                                 }\r
393                         }\r
394 \r
395                         // Attributes + !Children + Text = SimpleContent\r
396                         if (table.SimpleContent == null // no need to create\r
397                                 && !hasChildElements && hasText && hasAttributes) {\r
398                                 GetMappedColumn (table, table.Table.TableName + "_Text", String.Empty, String.Empty, MappingType.SimpleContent);\r
399                         }\r
400                 }\r
401 \r
402                 private TableMapping GetMappedTable (TableMapping parent, string tableName, string ns)\r
403                 {\r
404                         TableMapping map = tables [tableName];\r
405                         if (map != null) {\r
406                                 if (parent != null && map.ParentTable != null && map.ParentTable != parent)\r
407                                         throw new DataException (String.Format ("The table '{0}' is already allocated as a child of another table '{1}'. Cannot set table '{2}' as parent table.", tableName, map.ParentTable.Table.TableName, parent.Table.TableName));\r
408                         } else {\r
409                                 map = new TableMapping (tableName, ns);\r
410                                 map.ParentTable = parent;\r
411                                 tables.Add (map);\r
412                         }\r
413                         if (parent != null) {\r
414                                 bool shouldAdd = true;\r
415                                 foreach (TableMapping child in parent.ChildTables) {\r
416                                         if (child.Table.TableName == tableName) {\r
417                                                 shouldAdd = false;\r
418                                                 break;\r
419                                         }\r
420                                 }\r
421                                 if (shouldAdd)\r
422                                         parent.ChildTables.Add (map);\r
423                         }\r
424                         return map;\r
425                 }\r
426 \r
427                 private DataColumn GetMappedColumn (TableMapping table, string name, string prefix, string ns, MappingType type)\r
428                 {\r
429                         DataColumn col = table.GetColumn (name);\r
430                         // Infer schema\r
431                         if (col == null) {\r
432                                 col = new DataColumn (name);\r
433                                 col.Prefix = prefix;\r
434                                 col.Namespace = ns;\r
435                                 col.ColumnMapping = type;\r
436                                 switch (type) {\r
437                                 case MappingType.Element:\r
438                                         table.Elements.Add (col);\r
439                                         break;\r
440                                 case MappingType.Attribute:\r
441                                         table.Attributes.Add (col);\r
442                                         break;\r
443                                 case MappingType.SimpleContent:\r
444                                         table.SimpleContent = col;\r
445                                         break;\r
446                                 case MappingType.Hidden:\r
447                                         // To generate parent key\r
448                                         col.DataType = typeof (int);\r
449                                         table.ReferenceKey = col;\r
450                                         break;\r
451                                 }\r
452                         }\r
453                         else if (col.ColumnMapping != type) // Check mapping type\r
454                                 throw new DataException (String.Format ("There are already another column that has different mapping type. Column is {0}, existing mapping type is {1}", col.ColumnName, col.ColumnMapping));\r
455 \r
456                         return col;\r
457                 }\r
458 \r
459                 private static void SetAsExistingTable (XmlElement el, Hashtable existingTables)\r
460                 {\r
461                         if (existingTables == null)\r
462                                 return;\r
463                         ArrayList al = existingTables [el.NamespaceURI] as ArrayList;\r
464                         if (al == null) {\r
465                                 al = new ArrayList ();\r
466                                 existingTables [el.NamespaceURI] = al;\r
467                         }\r
468                         if (al.Contains (el.LocalName))\r
469                                 return;\r
470                         al.Add (el.LocalName);\r
471                 }\r
472 \r
473                 private static ElementMappingType GetElementMappingType (\r
474                         XmlElement el, ArrayList ignoredNamespaces, Hashtable existingTables)\r
475                 {\r
476                         if (existingTables != null) {\r
477                                 ArrayList al = existingTables [el.NamespaceURI] as ArrayList;\r
478                                 if (al != null && al.Contains (el.LocalName))\r
479                                         // this is not precise, but it is enough\r
480                                         // for IsDocumentElementTable().\r
481                                         return ElementMappingType.Complex;\r
482                         }\r
483 \r
484                         foreach (XmlAttribute attr in el.Attributes) {\r
485                                 if (attr.NamespaceURI == XmlConstants.XmlnsNS)\r
486                                         continue;\r
487                                 if (ignoredNamespaces != null && ignoredNamespaces.Contains (attr.NamespaceURI))\r
488                                         continue;\r
489                                 SetAsExistingTable (el, existingTables);\r
490                                 return ElementMappingType.Complex;\r
491                         }\r
492                         foreach (XmlNode n in el.ChildNodes) {\r
493                                 if (n.NodeType == XmlNodeType.Element) {\r
494                                         SetAsExistingTable (el, existingTables);\r
495                                         return ElementMappingType.Complex;\r
496                                 }\r
497                         }\r
498 \r
499                         for (XmlNode n = el.NextSibling; n != null; n = n.NextSibling) {\r
500                                 if (n.NodeType == XmlNodeType.Element && n.LocalName == el.LocalName) {\r
501                                         SetAsExistingTable (el, existingTables);\r
502                                         return GetElementMappingType (n as XmlElement,\r
503                                                 ignoredNamespaces, null)\r
504                                                 == ElementMappingType.Complex ?\r
505                                                 ElementMappingType.Complex :\r
506                                                 ElementMappingType.Repeated;\r
507                                 }\r
508                         }\r
509 \r
510                         return ElementMappingType.Simple;\r
511                 }\r
512 \r
513                 private bool IsDocumentElementTable ()\r
514                 {\r
515                         return IsDocumentElementTable (\r
516                                 document.DocumentElement,\r
517                                 ignoredNamespaces);\r
518                 }\r
519 \r
520                 internal static bool IsDocumentElementTable (XmlElement top,\r
521                         ArrayList ignoredNamespaces)\r
522                 {\r
523                         foreach (XmlAttribute attr in top.Attributes) {\r
524                                 if (attr.NamespaceURI == XmlConstants.XmlnsNS)\r
525                                         continue;\r
526                                 if (ignoredNamespaces != null &&\r
527                                         ignoredNamespaces.Contains (attr.NamespaceURI))\r
528                                         continue;\r
529                                 // document element has attributes other than xmlns\r
530                                 return true;\r
531                         }\r
532                         Hashtable existingTables = new Hashtable ();\r
533                         foreach (XmlNode n in top.ChildNodes) {\r
534                                 XmlElement el = n as XmlElement;\r
535                                 if (el == null)\r
536                                         continue;\r
537                                 if (GetElementMappingType (el, ignoredNamespaces,\r
538                                         existingTables)\r
539                                         == ElementMappingType.Simple)\r
540                                         return true;\r
541                         }\r
542                         return false;\r
543                 }\r
544 \r
545                 // Returns if it "might" be a column element (this method is\r
546                 // called per child element, thus it might still consist of\r
547                 // table, since it might be repeated).\r
548                 private bool IsPossibleColumnElement (XmlElement el)\r
549                 {\r
550                         foreach (XmlAttribute attr in el.Attributes) {\r
551                                 if (attr.NamespaceURI == XmlConstants.XmlnsNS)\r
552                                         continue;\r
553                                 return false;\r
554                         }\r
555                         foreach (XmlNode n in el.ChildNodes)\r
556                                 if (n.NodeType == XmlNodeType.Element)\r
557                                         return false;\r
558                         return true;\r
559                 }\r
560         }\r
561 }\r
562 \r
563 \r
564 #if TEST_STANDALONE_INFERENCE\r
565 internal class Driver\r
566 {\r
567         private static void DumpDataTable (DataTable dt)\r
568         {\r
569                 Console.WriteLine ("<Table>");\r
570                 Console.WriteLine (dt.TableName);\r
571                 Console.WriteLine ("ChildRelationCount: " + dt.ChildRelations.Count);\r
572                 Console.WriteLine ("ConstraintsCount: " + dt.Constraints.Count);\r
573                 Console.WriteLine ("ParentRelationCount: " + dt.ParentRelations.Count);\r
574                 Console.WriteLine ("Prefix: " + dt.Prefix);\r
575                 Console.WriteLine ("Namespace: " + dt.Namespace);\r
576                 Console.WriteLine ("Site: " + dt.Site);\r
577                 Console.WriteLine ("RowCount: " + dt.Rows.Count);\r
578                 Console.WriteLine ("<Columns count='" + dt.Columns.Count + "'>");\r
579                 foreach (DataColumn col in dt.Columns)\r
580                         DumpDataColumn (col);\r
581                 Console.WriteLine ("</Columns>");\r
582                 Console.WriteLine ("</Table>");\r
583         }\r
584 \r
585         private static void DumpDataRelation (DataRelation rel)\r
586         {\r
587                 Console.WriteLine ("<Relation>");\r
588                 Console.WriteLine (rel.RelationName);\r
589                 Console.WriteLine (rel.Nested);\r
590                 Console.Write ("  <ParentColumns>");\r
591                 foreach (DataColumn col in rel.ParentColumns)\r
592                         Console.Write (col.ColumnName + " ");\r
593                 Console.WriteLine ("</ParentColumns>");\r
594                 Console.Write ("  <ChildColumns>");\r
595                 foreach (DataColumn col in rel.ChildColumns)\r
596                         Console.Write (col.ColumnName + " ");\r
597                 Console.WriteLine ("</ChildColumns>");\r
598                 if (rel.ParentKeyConstraint != null) {\r
599                         Console.WriteLine ("  <ParentKeyConstraint>");\r
600                         DumpUniqueConstraint (rel.ParentKeyConstraint);\r
601                         Console.WriteLine ("  </ParentKeyConstraint>");\r
602                 }\r
603                 if (rel.ChildKeyConstraint != null) {\r
604                         Console.WriteLine ("  <ChildKeyConstraint>");\r
605                         DumpForeignKeyConstraint (rel.ChildKeyConstraint);\r
606                         Console.WriteLine ("  </ChildKeyConstraint>");\r
607                 }\r
608                 Console.WriteLine ("</Relation>");\r
609         }\r
610 \r
611         public static void DumpUniqueConstraint (UniqueConstraint uc)\r
612         {\r
613                 Console.WriteLine ("Name " + uc.ConstraintName);\r
614                 Console.WriteLine ("PK? " + uc.IsPrimaryKey);\r
615                 Console.Write ("  <Columns>");\r
616                 foreach (DataColumn col in uc.Columns)\r
617                         Console.Write (col.ColumnName + " ");\r
618                 Console.WriteLine ("</Columns>");\r
619         }\r
620 \r
621         public static void DumpForeignKeyConstraint (ForeignKeyConstraint fk)\r
622         {\r
623                 Console.WriteLine ("Name " + fk.ConstraintName);\r
624                 Console.WriteLine ("  <Rules>" + fk.AcceptRejectRule + ", " +\r
625                         fk.DeleteRule + ", " + fk.UpdateRule + "</Rules>");\r
626                 Console.Write ("  <Columns>");\r
627                 foreach (DataColumn col in fk.Columns)\r
628                         Console.Write (col.ColumnName + " ");\r
629                 Console.WriteLine ("</Columns>");\r
630                 Console.Write ("  <RelatedColumns>");\r
631                 foreach (DataColumn col in fk.RelatedColumns)\r
632                         Console.Write (col.ColumnName + " ");\r
633                 Console.WriteLine ("</RelatedColumns>");\r
634         }\r
635 \r
636         private static void DumpDataSet (DataSet ds)\r
637         {\r
638                 Console.WriteLine ("-----------------------");\r
639                 Console.WriteLine ("name: " + ds.DataSetName);\r
640                 Console.WriteLine ("ns: " + ds.Namespace);\r
641                 Console.WriteLine ("prefix: " + ds.Prefix);\r
642                 Console.WriteLine ("extprop: " + ds.ExtendedProperties.Count);\r
643                 Console.WriteLine ("<Tables count='" + ds.Tables.Count + "'>");\r
644                 foreach (DataTable dt in ds.Tables)\r
645                         DumpDataTable (dt);\r
646                 Console.WriteLine ("</Tables>");\r
647                 Console.WriteLine ("<Relations count='" + ds.Relations.Count + "'>");\r
648                 foreach (DataRelation rel in ds.Relations)\r
649                         DumpDataRelation (rel);\r
650                 Console.WriteLine ("</Relations>");\r
651         }\r
652 \r
653         public static void DumpDataColumn (DataColumn col)\r
654         {\r
655                 Console.WriteLine ("<Column>");\r
656                 Console.WriteLine ("  ColumnName: " + col.ColumnName);\r
657                 Console.WriteLine ("  AllowDBNull? " + col.AllowDBNull);\r
658                 Console.WriteLine ("  AutoIncrement? " + col.AutoIncrement);\r
659                 Console.WriteLine ("    Seed: " + col.AutoIncrementSeed);\r
660                 Console.WriteLine ("    Step: " + col.AutoIncrementStep);\r
661                 Console.WriteLine ("  Caption " + col.Caption);\r
662                 Console.WriteLine ("  Mapping: " + col.ColumnMapping);\r
663                 Console.WriteLine ("  Type: " + col.DataType);\r
664                 Console.WriteLine ("  DefaultValue: " + (col.DefaultValue == DBNull.Value ? "(DBNull)" : col.DefaultValue));\r
665                 Console.WriteLine ("  Expression: " + (col.Expression == "" ? "(empty)" : col.Expression));\r
666                 Console.WriteLine ("  MaxLength: " + col.MaxLength);\r
667                 Console.WriteLine ("  Namespace: " + (col.Namespace == null ? "(null)" : col.Namespace));\r
668                 Console.WriteLine ("  Ordinal: " + col.Ordinal);\r
669                 Console.WriteLine ("  Prefix: " + (col.Prefix == null ? "(null)" : col.Prefix));\r
670                 Console.WriteLine ("  ReadOnly: " + col.ReadOnly);\r
671                 Console.WriteLine ("  Unique: " + col.Unique);\r
672                 Console.WriteLine ("</Column>");\r
673         }\r
674 \r
675         public static void Main (string [] args)\r
676         {\r
677                 if (args.Length < 1) {\r
678                         Console.WriteLine ("reader.exe xmlfilename");\r
679                         return;\r
680                 }\r
681                 try {\r
682                         XmlSerializer ser = new XmlSerializer (typeof (DataSet));\r
683 \r
684                         DataSet ds = new DataSet ();\r
685                         XmlTextReader xtr = new XmlTextReader (args [0]);\r
686                         ds.ReadXml (xtr, XmlReadMode.Auto);\r
687 DumpDataSet (ds);\r
688                         TextWriter sw = new StringWriter ();\r
689                         ser.Serialize (sw, ds);\r
690                         using (TextWriter w = new StreamWriter (Path.ChangeExtension (args [0], "ms.txt"), false, Encoding.ASCII)) {\r
691                                 w.WriteLine (sw.ToString ());\r
692                         }\r
693 \r
694                         ds = new DataSet ();\r
695                         xtr = new XmlTextReader (args [0]);\r
696                         XmlDocument doc = new XmlDocument ();\r
697                         doc.Load (xtr);\r
698                         XmlDataInferenceLoader.Infer (ds, doc, XmlReadMode.Auto, null);\r
699 DumpDataSet (ds);\r
700                         sw = new StringWriter ();\r
701 sw = Console.Out;\r
702                         ser.Serialize (sw, ds);\r
703                         using (TextWriter w = new StreamWriter (Path.ChangeExtension (args [0], "my.txt"), false, Encoding.ASCII)) {\r
704                                 w.WriteLine (sw.ToString ());\r
705                         }\r
706 \r
707                 } catch (Exception ex) {\r
708                         Console.WriteLine (ex);\r
709                 }\r
710         }\r
711 }\r
712 \r
713 #endif\r
714 \r
715 //\r
716 // * Design Notes\r
717 //\r
718 //      This class is used to implement DataSet's ReadXml() and \r
719 //      InferXmlSchema() methods. That is, 1) to infer dataset schema \r
720 //      structure and 2) to read data rows.\r
721 //\r
722 //      It is instantiated from DataSet, XmlReader and XmlReadMode.\r
723 //\r
724 //\r
725 // ** General Design\r
726 //\r
727 // *** Read mode\r
728 //\r
729 //      Data rows are not read when XmlReadMode is ReadSchema or InferSchema\r
730 //      (well, actually ReadSchema should not be passed).\r
731 //\r
732 //      Schema inference is done only when XmlReadMode is Auto or InferSchema.\r
733 //\r
734 // *** Information Set\r
735 //\r
736 //      Only elements, "attributes", and "text" are considered. Here "text" \r
737 //      includes text node and CDATA section, but does not include whitespace\r
738 //      and even significant whitespace (as MS does). Also, here "attributes"\r
739 //      does not include "namespace" nodes.\r
740 //\r
741 //\r
742 // ** Inference Design\r
743 //\r
744 // *** MappingType\r
745 //\r
746 //      There are four type of mapping in MappingType enumeration:\r
747 //\r
748 //      Element : Mapped to a simple type element.\r
749 //      Attribute : Mapped to an attribute\r
750 //      SimpleContent : Mapped to the text content of complex type element.\r
751 //                      (i.e. xs:element/xs:complexType/xs:simpleContent)\r
752 //      Hidden : Used for both key and reference, for auto-generated columns.\r
753 //\r
754 // *** Mapping strategy\r
755 //\r
756 //      Attributes are always (except for namespace nodes) infered as \r
757 //      DataColumn (of MappingType.Attribute).\r
758 //\r
759 //      When an element has attributes, it always becomes a DataTable.\r
760 //      Otherwise if there is a child element, it becomes DataTable as well.\r
761 //\r
762 //      When there is a text content, 1) if the container element has \r
763 //      attribute(s), the text content becomes a SimpleContent DataColumn\r
764 //      in the container "table" element (yes, it becomes a DataTable).\r
765 //      2) if the container has no attribute, the element becomes DataColumn.\r
766 //\r
767 //      If there are both text content and child element(s), the text content\r
768 //      is ignored (thus, it always become DataTable).\r
769 //\r
770 // *** Mapping conflicts\r
771 //\r
772 //      If there has been already a different MappingType of DataColumn,\r
773 //      it is DataException. For example, it is an error if there are an\r
774 //      attribute and an element child those names are the same.\r
775 //\r
776 //      Name remapping will never be done. It introduces complicated rules.\r
777 //\r
778 // *** Upgrading from DataColumn to DataTable\r
779 //\r
780 //      If there has been the same Element type of mapping (that is, when\r
781 //      the same-name child elements appeared in the element), then the\r
782 //      child elements become a DataTable (so here must be a conversion\r
783 //      from DataColumn/value_DataRow to DataTable/value_DataRow in the new\r
784 //      table and reference_to_new_table in the old DataColumn.\r
785 //\r
786 //\r
787 // ** Implementation\r
788 //\r
789 // *** XmlReader based implementation\r
790 //\r
791 //      This class uses XmlReader to avoid having the entire XML document\r
792 //      object. The basic stategy is\r
793 //\r
794 //              1) handle attributes at startElement\r
795 //              2) store text content (if it "stores" in data rows) while\r
796 //                 EndElement\r
797 //              3) dispose of elements at endElement\r
798 //              4) Empty element without attributes is equal to a column \r
799 //                 that holds "".\r
800 //\r
801 //      In XmlSchemaMapper.cs (by Ville Palo) there is an enumeration type\r
802 //      ElementType (undefined, table, column). This concept is nice to reuse.\r
803 //\r
804 // *** Top level inference\r
805 //\r
806 //      The process starts with ReadElement() for the top-level element.\r
807 //      (considering Fragment mode, it might not be the document element.\r
808 //      However, no inference is done in that mode.)\r
809 //\r
810 //      If the top level element was not a DataTable and there is\r
811 //      no more content, the element is regarded as DataSet with no tables.\r
812 //\r
813 // *** First child of the DataSet element\r
814 //\r
815 //      There are some special cases.\r
816 //\r
817 // *** ReadElement()\r
818 //\r
819 //      The main inference process is ReadElement(). This method consumes\r
820 //      (should consume) exactly one element and interpret it as either\r
821 //      DataTable or DataColumn.\r
822 //\r