2004-06-06 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.Data / System.Xml / XmlDataDocument.cs
1 //
2 // mcs/class/System.Data/System.Xml/XmlDataDocument.cs
3 //
4 // Purpose: Provides a W3C XML DOM Document to interact with
5 //          relational data in a DataSet
6 //
7 // class: XmlDataDocument
8 // assembly: System.Data.dll
9 // namespace: System.Xml
10 //
11 // Author:
12 //     Daniel Morgan <danmorg@sc.rr.com>
13 //     Ville Palo <vi64pa@koti.soon.fi>
14 //     Atsushi Enomoto <atsushi@ximian.com>
15 //
16 // (c)copyright 2002 Daniel Morgan
17 // (c)copyright 2003 Ville Palo
18 // (c)2004 Novell Inc.
19 //
20 // XmlDataDocument is included within the Mono Class Library.
21 //
22
23 using System;
24 using System.Data;
25 using System.IO;
26 using System.Text;
27 using System.Xml.XPath;
28 using System.Collections;
29 using System.Globalization;
30 using System.ComponentModel;
31
32 namespace System.Xml 
33 {
34
35         public class XmlDataDocument : XmlDocument 
36         {
37                 // Should we consider overriding CloneNode() ? By default
38                 // base CloneNode() will be invoked and thus no DataRow conflict
39                 // would happen, that sounds the best (that means, no mapped
40                 // DataRow will be provided).
41                 internal class XmlDataElement : XmlElement
42                 {
43                         DataRow row;
44
45                         internal XmlDataElement (DataRow row, string prefix, string localName, string ns, XmlDataDocument doc)
46                                 : base (prefix, localName, ns, doc)
47                         {
48                                 this.row = row;
49                                 // Embed row ID only when the element is mapped to
50                                 // certain DataRow.
51                                 if (row != null) {
52                                         row.DataElement = this;
53                                         row.XmlRowID = doc.dataRowID;
54                                         doc.dataRowIDList.Add (row.XmlRowID);
55                                         // It should not be done here. The node is detached
56                                         // dt.Rows.Add (tempRow);
57                                         doc.dataRowID++;
58                                 }
59                         }
60
61                         internal DataRow DataRow {
62                                 get { return row; }
63                         }
64                 }
65
66                 #region Fields
67
68                 private DataSet dataSet;
69
70                 private int dataRowID = 1;
71                 private ArrayList dataRowIDList = new ArrayList ();
72
73                 // this keeps whether table change events should be handles
74                 private bool raiseDataSetEvents = true;
75                 private bool raiseDocumentEvents = true;
76
77                 // this is needed for inserting new row to datatable via xml
78                 private Hashtable TempTable = new Hashtable ();
79
80                 DataColumnChangeEventHandler columnChanged;
81                 DataRowChangeEventHandler rowDeleted;
82                 DataRowChangeEventHandler rowChanged;
83                 CollectionChangeEventHandler tablesChanged;
84                 #endregion // Fields
85
86                 #region Constructors
87
88                 public XmlDataDocument ()
89                 {
90                         InitDelegateFields ();
91
92                         dataSet = new DataSet();
93                         dataSet._xmlDataDocument = this;
94                         dataSet.Tables.CollectionChanged += tablesChanged;
95
96                         AddXmlDocumentListeners ();
97                         DataSet.EnforceConstraints = false;
98                 }
99
100                 public XmlDataDocument (DataSet dataset) 
101                 {
102                         if (dataset == null)
103                                 throw new ArgumentException ("Parameter dataset cannot be null.");\r
104                         if (dataset._xmlDataDocument != null)\r
105                                 throw new ArgumentException ("DataSet cannot be associated with two or more XmlDataDocument.");\r
106
107                         InitDelegateFields ();
108
109                         this.dataSet = dataset;
110                         this.dataSet._xmlDataDocument = this;
111
112                         XmlElement docElem = CreateElement (dataSet.Prefix, dataSet.DataSetName, dataSet.Namespace);
113                         foreach (DataTable dt in dataSet.Tables) {
114                                 if (dt.ParentRelations.Count > 0)
115                                         continue; // don't add them here
116                                 FillNodeRows (docElem, dt, dt.Rows);
117                         }
118
119                         // This seems required to avoid Load() error when for
120                         // example empty DataSet will be filled on Load().
121                         if (docElem.ChildNodes.Count > 0)
122                                 AppendChild (docElem);
123
124                         foreach (DataTable dt in dataSet.Tables) {
125                                 dt.ColumnChanged += columnChanged;
126                                 dt.RowDeleted += rowDeleted;
127                                 dt.RowChanged += rowChanged;
128                         }
129
130                         AddXmlDocumentListeners ();
131                 }
132
133                 // bool clone. If we are cloning XmlDataDocument then clone should be true.
134                 // FIXME: shouldn't DataSet be mapped to at most one document??
135                 private XmlDataDocument (DataSet dataset, bool clone)
136                 {
137                         InitDelegateFields ();
138
139                         this.dataSet = dataset;
140                         this.dataSet._xmlDataDocument = this;
141
142                         foreach (DataTable Table in DataSet.Tables) {
143                                 
144                                 foreach (DataRow Row in Table.Rows) {
145                                         Row.XmlRowID = dataRowID;
146                                         dataRowIDList.Add (dataRowID);
147                                         dataRowID++;
148                                 }
149                         }
150
151                         AddXmlDocumentListeners ();
152
153                         foreach (DataTable Table in dataSet.Tables) {
154                                 Table.ColumnChanged += columnChanged;
155                                 Table.RowDeleted += rowDeleted;
156                                 Table.RowChanged += rowChanged;
157                         }
158                 }
159
160                 #endregion // Constructors
161
162                 #region Public Properties
163
164                 public DataSet DataSet {
165                         get {
166                                 return dataSet;
167                         }
168                 }
169
170                 #endregion // Public Properties
171
172                 #region Public Methods
173
174                 private void FillNodeRows (XmlElement parent, DataTable dt, ICollection rows)
175                 {
176                         foreach (DataRow dr in dt.Rows) {
177                                 XmlDataElement el = new XmlDataElement (dr, dt.Prefix, dt.TableName, dt.Namespace, this);
178                                 for (int i = 0; i < dt.Columns.Count; i++) {
179                                         DataColumn col = dt.Columns [i];
180                                         string value = dr.IsNull (col) ? String.Empty : dr [col].ToString ();
181                                         switch (col.ColumnMapping) {
182                                         case MappingType.Element:
183                                                 XmlElement cel = CreateElement (col.Prefix, col.ColumnName, col.Namespace);
184                                                 cel.InnerText = value;
185                                                 el.AppendChild (cel);
186                                                 break;
187                                         case MappingType.Attribute:
188                                                 XmlAttribute a = CreateAttribute (col.Prefix, col.ColumnName, col.Namespace);
189                                                 a.Value = value;
190                                                 el.SetAttributeNode (a);
191                                                 break;
192                                         case MappingType.SimpleContent:
193                                                 XmlText t = CreateTextNode (value);
194                                                 el.AppendChild (t);
195                                                 break;
196                                         }
197                                 }
198                                 foreach (DataRelation rel in dt.ChildRelations)
199                                         FillNodeRows (el, rel.ChildTable, dr.GetChildRows (rel));
200                                 parent.AppendChild (el);
201                         }
202                 }
203
204                 public override XmlNode CloneNode (bool deep) 
205                 {
206                         XmlDataDocument Document;
207                         if (deep)
208                                 Document = new XmlDataDocument (DataSet.Copy (), true);
209                         else
210                                 Document = new XmlDataDocument (DataSet.Clone (), true);
211
212                         Document.RemoveXmlDocumentListeners ();
213
214                         Document.PreserveWhitespace = PreserveWhitespace;
215                         if (deep) {
216                                 foreach(XmlNode n in ChildNodes)
217                                         Document.AppendChild (Document.ImportNode (n, deep));
218                         }
219
220                         Document.AddXmlDocumentListeners ();
221
222                         return Document;                        
223                 }
224
225                 #region overloaded CreateElement methods
226
227                 public override XmlElement CreateElement(
228                         string prefix, string localName, string namespaceURI) 
229                 {
230                         DataTable dt = DataSet.Tables [localName];
231                         DataRow row = dt != null ? dt.NewRow () : null;
232                         if (row != null)
233                                 return GetElementFromRow (row);
234                         else
235                                 return base.CreateElement (prefix, localName, namespaceURI);
236                 }
237
238                 #endregion // overloaded CreateElement Methods
239                         
240                 // It is not supported in XmlDataDocument
241                 public override XmlEntityReference CreateEntityReference(string name) 
242                 {
243                         throw new NotSupportedException ();
244                 }
245                 
246                 // It is not supported in XmlDataDocument
247                 public override XmlElement GetElementById (string elemId) 
248                 {
249                         throw new NotSupportedException ();
250                 }
251
252                 // get the XmlElement associated with the DataRow
253                 public XmlElement GetElementFromRow (DataRow r) 
254                 {
255                         return r.DataElement;
256                 }
257
258                 // get the DataRow associated with the XmlElement
259                 public DataRow GetRowFromElement (XmlElement e)
260                 {
261                         XmlDataElement el = e as XmlDataElement;
262                         if (el == null)
263                                 return null;
264                         return el.DataRow;
265                 }
266
267                 #region overload Load methods
268
269                 public override void Load(Stream inStream) {
270                         Load (new XmlTextReader (inStream));
271                 }
272
273                 public override void Load(string filename) {
274                         Load (new XmlTextReader (filename));
275                 }
276
277                 public override void Load(TextReader txtReader) {
278                         Load (new XmlTextReader (txtReader));
279                 }
280
281                 public override void Load (XmlReader reader) 
282                 {
283                         if (DocumentElement != null)
284                                 throw new InvalidOperationException ("XmlDataDocument does not support multi-time loading. New XmlDadaDocument is always required.");
285
286                         bool OldEC = DataSet.EnforceConstraints;
287                         DataSet.EnforceConstraints = false;
288                         dataSet.Tables.CollectionChanged -= tablesChanged;
289
290                         base.Load (reader);
291
292                         DataSet.EnforceConstraints = OldEC;
293                         dataSet.Tables.CollectionChanged += tablesChanged;
294                 }
295                 
296                 #endregion // overloaded Load methods
297                 #endregion // Public Methods
298
299                 #region Protected Methods
300
301                 [MonoTODO ("Create optimized XPathNavigator")]
302                 protected override XPathNavigator CreateNavigator(XmlNode node) {
303                         return base.CreateNavigator (node);
304                 }
305
306                 #endregion // Protected Methods
307                 
308                 #region XmlDocument event handlers
309
310                 private void OnNodeChanging (object sender, XmlNodeChangedEventArgs args)
311                 {
312                         if (!this.raiseDocumentEvents)
313                                 return;
314                         if (DataSet.EnforceConstraints) 
315                                 throw new InvalidOperationException (Locale.GetText ("Please set DataSet.EnforceConstraints == false before trying to edit XmlDataDocument using XML operations."));
316                 }
317
318                 // Invoked when XmlNode is changed colum is changed
319                 private void OnNodeChanged (object sender, XmlNodeChangedEventArgs args)
320                 {
321                         if (!raiseDocumentEvents)
322                                 return;
323                         bool escapedRaiseDataSetEvents = raiseDataSetEvents;
324                         raiseDataSetEvents = false;
325                         try {
326
327                                 if (args.Node == null)
328                                         return;
329
330                                 DataRow row = GetRowFromElement ((XmlElement)args.Node.ParentNode.ParentNode);
331
332                                 if (row == null)
333                                         return;
334
335                                 if (!row.Table.Columns.Contains (args.Node.ParentNode.Name))
336                                         return;
337
338                                 if (row [args.Node.ParentNode.Name].ToString () != args.Node.InnerText) {
339                                         DataColumn col = row.Table.Columns [args.Node.ParentNode.Name];
340                                         row [col] = StringToObject (col.DataType, args.Node.InnerText);
341                                 }
342
343                         } finally {
344                                 raiseDataSetEvents = escapedRaiseDataSetEvents;
345                         }
346                 }
347
348                 private void OnNodeRemoving (object sender, XmlNodeChangedEventArgs args) 
349                 {
350                         if (!this.raiseDocumentEvents)
351                                 return;
352                         if (DataSet.EnforceConstraints) 
353                                 throw new InvalidOperationException (Locale.GetText ("Please set DataSet.EnforceConstraints == false before trying to edit XmlDataDocument using XML operations."));
354                         
355                 }
356                 
357                 // Invoked when XmlNode is removed
358                 private void OnNodeRemoved (object sender, XmlNodeChangedEventArgs args)
359                 {
360                         if (!raiseDocumentEvents)
361                                 return;
362                         bool escapedRaiseDataSetEvents = raiseDataSetEvents;
363                         raiseDataSetEvents = false;
364
365                         try {
366                                 if (args.OldParent == null)
367                                         return;
368
369                                 XmlElement oldParentElem = args.OldParent as XmlElement;
370                                 if (oldParentElem == null)
371                                         return;
372                                 
373                                 // detach child row (if exists)
374                                 XmlElement childElem = args.Node as XmlElement;
375                                 if (childElem != null) {
376                                         DataRow childRow = GetRowFromElement (childElem);
377                                         if (childRow != null)
378                                                 childRow.Table.Rows.Remove (childRow);
379                                 }
380
381                                 DataRow row = GetRowFromElement (oldParentElem);
382                                 
383                                 if (row == null)
384                                         return ;
385
386                                 row [args.Node.Name] = null;
387
388                         } finally {
389                                 raiseDataSetEvents = escapedRaiseDataSetEvents;
390                         }
391                 }
392
393                 private void OnNodeInserting (object sender, XmlNodeChangedEventArgs args) 
394                 {
395                         if (!this.raiseDocumentEvents)
396                                 return;
397                         if (DataSet.EnforceConstraints) 
398                                 throw new InvalidOperationException (Locale.GetText ("Please set DataSet.EnforceConstraints == false before trying to edit XmlDataDocument using XML operations."));
399                         
400                 }
401                 
402                 private void OnNodeInserted (object sender, XmlNodeChangedEventArgs args)
403                 {
404                         if (!raiseDocumentEvents)
405                                 return;
406                         bool escapedRaiseDataSetEvents = raiseDataSetEvents;
407                         raiseDataSetEvents = false;
408
409                         // If the parent node is mapped to a DataTable, then
410                         // add a DataRow and map the parent element to it.
411                         //
412                         // AND If the child node is mapped to a DataTable, then
413                         // 1. if it is mapped to a DataTable and relation, add
414                         // a new DataRow and map the child element to it.
415                         // 2. if it is mapped to a DataColumn, set the column
416                         // value of the parent DataRow as the child
417
418                         try {
419                                 if (! (args.NewParent is XmlElement)) {
420                                         // i.e. adding document element
421                                         foreach (XmlNode table in args.Node.ChildNodes)
422                                                 CheckDescendantRelationship (table);
423                                         return;
424                                 }
425
426                                 DataRow row = GetRowFromElement (args.NewParent as XmlElement);
427                                 if (row == null) {
428                                         // That happens only when adding table to existing DocumentElement (aka DataSet element)
429                                         if (args.NewParent == DocumentElement)
430                                                 CheckDescendantRelationship (args.Node);
431                                         return;
432                                 }
433
434                                 XmlAttribute attr = args.Node as XmlAttribute;
435                                 if (attr != null) { // fill attribute value
436                                         DataColumn col = row.Table.Columns [attr.LocalName];
437                                         if (col != null)
438                                                 row [col] = StringToObject (col.DataType, args.Node.Value);
439                                 } else {
440                                         DataRow childRow = GetRowFromElement (args.Node as XmlElement);
441                                         if (childRow != null) {
442                                                 // child might be a table row.
443                                                 // I might be impossible to set parent
444                                                 // since either of them might be detached
445                                                 if (childRow.RowState != DataRowState.Detached && row.RowState != DataRowState.Detached) {
446                                                         FillRelationship (row, childRow, args.NewParent, args.Node);
447                                                 }
448                                         } else if (args.Node.NodeType == XmlNodeType.Element) {
449                                                 // child element might be a column
450                                                 DataColumn col = row.Table.Columns [args.Node.LocalName];
451                                                 if (col != null)
452                                                         row [col] = StringToObject (col.DataType, args.Node.InnerText);
453                                         } else if (args.Node is XmlCharacterData) {
454                                                 if (args.Node.NodeType != XmlNodeType.Comment) {
455                                                         for (int i = 0; i < row.Table.Columns.Count; i++) {
456                                                                 DataColumn col = row.Table.Columns [i];
457                                                                 if (col.ColumnMapping == MappingType.SimpleContent)
458                                                                         row [col] = StringToObject (col.DataType, args.Node.Value);
459                                                         }
460                                                 }
461                                         }
462                                 }
463                         } finally {
464                                 raiseDataSetEvents = escapedRaiseDataSetEvents;
465                         }
466                 }
467
468                 private void CheckDescendantRelationship (XmlNode n)
469                 {
470                         XmlElement el = n as XmlElement;
471                         DataRow row = GetRowFromElement (el);
472                         if (row == null)
473                                 return;
474                         row.Table.Rows.Add (row); // attach
475                         CheckDescendantRelationship (n, row);
476                 }
477
478                 private void CheckDescendantRelationship (XmlNode p, DataRow row)
479                 {
480                         foreach (XmlNode n in p.ChildNodes) {
481                                 XmlElement el = n as XmlElement;
482                                 if (el == null)
483                                         continue;
484                                 DataRow childRow = GetRowFromElement (el);
485                                 if (childRow == null)
486                                         continue;
487                                 childRow.Table.Rows.Add (childRow);
488                                 FillRelationship (row, childRow, p, el);
489                         }
490                 }
491
492                 private void FillRelationship (DataRow row, DataRow childRow, XmlNode parentNode, XmlNode childNode)
493                 {
494                         for (int i = 0; i < childRow.Table.ParentRelations.Count; i++) {
495                                 DataRelation rel = childRow.Table.ParentRelations [i];
496                                 if (rel.ParentTable == row.Table) {
497                                         childRow.SetParentRow (row);
498                                         break;
499                                 }
500                         }
501                         CheckDescendantRelationship (childNode, childRow);
502                 }
503                 #endregion // DataSet event handlers
504
505                 #region DataSet event handlers
506
507                 // If DataTable is added or removed from DataSet
508                 private void OnDataTableChanged (object sender, CollectionChangeEventArgs eventArgs)
509                 {
510                         if (!raiseDataSetEvents)
511                                 return;
512                         bool escapedRaiseDocumentEvents = raiseDocumentEvents;
513                         raiseDocumentEvents = false;
514
515                         try {
516                                 DataTable Table = (DataTable)eventArgs.Element;
517                                 switch (eventArgs.Action) {
518                                 case CollectionChangeAction.Add:
519                                         Table.ColumnChanged += columnChanged;
520                                         Table.RowDeleted += rowDeleted;
521                                         Table.RowChanged += rowChanged;
522                                         break;
523                                 case CollectionChangeAction.Remove:
524                                         Table.ColumnChanged -= columnChanged;
525                                         Table.RowDeleted -= rowDeleted;
526                                         Table.RowChanged -= rowChanged;
527                                         break;
528                                 }
529                         } finally {
530                                 raiseDocumentEvents = escapedRaiseDocumentEvents;
531                         }
532                 }
533
534                 // If column has changed 
535                 private void OnDataTableColumnChanged (object sender, 
536                                                              DataColumnChangeEventArgs eventArgs)
537                 {
538                         if (!raiseDataSetEvents)
539                                 return;
540                         bool escapedRaiseDocumentEvents = raiseDocumentEvents;
541                         raiseDocumentEvents = false;
542
543                         try {
544                                 DataRow row = eventArgs.Row;
545                                 XmlElement el = GetElementFromRow (row);
546                                 if (el == null)
547                                         return;
548                                 DataColumn col = eventArgs.Column;
549                                 string value = row.IsNull (col) ? String.Empty : row [col].ToString ();
550                                 switch (col.ColumnMapping) {
551                                 case MappingType.Attribute:
552                                         el.SetAttribute (col.ColumnName, col.Namespace, value);
553                                         break;
554                                 case MappingType.SimpleContent:
555                                         el.InnerText = value;
556                                         break;
557                                 case MappingType.Element:
558                                         bool exists = false;
559                                         for (int i = 0; i < el.ChildNodes.Count; i++) {
560                                                 XmlElement c = el.ChildNodes [i] as XmlElement;
561                                                 if (c != null && c.LocalName == col.ColumnName && c.NamespaceURI == col.Namespace) {
562                                                         exists = true;
563                                                         c.InnerText = value;
564                                                         break;
565                                                 }
566                                         }
567                                         if (!exists) {
568                                                 XmlElement cel = CreateElement (col.Prefix, col.ColumnName, col.Namespace);
569                                                 cel.InnerText = value;
570                                                 el.AppendChild (cel);
571                                         }
572                                         break;
573                                 // FIXME: how to handle hidden?
574                                 }
575                         } finally {
576                                 raiseDocumentEvents = escapedRaiseDocumentEvents;
577                         }
578                 }
579         
580                 private void OnDataTableRowDeleted (object sender,
581                                                           DataRowChangeEventArgs eventArgs)
582                 {
583                         if (!raiseDataSetEvents)
584                                 return;
585                         bool escapedRaiseDocumentEvents = raiseDocumentEvents;
586                         raiseDocumentEvents = false;
587
588                         try {
589                                 // This code is obsolete XmlDataDocument one
590
591                                 DataRow deletedRow = null;
592                                 deletedRow = eventArgs.Row;
593
594                                 XmlElement el = GetElementFromRow (eventArgs.Row);
595                                 if (el == null)
596                                         return;
597
598                                 el.ParentNode.RemoveChild (el);
599                         } finally {
600                                 raiseDocumentEvents = escapedRaiseDocumentEvents;
601                         }
602                 }
603                 
604                 [MonoTODO ("Need to handle hidden columns? - see comments on each private method")]
605                 private void OnDataTableRowChanged (object sender, DataRowChangeEventArgs eventArgs)
606                 {
607                         if (!raiseDataSetEvents)
608                                 return;
609                         bool escapedRaiseDocumentEvents = raiseDocumentEvents;
610                         raiseDocumentEvents = false;
611                         try {
612
613                                 switch (eventArgs.Action) {
614
615                                 case DataRowAction.Delete:
616                                         OnDataTableRowDeleted (sender, eventArgs);
617                                         break;
618
619                                 case DataRowAction.Add:
620                                         OnDataTableRowAdded (eventArgs);
621                                         break;
622
623                                 case DataRowAction.Rollback:
624                                         OnDataTableRowRollback (eventArgs);
625                                         break;
626                                 default:
627                                         break;
628                                 } 
629                         } finally {
630                                 raiseDocumentEvents = escapedRaiseDocumentEvents;
631                         }
632                 }
633
634                 // Added - see FillNodeChildrenFromRow comment
635                 private void OnDataTableRowAdded (DataRowChangeEventArgs args)
636                 {
637                         if (!raiseDataSetEvents)
638                                 return;
639                         bool escapedRaiseDocumentEvents = raiseDocumentEvents;
640                         raiseDocumentEvents = false;
641
642                         try {
643
644                                 // Create row element. Row's name same as TableName                                     
645                                 DataRow row = args.Row;
646
647                                 // create document element if it does not exist
648                                 if (DocumentElement == null)
649                                         this.AppendChild (CreateElement (DataSet.DataSetName));
650
651                                 DataTable table= args.Row.Table;
652                                 XmlElement element = GetElementFromRow (row);
653                                 if (element == null)
654                                         element = CreateElement (table.Prefix, table.TableName, table.Namespace);
655                                 if (element.ParentNode == null) {
656                                         // parent is not always DocumentElement.
657                                         XmlElement parent = null;
658
659                                         if (table.ParentRelations.Count > 0) {
660                                                 for (int i = 0; i < table.ParentRelations.Count; i++) {
661                                                         DataRelation rel = table.ParentRelations [i];
662                                                         DataRow parentRow = row.GetParentRow (rel);
663                                                         if (parentRow == null)
664                                                                 continue;
665                                                         parent = GetElementFromRow (parentRow);
666                                                 }
667                                         }
668
669                                         // The row might be orphan. In such case, the 
670                                         // element is appended to DocumentElement.
671                                         if (parent == null)
672                                                 parent = DocumentElement;
673                                         parent.AppendChild (element);
674                                 }
675                         } finally {                     
676                                 raiseDocumentEvents = escapedRaiseDocumentEvents;
677                         }
678                 }
679
680                 private void FillNodeChildrenFromRow (DataRow row, XmlElement element)
681                 {
682                         DataTable table = row.Table;
683                         // fill columns for the row
684                         for (int i = 0; i < table.Columns.Count; i++) {
685                                 DataColumn col = table.Columns [i];
686                                 string value = row.IsNull (col) ? String.Empty : row [col].ToString ();
687                                 switch (col.ColumnMapping) {
688                                 case MappingType.Element:
689                                         XmlElement el = CreateElement (col.Prefix, col.ColumnName, col.Namespace);
690                                         el.InnerText = value;
691                                         element.AppendChild (el);
692                                         break;
693                                 case MappingType.Attribute:
694                                         XmlAttribute attr = CreateAttribute (col.Prefix, col.ColumnName, col.Namespace);
695                                         attr.Value = value;
696                                         element.SetAttributeNode (attr);
697                                         break;
698                                 case MappingType.SimpleContent:
699                                         XmlText text = CreateTextNode (value);
700                                         element.AppendChild (text);
701                                         break;
702                                 // FIXME: how to handle hidden?
703                                 }
704                         }
705                 }
706
707                 // Rollback
708                 [MonoTODO ("It does not look complete.")]
709                 private void OnDataTableRowRollback (DataRowChangeEventArgs args)
710                 {
711                         if (!raiseDataSetEvents)
712                                 return;
713                         bool escapedRaiseDocumentEvents = raiseDocumentEvents;
714                         raiseDocumentEvents = false;
715
716                         try {
717                                 DataRow r = args.Row;
718                                 XmlElement el = GetElementFromRow (r);
719                                 if (el == null)
720                                         return;
721                                 DataTable tab = r.Table;
722                                 ArrayList al = new ArrayList ();
723                                 foreach (XmlAttribute attr in el.Attributes) {
724                                         DataColumn col = tab.Columns [attr.LocalName];
725                                         if (col != null) {
726                                                 if (r.IsNull (col))
727                                                         // should be removed
728                                                         al.Add (attr);
729                                                 else
730                                                         attr.Value = r [col].ToString ();
731                                         }
732                                 }
733                                 foreach (XmlAttribute attr in al)
734                                         el.RemoveAttributeNode (attr);
735                                 al.Clear ();
736                                 foreach (XmlNode child in el.ChildNodes) {
737                                         if (child.NodeType == XmlNodeType.Element) {
738                                                 DataColumn col = tab.Columns [child.LocalName];
739                                                 if (col != null) {
740                                                         if (r.IsNull (col))
741                                                                 al.Add (child);
742                                                         else
743                                                                 child.InnerText = r [col].ToString ();
744                                                 }
745                                         }
746                                 }
747                                 foreach (XmlNode n in al)
748                                         el.RemoveChild (n);
749                         } finally {
750                                 raiseDocumentEvents = escapedRaiseDocumentEvents;
751                         }
752                 }
753
754                 #endregion // DataSet event handlers
755
756                 #region Private methods
757                 private void InitDelegateFields ()
758                 {
759                         columnChanged = new DataColumnChangeEventHandler (OnDataTableColumnChanged);
760                         rowDeleted = new DataRowChangeEventHandler (OnDataTableRowDeleted);
761                         rowChanged = new DataRowChangeEventHandler (OnDataTableRowChanged);
762                         tablesChanged = new CollectionChangeEventHandler (OnDataTableChanged);
763                 }
764                 
765                 private void RemoveXmlDocumentListeners ()
766                 {
767                         this.NodeInserting -= new XmlNodeChangedEventHandler (OnNodeInserting);
768                         this.NodeInserted -= new XmlNodeChangedEventHandler (OnNodeInserted);
769                         this.NodeChanging -= new XmlNodeChangedEventHandler (OnNodeChanging);
770                         this.NodeChanged -= new XmlNodeChangedEventHandler (OnNodeChanged);
771                         this.NodeRemoving -= new XmlNodeChangedEventHandler (OnNodeRemoving);
772                         this.NodeRemoved -= new XmlNodeChangedEventHandler (OnNodeRemoved);
773                 }
774
775                 private void AddXmlDocumentListeners ()
776                 {
777                         this.NodeInserting += new XmlNodeChangedEventHandler (OnNodeInserting);
778                         this.NodeInserted += new XmlNodeChangedEventHandler (OnNodeInserted);
779                         this.NodeChanging += new XmlNodeChangedEventHandler (OnNodeChanging);
780                         this.NodeChanged += new XmlNodeChangedEventHandler (OnNodeChanged);
781                         this.NodeRemoving += new XmlNodeChangedEventHandler (OnNodeRemoving);
782                         this.NodeRemoved += new XmlNodeChangedEventHandler (OnNodeRemoved);
783                 }
784
785                 internal static object StringToObject (Type type, string value)
786                 {
787                         if (type == null) return value;
788
789                         switch (Type.GetTypeCode (type)) {
790                                 case TypeCode.Boolean: return XmlConvert.ToBoolean (value);
791                                 case TypeCode.Byte: return XmlConvert.ToByte (value);
792                                 case TypeCode.Char: return (char)XmlConvert.ToInt32 (value);
793                                 case TypeCode.DateTime: return XmlConvert.ToDateTime (value);
794                                 case TypeCode.Decimal: return XmlConvert.ToDecimal (value);
795                                 case TypeCode.Double: return XmlConvert.ToDouble (value);
796                                 case TypeCode.Int16: return XmlConvert.ToInt16 (value);
797                                 case TypeCode.Int32: return XmlConvert.ToInt32 (value);
798                                 case TypeCode.Int64: return XmlConvert.ToInt64 (value);
799                                 case TypeCode.SByte: return XmlConvert.ToSByte (value);
800                                 case TypeCode.Single: return XmlConvert.ToSingle (value);
801                                 case TypeCode.UInt16: return XmlConvert.ToUInt16 (value);
802                                 case TypeCode.UInt32: return XmlConvert.ToUInt32 (value);
803                                 case TypeCode.UInt64: return XmlConvert.ToUInt64 (value);
804                         }
805
806                         if (type == typeof (TimeSpan)) return XmlConvert.ToTimeSpan (value);
807                         if (type == typeof (Guid)) return XmlConvert.ToGuid (value);
808                         if (type == typeof (byte[])) return Convert.FromBase64String (value);
809
810                         return Convert.ChangeType (value, type);
811                 }
812                 #endregion // Private methods
813         }
814 }
815