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