Drop of Mainsoft.System.Data
[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)) {\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 another table's child 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 ElementMappingType GetElementMappingType (\r
460                         XmlElement el, ArrayList ignoredNamespaces)\r
461                 {\r
462                         foreach (XmlAttribute attr in el.Attributes) {\r
463                                 if (attr.NamespaceURI == XmlConstants.XmlnsNS)\r
464                                         continue;\r
465                                 if (ignoredNamespaces != null && ignoredNamespaces.Contains (attr.NamespaceURI))\r
466                                         continue;\r
467                                 return ElementMappingType.Complex;\r
468                         }\r
469                         foreach (XmlNode n in el.ChildNodes)\r
470                                 if (n.NodeType == XmlNodeType.Element)\r
471                                         return ElementMappingType.Complex;\r
472 \r
473                         for (XmlNode n = el.NextSibling; n != null; n = n.NextSibling)\r
474                                 if (n.NodeType == XmlNodeType.Element && n.LocalName == el.LocalName)\r
475                                         return GetElementMappingType (\r
476                                                 n as XmlElement,\r
477                                                 ignoredNamespaces)\r
478                                                 == ElementMappingType.Complex ?\r
479                                                 ElementMappingType.Complex :\r
480                                                 ElementMappingType.Repeated;\r
481 \r
482                         return ElementMappingType.Simple;\r
483                 }\r
484 \r
485                 private bool IsDocumentElementTable ()\r
486                 {\r
487                         return IsDocumentElementTable (\r
488                                 document.DocumentElement,\r
489                                 ignoredNamespaces);\r
490                 }\r
491 \r
492                 internal static bool IsDocumentElementTable (XmlElement top,\r
493                         ArrayList ignoredNamespaces)\r
494                 {\r
495                         foreach (XmlAttribute attr in top.Attributes) {\r
496                                 if (attr.NamespaceURI == XmlConstants.XmlnsNS)\r
497                                         continue;\r
498                                 if (ignoredNamespaces != null &&\r
499                                         ignoredNamespaces.Contains (attr.NamespaceURI))\r
500                                         continue;\r
501                                 // document element has attributes other than xmlns\r
502                                 return true;\r
503                         }\r
504                         foreach (XmlNode n in top.ChildNodes) {\r
505                                 XmlElement el = n as XmlElement;\r
506                                 if (el == null)\r
507                                         continue;\r
508                                 if (GetElementMappingType (el, ignoredNamespaces)\r
509                                         == ElementMappingType.Simple)\r
510                                         return true;\r
511                         }\r
512                         return false;\r
513                 }\r
514 \r
515                 // Returns if it "might" be a column element (this method is\r
516                 // called per child element, thus it might still consist of\r
517                 // table, since it might be repeated).\r
518                 private bool IsPossibleColumnElement (XmlElement el)\r
519                 {\r
520                         foreach (XmlAttribute attr in el.Attributes) {\r
521                                 if (attr.NamespaceURI == XmlConstants.XmlnsNS)\r
522                                         continue;\r
523                                 return false;\r
524                         }\r
525                         foreach (XmlNode n in el.ChildNodes)\r
526                                 if (n.NodeType == XmlNodeType.Element)\r
527                                         return false;\r
528                         return true;\r
529                 }\r
530         }\r
531 }\r
532 \r
533 \r
534 #if TEST_STANDALONE_INFERENCE\r
535 internal class Driver\r
536 {\r
537         private static void DumpDataTable (DataTable dt)\r
538         {\r
539                 Console.WriteLine ("<Table>");\r
540                 Console.WriteLine (dt.TableName);\r
541                 Console.WriteLine ("ChildRelationCount: " + dt.ChildRelations.Count);\r
542                 Console.WriteLine ("ConstraintsCount: " + dt.Constraints.Count);\r
543                 Console.WriteLine ("ParentRelationCount: " + dt.ParentRelations.Count);\r
544                 Console.WriteLine ("Prefix: " + dt.Prefix);\r
545                 Console.WriteLine ("Namespace: " + dt.Namespace);\r
546                 Console.WriteLine ("Site: " + dt.Site);\r
547                 Console.WriteLine ("RowCount: " + dt.Rows.Count);\r
548                 Console.WriteLine ("<Columns count='" + dt.Columns.Count + "'>");\r
549                 foreach (DataColumn col in dt.Columns)\r
550                         DumpDataColumn (col);\r
551                 Console.WriteLine ("</Columns>");\r
552                 Console.WriteLine ("</Table>");\r
553         }\r
554 \r
555         private static void DumpDataRelation (DataRelation rel)\r
556         {\r
557                 Console.WriteLine ("<Relation>");\r
558                 Console.WriteLine (rel.RelationName);\r
559                 Console.WriteLine (rel.Nested);\r
560                 Console.Write ("  <ParentColumns>");\r
561                 foreach (DataColumn col in rel.ParentColumns)\r
562                         Console.Write (col.ColumnName + " ");\r
563                 Console.WriteLine ("</ParentColumns>");\r
564                 Console.Write ("  <ChildColumns>");\r
565                 foreach (DataColumn col in rel.ChildColumns)\r
566                         Console.Write (col.ColumnName + " ");\r
567                 Console.WriteLine ("</ChildColumns>");\r
568                 if (rel.ParentKeyConstraint != null) {\r
569                         Console.WriteLine ("  <ParentKeyConstraint>");\r
570                         DumpUniqueConstraint (rel.ParentKeyConstraint);\r
571                         Console.WriteLine ("  </ParentKeyConstraint>");\r
572                 }\r
573                 if (rel.ChildKeyConstraint != null) {\r
574                         Console.WriteLine ("  <ChildKeyConstraint>");\r
575                         DumpForeignKeyConstraint (rel.ChildKeyConstraint);\r
576                         Console.WriteLine ("  </ChildKeyConstraint>");\r
577                 }\r
578                 Console.WriteLine ("</Relation>");\r
579         }\r
580 \r
581         public static void DumpUniqueConstraint (UniqueConstraint uc)\r
582         {\r
583                 Console.WriteLine ("Name " + uc.ConstraintName);\r
584                 Console.WriteLine ("PK? " + uc.IsPrimaryKey);\r
585                 Console.Write ("  <Columns>");\r
586                 foreach (DataColumn col in uc.Columns)\r
587                         Console.Write (col.ColumnName + " ");\r
588                 Console.WriteLine ("</Columns>");\r
589         }\r
590 \r
591         public static void DumpForeignKeyConstraint (ForeignKeyConstraint fk)\r
592         {\r
593                 Console.WriteLine ("Name " + fk.ConstraintName);\r
594                 Console.WriteLine ("  <Rules>" + fk.AcceptRejectRule + ", " +\r
595                         fk.DeleteRule + ", " + fk.UpdateRule + "</Rules>");\r
596                 Console.Write ("  <Columns>");\r
597                 foreach (DataColumn col in fk.Columns)\r
598                         Console.Write (col.ColumnName + " ");\r
599                 Console.WriteLine ("</Columns>");\r
600                 Console.Write ("  <RelatedColumns>");\r
601                 foreach (DataColumn col in fk.RelatedColumns)\r
602                         Console.Write (col.ColumnName + " ");\r
603                 Console.WriteLine ("</RelatedColumns>");\r
604         }\r
605 \r
606         private static void DumpDataSet (DataSet ds)\r
607         {\r
608                 Console.WriteLine ("-----------------------");\r
609                 Console.WriteLine ("name: " + ds.DataSetName);\r
610                 Console.WriteLine ("ns: " + ds.Namespace);\r
611                 Console.WriteLine ("prefix: " + ds.Prefix);\r
612                 Console.WriteLine ("extprop: " + ds.ExtendedProperties.Count);\r
613                 Console.WriteLine ("<Tables count='" + ds.Tables.Count + "'>");\r
614                 foreach (DataTable dt in ds.Tables)\r
615                         DumpDataTable (dt);\r
616                 Console.WriteLine ("</Tables>");\r
617                 Console.WriteLine ("<Relations count='" + ds.Relations.Count + "'>");\r
618                 foreach (DataRelation rel in ds.Relations)\r
619                         DumpDataRelation (rel);\r
620                 Console.WriteLine ("</Relations>");\r
621         }\r
622 \r
623         public static void DumpDataColumn (DataColumn col)\r
624         {\r
625                 Console.WriteLine ("<Column>");\r
626                 Console.WriteLine ("  ColumnName: " + col.ColumnName);\r
627                 Console.WriteLine ("  AllowDBNull? " + col.AllowDBNull);\r
628                 Console.WriteLine ("  AutoIncrement? " + col.AutoIncrement);\r
629                 Console.WriteLine ("    Seed: " + col.AutoIncrementSeed);\r
630                 Console.WriteLine ("    Step: " + col.AutoIncrementStep);\r
631                 Console.WriteLine ("  Caption " + col.Caption);\r
632                 Console.WriteLine ("  Mapping: " + col.ColumnMapping);\r
633                 Console.WriteLine ("  Type: " + col.DataType);\r
634                 Console.WriteLine ("  DefaultValue: " + (col.DefaultValue == DBNull.Value ? "(DBNull)" : col.DefaultValue));\r
635                 Console.WriteLine ("  Expression: " + (col.Expression == "" ? "(empty)" : col.Expression));\r
636                 Console.WriteLine ("  MaxLength: " + col.MaxLength);\r
637                 Console.WriteLine ("  Namespace: " + (col.Namespace == null ? "(null)" : col.Namespace));\r
638                 Console.WriteLine ("  Ordinal: " + col.Ordinal);\r
639                 Console.WriteLine ("  Prefix: " + (col.Prefix == null ? "(null)" : col.Prefix));\r
640                 Console.WriteLine ("  ReadOnly: " + col.ReadOnly);\r
641                 Console.WriteLine ("  Unique: " + col.Unique);\r
642                 Console.WriteLine ("</Column>");\r
643         }\r
644 \r
645         public static void Main (string [] args)\r
646         {\r
647                 if (args.Length < 1) {\r
648                         Console.WriteLine ("reader.exe xmlfilename");\r
649                         return;\r
650                 }\r
651                 try {\r
652                         XmlSerializer ser = new XmlSerializer (typeof (DataSet));\r
653 \r
654                         DataSet ds = new DataSet ();\r
655                         XmlTextReader xtr = new XmlTextReader (args [0]);\r
656                         ds.ReadXml (xtr, XmlReadMode.Auto);\r
657 DumpDataSet (ds);\r
658                         TextWriter sw = new StringWriter ();\r
659                         ser.Serialize (sw, ds);\r
660                         using (TextWriter w = new StreamWriter (Path.ChangeExtension (args [0], "ms.txt"), false, Encoding.ASCII)) {\r
661                                 w.WriteLine (sw.ToString ());\r
662                         }\r
663 \r
664                         ds = new DataSet ();\r
665                         xtr = new XmlTextReader (args [0]);\r
666                         XmlDocument doc = new XmlDocument ();\r
667                         doc.Load (xtr);\r
668                         XmlDataInferenceLoader.Infer (ds, doc, XmlReadMode.Auto, null);\r
669 DumpDataSet (ds);\r
670                         sw = new StringWriter ();\r
671 sw = Console.Out;\r
672                         ser.Serialize (sw, ds);\r
673                         using (TextWriter w = new StreamWriter (Path.ChangeExtension (args [0], "my.txt"), false, Encoding.ASCII)) {\r
674                                 w.WriteLine (sw.ToString ());\r
675                         }\r
676 \r
677                 } catch (Exception ex) {\r
678                         Console.WriteLine (ex);\r
679                 }\r
680         }\r
681 }\r
682 \r
683 #endif\r
684 \r
685 //\r
686 // * Design Notes\r
687 //\r
688 //      This class is used to implement DataSet's ReadXml() and \r
689 //      InferXmlSchema() methods. That is, 1) to infer dataset schema \r
690 //      structure and 2) to read data rows.\r
691 //\r
692 //      It is instantiated from DataSet, XmlReader and XmlReadMode.\r
693 //\r
694 //\r
695 // ** General Design\r
696 //\r
697 // *** Read mode\r
698 //\r
699 //      Data rows are not read when XmlReadMode is ReadSchema or InferSchema\r
700 //      (well, actually ReadSchema should not be passed).\r
701 //\r
702 //      Schema inference is done only when XmlReadMode is Auto or InferSchema.\r
703 //\r
704 // *** Information Set\r
705 //\r
706 //      Only elements, "attributes", and "text" are considered. Here "text" \r
707 //      includes text node and CDATA section, but does not include whitespace\r
708 //      and even significant whitespace (as MS does). Also, here "attributes"\r
709 //      does not include "namespace" nodes.\r
710 //\r
711 //\r
712 // ** Inference Design\r
713 //\r
714 // *** MappingType\r
715 //\r
716 //      There are four type of mapping in MappingType enumeration:\r
717 //\r
718 //      Element : Mapped to a simple type element.\r
719 //      Attribute : Mapped to an attribute\r
720 //      SimpleContent : Mapped to the text content of complex type element.\r
721 //                      (i.e. xs:element/xs:complexType/xs:simpleContent)\r
722 //      Hidden : Used for both key and reference, for auto-generated columns.\r
723 //\r
724 // *** Mapping strategy\r
725 //\r
726 //      Attributes are always (except for namespace nodes) infered as \r
727 //      DataColumn (of MappingType.Attribute).\r
728 //\r
729 //      When an element has attributes, it always becomes a DataTable.\r
730 //      Otherwise if there is a child element, it becomes DataTable as well.\r
731 //\r
732 //      When there is a text content, 1) if the container element has \r
733 //      attribute(s), the text content becomes a SimpleContent DataColumn\r
734 //      in the container "table" element (yes, it becomes a DataTable).\r
735 //      2) if the container has no attribute, the element becomes DataColumn.\r
736 //\r
737 //      If there are both text content and child element(s), the text content\r
738 //      is ignored (thus, it always become DataTable).\r
739 //\r
740 // *** Mapping conflicts\r
741 //\r
742 //      If there has been already a different MappingType of DataColumn,\r
743 //      it is DataException. For example, it is an error if there are an\r
744 //      attribute and an element child those names are the same.\r
745 //\r
746 //      Name remapping will never be done. It introduces complicated rules.\r
747 //\r
748 // *** Upgrading from DataColumn to DataTable\r
749 //\r
750 //      If there has been the same Element type of mapping (that is, when\r
751 //      the same-name child elements appeared in the element), then the\r
752 //      child elements become a DataTable (so here must be a conversion\r
753 //      from DataColumn/value_DataRow to DataTable/value_DataRow in the new\r
754 //      table and reference_to_new_table in the old DataColumn.\r
755 //\r
756 //\r
757 // ** Implementation\r
758 //\r
759 // *** XmlReader based implementation\r
760 //\r
761 //      This class uses XmlReader to avoid having the entire XML document\r
762 //      object. The basic stategy is\r
763 //\r
764 //              1) handle attributes at startElement\r
765 //              2) store text content (if it "stores" in data rows) while\r
766 //                 EndElement\r
767 //              3) dispose of elements at endElement\r
768 //              4) Empty element without attributes is equal to a column \r
769 //                 that holds "".\r
770 //\r
771 //      In XmlSchemaMapper.cs (by Ville Palo) there is an enumeration type\r
772 //      ElementType (undefined, table, column). This concept is nice to reuse.\r
773 //\r
774 // *** Top level inference\r
775 //\r
776 //      The process starts with ReadElement() for the top-level element.\r
777 //      (considering Fragment mode, it might not be the document element.\r
778 //      However, no inference is done in that mode.)\r
779 //\r
780 //      If the top level element was not a DataTable and there is\r
781 //      no more content, the element is regarded as DataSet with no tables.\r
782 //\r
783 // *** First child of the DataSet element\r
784 //\r
785 //      There are some special cases.\r
786 //\r
787 // *** ReadElement()\r
788 //\r
789 //      The main inference process is ReadElement(). This method consumes\r
790 //      (should consume) exactly one element and interpret it as either\r
791 //      DataTable or DataColumn.\r
792 //\r