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