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