New tests, and fixed buffer mcs -> gmcs switch.
[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 //
24 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
25 //
26 // Permission is hereby granted, free of charge, to any person obtaining
27 // a copy of this software and associated documentation files (the
28 // "Software"), to deal in the Software without restriction, including
29 // without limitation the rights to use, copy, modify, merge, publish,
30 // distribute, sublicense, and/or sell copies of the Software, and to
31 // permit persons to whom the Software is furnished to do so, subject to
32 // the following conditions:
33 // 
34 // The above copyright notice and this permission notice shall be
35 // included in all copies or substantial portions of the Software.
36 // 
37 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
38 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
39 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
40 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
41 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
42 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
43 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
44 //
45
46 using System;
47 using System.Data;
48 using System.IO;
49 using System.Text;
50 using System.Xml.XPath;
51 using System.Collections;
52 using System.Globalization;
53 using System.ComponentModel;
54
55 namespace System.Xml 
56 {
57
58         public class XmlDataDocument : XmlDocument 
59         {
60                 // Should we consider overriding CloneNode() ? By default
61                 // base CloneNode() will be invoked and thus no DataRow conflict
62                 // would happen, that sounds the best (that means, no mapped
63                 // DataRow will be provided).
64                 internal class XmlDataElement : XmlElement
65                 {
66                         DataRow row;
67
68                         internal XmlDataElement (DataRow row, string prefix, string localName, string ns, XmlDataDocument doc)
69                                 : base (prefix, localName, ns, doc)
70                         {
71                                 this.row = row;
72                                 // Embed row ID only when the element is mapped to
73                                 // certain DataRow.
74                                 if (row != null) {
75                                         row.DataElement = this;
76                                         row.XmlRowID = doc.dataRowID;
77                                         doc.dataRowIDList.Add (row.XmlRowID);
78                                         doc.dataRowID++;
79                                 }
80                         }
81
82                         internal DataRow DataRow {
83                                 get { return row; }
84                         }
85                 }
86
87                 #region Fields
88
89                 private DataSet dataSet;
90
91                 private int dataRowID = 1;
92                 private ArrayList dataRowIDList = new ArrayList ();
93
94                 // this keeps whether table change events should be handles
95                 private bool raiseDataSetEvents = true;
96                 private bool raiseDocumentEvents = true;
97
98                 DataColumnChangeEventHandler columnChanged;
99                 DataRowChangeEventHandler rowDeleted;
100                 DataRowChangeEventHandler rowChanged;
101                 CollectionChangeEventHandler tablesChanged;
102                 #endregion // Fields
103
104                 #region Constructors
105
106                 public XmlDataDocument ()
107                 {
108                         InitDelegateFields ();
109
110                         dataSet = new DataSet();
111                         dataSet._xmlDataDocument = this;
112                         dataSet.Tables.CollectionChanged += tablesChanged;
113
114                         AddXmlDocumentListeners ();
115                         DataSet.EnforceConstraints = false;
116                 }
117
118                 public XmlDataDocument (DataSet dataset) 
119                 {
120                         if (dataset == null)
121                                 throw new ArgumentException ("Parameter dataset cannot be null.");
122                         if (dataset._xmlDataDocument != null)
123                                 throw new ArgumentException ("DataSet cannot be associated with two or more XmlDataDocument.");
124
125                         InitDelegateFields ();
126
127                         this.dataSet = dataset;
128                         this.dataSet._xmlDataDocument = this;
129
130                         XmlElement docElem = CreateElement (dataSet.Prefix, XmlHelper.Encode (dataSet.DataSetName), dataSet.Namespace);
131                         foreach (DataTable dt in dataSet.Tables) {
132                                 if (dt.ParentRelations.Count > 0)
133                                         continue; // don't add them here
134                                 FillNodeRows (docElem, dt, dt.Rows);
135                         }
136
137                         // This seems required to avoid Load() error when for
138                         // example empty DataSet will be filled on Load().
139                         if (docElem.ChildNodes.Count > 0)
140                                 AppendChild (docElem);
141
142                         foreach (DataTable dt in dataSet.Tables) {
143                                 dt.ColumnChanged += columnChanged;
144                                 dt.RowDeleted += rowDeleted;
145                                 dt.RowChanged += rowChanged;
146                         }
147
148                         AddXmlDocumentListeners ();
149                 }
150
151                 // bool clone. If we are cloning XmlDataDocument then clone should be true.
152                 // FIXME: shouldn't DataSet be mapped to at most one document??
153                 private XmlDataDocument (DataSet dataset, bool clone)
154                 {
155                         InitDelegateFields ();
156
157                         this.dataSet = dataset;
158                         this.dataSet._xmlDataDocument = this;
159
160                         foreach (DataTable Table in DataSet.Tables) {
161                                 
162                                 foreach (DataRow Row in Table.Rows) {
163                                         Row.XmlRowID = dataRowID;
164                                         dataRowIDList.Add (dataRowID);
165                                         dataRowID++;
166                                 }
167                         }
168
169                         AddXmlDocumentListeners ();
170
171                         foreach (DataTable Table in dataSet.Tables) {
172                                 Table.ColumnChanged += columnChanged;
173                                 Table.RowDeleted += rowDeleted;
174                                 Table.RowChanged += rowChanged;
175                         }
176                 }
177
178                 #endregion // Constructors
179
180                 #region Public Properties
181
182                 public DataSet DataSet {
183                         get {
184                                 return dataSet;
185                         }
186                 }
187
188                 #endregion // Public Properties
189
190                 #region Public Methods
191
192                 private void FillNodeRows (XmlElement parent, DataTable dt, ICollection rows)
193                 {
194                         foreach (DataRow dr in dt.Rows) {
195                                 XmlDataElement el = dr.DataElement;
196                                 FillNodeChildrenFromRow (dr, el);
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 [XmlHelper.Decode (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                 private void OnNodeInserted (object sender, XmlNodeChangedEventArgs args)
402                 {
403                         if (!raiseDocumentEvents)
404                                 return;
405                         bool escapedRaiseDataSetEvents = raiseDataSetEvents;
406                         raiseDataSetEvents = false;
407
408                         // If the parent node is mapped to a DataTable, then
409                         // add a DataRow and map the parent element to it.
410                         //
411                         // AND If the child node is mapped to a DataTable, then
412                         // 1. if it is mapped to a DataTable and relation, add
413                         // a new DataRow and map the child element to it.
414                         // 2. if it is mapped to a DataColumn, set the column
415                         // value of the parent DataRow as the child
416
417                         try {
418                                 if (! (args.NewParent is XmlElement)) {
419                                         // i.e. adding document element
420                                         foreach (XmlNode table in args.Node.ChildNodes)
421                                                 CheckDescendantRelationship (table);
422                                         return;
423                                 }
424
425                                 DataRow row = GetRowFromElement (args.NewParent as XmlElement);
426                                 if (row == null) {
427                                         // That happens only when adding table to existing DocumentElement (aka DataSet element)
428                                         if (args.NewParent == DocumentElement)
429                                                 CheckDescendantRelationship (args.Node);
430                                         return;
431                                 }
432
433                                 XmlAttribute attr = args.Node as XmlAttribute;
434                                 if (attr != null) { // fill attribute value
435                                         DataColumn col = row.Table.Columns [XmlHelper.Decode (attr.LocalName)];
436                                         if (col != null)
437                                                 row [col] = StringToObject (col.DataType, args.Node.Value);
438                                 } else {
439                                         DataRow childRow = GetRowFromElement (args.Node as XmlElement);
440                                         if (childRow != null) {
441                                                 // child might be a table row.
442                                                 // I might be impossible to set parent
443                                                 // since either of them might be detached
444                                                 if (childRow.RowState != DataRowState.Detached && row.RowState != DataRowState.Detached) {
445                                                         FillRelationship (row, childRow, args.NewParent, args.Node);
446                                                 }
447                                         } else if (args.Node.NodeType == XmlNodeType.Element) {
448                                                 // child element might be a column
449                                                 DataColumn col = row.Table.Columns [XmlHelper.Decode (args.Node.LocalName)];
450                                                 if (col != null)
451                                                         row [col] = StringToObject (col.DataType, args.Node.InnerText);
452                                         } else if (args.Node is XmlCharacterData) {
453                                                 if (args.Node.NodeType != XmlNodeType.Comment) {
454                                                         for (int i = 0; i < row.Table.Columns.Count; i++) {
455                                                                 DataColumn col = row.Table.Columns [i];
456                                                                 if (col.ColumnMapping == MappingType.SimpleContent)
457                                                                         row [col] = StringToObject (col.DataType, args.Node.Value);
458                                                         }
459                                                 }
460                                         }
461                                 }
462                         } finally {
463                                 raiseDataSetEvents = escapedRaiseDataSetEvents;
464                         }
465                 }
466
467                 private void CheckDescendantRelationship (XmlNode n)
468                 {
469                         XmlElement el = n as XmlElement;
470                         DataRow row = GetRowFromElement (el);
471                         if (row == null)
472                                 return;
473                         row.Table.Rows.Add (row); // attach
474                         CheckDescendantRelationship (n, row);
475                 }
476
477                 private void CheckDescendantRelationship (XmlNode p, DataRow row)
478                 {
479                         foreach (XmlNode n in p.ChildNodes) {
480                                 XmlElement el = n as XmlElement;
481                                 if (el == null)
482                                         continue;
483                                 DataRow childRow = GetRowFromElement (el);
484                                 if (childRow == null)
485                                         continue;
486                                 childRow.Table.Rows.Add (childRow);
487                                 FillRelationship (row, childRow, p, el);
488                         }
489                 }
490
491                 private void FillRelationship (DataRow row, DataRow childRow, XmlNode parentNode, XmlNode childNode)
492                 {
493                         for (int i = 0; i < childRow.Table.ParentRelations.Count; i++) {
494                                 DataRelation rel = childRow.Table.ParentRelations [i];
495                                 if (rel.ParentTable == row.Table) {
496                                         childRow.SetParentRow (row);
497                                         break;
498                                 }
499                         }
500                         CheckDescendantRelationship (childNode, childRow);
501                 }
502                 #endregion // DataSet event handlers
503
504                 #region DataSet event handlers
505
506                 // If DataTable is added or removed from DataSet
507                 private void OnDataTableChanged (object sender, CollectionChangeEventArgs eventArgs)
508                 {
509                         if (!raiseDataSetEvents)
510                                 return;
511                         bool escapedRaiseDocumentEvents = raiseDocumentEvents;
512                         raiseDocumentEvents = false;
513
514                         try {
515                                 DataTable Table = (DataTable)eventArgs.Element;
516                                 switch (eventArgs.Action) {
517                                 case CollectionChangeAction.Add:
518                                         Table.ColumnChanged += columnChanged;
519                                         Table.RowDeleted += rowDeleted;
520                                         Table.RowChanged += rowChanged;
521                                         break;
522                                 case CollectionChangeAction.Remove:
523                                         Table.ColumnChanged -= columnChanged;
524                                         Table.RowDeleted -= rowDeleted;
525                                         Table.RowChanged -= rowChanged;
526                                         break;
527                                 }
528                         } finally {
529                                 raiseDocumentEvents = escapedRaiseDocumentEvents;
530                         }
531                 }
532
533                 // If column has changed 
534                 private void OnDataTableColumnChanged (object sender, 
535                                                              DataColumnChangeEventArgs eventArgs)
536                 {
537                         if (!raiseDataSetEvents)
538                                 return;
539                         bool escapedRaiseDocumentEvents = raiseDocumentEvents;
540                         raiseDocumentEvents = false;
541
542                         try {
543                                 DataRow row = eventArgs.Row;
544                                 XmlElement el = GetElementFromRow (row);
545                                 if (el == null)
546                                         return;
547                                 DataColumn col = eventArgs.Column;
548                                 string value = row.IsNull (col) ? String.Empty : row [col].ToString ();
549                                 switch (col.ColumnMapping) {
550                                 case MappingType.Attribute:
551                                         el.SetAttribute (XmlHelper.Encode (col.ColumnName), col.Namespace, value);
552                                         break;
553                                 case MappingType.SimpleContent:
554                                         el.InnerText = value;
555                                         break;
556                                 case MappingType.Element:
557                                         bool exists = false;
558                                         for (int i = 0; i < el.ChildNodes.Count; i++) {
559                                                 XmlElement c = el.ChildNodes [i] as XmlElement;
560                                                 if (c != null && c.LocalName == XmlHelper.Encode (col.ColumnName) && c.NamespaceURI == col.Namespace) {
561                                                         exists = true;
562                                                         c.InnerText = value;
563                                                         break;
564                                                 }
565                                         }
566                                         if (!exists) {
567                                                 XmlElement cel = CreateElement (col.Prefix, XmlHelper.Encode (col.ColumnName), col.Namespace); 
568                                                 cel.InnerText = value;
569                                                 el.AppendChild (cel);
570                                         }
571                                         break;
572                                 // FIXME: how to handle hidden?
573                                 }
574                         } finally {
575                                 raiseDocumentEvents = escapedRaiseDocumentEvents;
576                         }
577                 }
578         
579                 private void OnDataTableRowDeleted (object sender,
580                                                           DataRowChangeEventArgs eventArgs)
581                 {
582                         if (!raiseDataSetEvents)
583                                 return;
584                         bool escapedRaiseDocumentEvents = raiseDocumentEvents;
585                         raiseDocumentEvents = false;
586
587                         try {
588                                 // This code is obsolete XmlDataDocument one
589
590                                 XmlElement el = GetElementFromRow (eventArgs.Row);
591                                 if (el == null)
592                                         return;
593
594                                 el.ParentNode.RemoveChild (el);
595                         } finally {
596                                 raiseDocumentEvents = escapedRaiseDocumentEvents;
597                         }
598                 }
599                 
600                 [MonoTODO ("Need to handle hidden columns? - see comments on each private method")]
601                 private void OnDataTableRowChanged (object sender, DataRowChangeEventArgs eventArgs)
602                 {
603                         if (!raiseDataSetEvents)
604                                 return;
605                         bool escapedRaiseDocumentEvents = raiseDocumentEvents;
606                         raiseDocumentEvents = false;
607                         try {
608
609                                 switch (eventArgs.Action) {
610
611                                 case DataRowAction.Delete:
612                                         OnDataTableRowDeleted (sender, eventArgs);
613                                         break;
614
615                                 case DataRowAction.Add:
616                                         OnDataTableRowAdded (eventArgs);
617                                         break;
618
619                                 case DataRowAction.Rollback:
620                                         OnDataTableRowRollback (eventArgs);
621                                         break;
622                                 default:
623                                         break;
624                                 } 
625                         } finally {
626                                 raiseDocumentEvents = escapedRaiseDocumentEvents;
627                         }
628                 }
629
630                 // Added - see FillNodeChildrenFromRow comment
631                 private void OnDataTableRowAdded (DataRowChangeEventArgs args)
632                 {
633                         if (!raiseDataSetEvents)
634                                 return;
635
636                         bool escapedRaiseDocumentEvents = raiseDocumentEvents;
637                         raiseDocumentEvents = false;
638
639                         try {
640                                 // Create row element. Row's name same as TableName                                     
641                                 DataRow row = args.Row;
642
643                                 // create document element if it does not exist
644                                 if (DocumentElement == null)
645                                         this.AppendChild (CreateElement (XmlHelper.Encode (DataSet.DataSetName)));
646
647                                 DataTable table= args.Row.Table;
648                                 XmlElement element = GetElementFromRow (row);
649                                 if (element == null)
650                                         element = CreateElement (table.Prefix, XmlHelper.Encode (table.TableName), table.Namespace);
651
652                                 if (element.ChildNodes.Count == 0)
653                                         FillNodeChildrenFromRow (row, element);
654
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, XmlHelper.Encode (col.ColumnName), col.Namespace);
690                                         el.InnerText = value;
691                                         element.AppendChild (el);
692                                         break;
693                                 case MappingType.Attribute:
694                                         XmlAttribute attr = CreateAttribute (col.Prefix, XmlHelper.Encode (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 [XmlHelper.Decode (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 [XmlHelper.Decode (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 (value == null || value == String.Empty)
788                                 return DBNull.Value;
789
790                         switch (Type.GetTypeCode (type)) {
791                                 case TypeCode.Boolean: return XmlConvert.ToBoolean (value);
792                                 case TypeCode.Byte: return XmlConvert.ToByte (value);
793                                 case TypeCode.Char: return (char)XmlConvert.ToInt32 (value);
794 #if NET_2_0
795                                 case TypeCode.DateTime: return XmlConvert.ToDateTime (value, XmlDateTimeSerializationMode.Unspecified);
796 #else
797                                 case TypeCode.DateTime: return XmlConvert.ToDateTime (value);
798 #endif
799                                 case TypeCode.Decimal: return XmlConvert.ToDecimal (value);
800                                 case TypeCode.Double: return XmlConvert.ToDouble (value);
801                                 case TypeCode.Int16: return XmlConvert.ToInt16 (value);
802                                 case TypeCode.Int32: return XmlConvert.ToInt32 (value);
803                                 case TypeCode.Int64: return XmlConvert.ToInt64 (value);
804                                 case TypeCode.SByte: return XmlConvert.ToSByte (value);
805                                 case TypeCode.Single: return XmlConvert.ToSingle (value);
806                                 case TypeCode.UInt16: return XmlConvert.ToUInt16 (value);
807                                 case TypeCode.UInt32: return XmlConvert.ToUInt32 (value);
808                                 case TypeCode.UInt64: return XmlConvert.ToUInt64 (value);
809                         }
810
811                         if (type == typeof (TimeSpan)) return XmlConvert.ToTimeSpan (value);
812                         if (type == typeof (Guid)) return XmlConvert.ToGuid (value);
813                         if (type == typeof (byte[])) return Convert.FromBase64String (value);
814
815                         return Convert.ChangeType (value, type);
816                 }
817                 #endregion // Private methods
818         }
819 }
820