2005-01-31 Zoltan Varga <vargaz@freemail.hu>
[mono.git] / mcs / class / System.Data / System.Xml / XmlDataDocument.cs
index c69b91fff706b03d380ecce202586b9aa6b74830..b001652c825402e8fbd3f0583fea66ff1a6c7b73 100644 (file)
 // Author:
 //     Daniel Morgan <danmorg@sc.rr.com>
 //     Ville Palo <vi64pa@koti.soon.fi>
+//     Atsushi Enomoto <atsushi@ximian.com>
 //
 // (c)copyright 2002 Daniel Morgan
+// (c)copyright 2003 Ville Palo
+// (c)2004 Novell Inc.
 //
 // XmlDataDocument is included within the Mono Class Library.
 //
 
+//
+// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
 using System;
 using System.Data;
 using System.IO;
 using System.Text;
 using System.Xml.XPath;
 using System.Collections;
+using System.Globalization;
+using System.ComponentModel;
+
+namespace System.Xml 
+{
+
+       public class XmlDataDocument : XmlDocument 
+       {
+               // Should we consider overriding CloneNode() ? By default
+               // base CloneNode() will be invoked and thus no DataRow conflict
+               // would happen, that sounds the best (that means, no mapped
+               // DataRow will be provided).
+               internal class XmlDataElement : XmlElement
+               {
+                       DataRow row;
+
+                       internal XmlDataElement (DataRow row, string prefix, string localName, string ns, XmlDataDocument doc)
+                               : base (prefix, localName, ns, doc)
+                       {
+                               this.row = row;
+                               // Embed row ID only when the element is mapped to
+                               // certain DataRow.
+                               if (row != null) {
+                                       row.DataElement = this;
+                                       row.XmlRowID = doc.dataRowID;
+                                       doc.dataRowIDList.Add (row.XmlRowID);
+                                       // It should not be done here. The node is detached
+                                       // dt.Rows.Add (tempRow);
+                                       doc.dataRowID++;
+                               }
+                       }
 
-namespace System.Xml {
-
-       public class XmlDataDocument : XmlDocument {
+                       internal DataRow DataRow {
+                               get { return row; }
+                       }
+               }
 
                #region Fields
 
                private DataSet dataSet;
-               private bool isReadOnly = false;
 
                private int dataRowID = 1;
                private ArrayList dataRowIDList = new ArrayList ();
 
+               // this keeps whether table change events should be handles
+               private bool raiseDataSetEvents = true;
+               private bool raiseDocumentEvents = true;
+
+               // this is needed for inserting new row to datatable via xml
+               private Hashtable TempTable = new Hashtable ();
+
+               DataColumnChangeEventHandler columnChanged;
+               DataRowChangeEventHandler rowDeleted;
+               DataRowChangeEventHandler rowChanged;
+               CollectionChangeEventHandler tablesChanged;
                #endregion // Fields
 
                #region Constructors
 
-               public XmlDataDocument() {
+               public XmlDataDocument ()
+               {
+                       InitDelegateFields ();
+
                        dataSet = new DataSet();
-                       this.NodeChanged += new XmlNodeChangedEventHandler (OnXmlDataColumnChanged);
-                       //this.NodeChanging += new XmlNodeChangedEventHandler (OnXmlDataColumnChanged);
-                       //this.NodeInserted += new XmlNodeChangedEventHandler (OnXmlDataColumnChanged);
-                       //this.NodeRemoved += new XmlNodeChangedEventHandler (OnXmlDataColumnChanged);
-               }
+                       dataSet._xmlDataDocument = this;
+                       dataSet.Tables.CollectionChanged += tablesChanged;
 
-               public XmlDataDocument(DataSet dataset) {
-                       this.dataSet = dataset;
-                       this.NodeChanged += new XmlNodeChangedEventHandler (OnXmlDataColumnChanged);
+                       AddXmlDocumentListeners ();
+                       DataSet.EnforceConstraints = false;
                }
 
-               #endregion // Constructors
+               public XmlDataDocument (DataSet dataset) 
+               {
+                       if (dataset == null)
+                               throw new ArgumentException ("Parameter dataset cannot be null.");\r
+                       if (dataset._xmlDataDocument != null)\r
+                               throw new ArgumentException ("DataSet cannot be associated with two or more XmlDataDocument.");\r
 
-               #region Public Properties
+                       InitDelegateFields ();
 
-               public override string BaseURI {
-                       [MonoTODO]
-                       get {
-                               // TODO: why are we overriding?
-                               return base.BaseURI;
-                       }
-               }
+                       this.dataSet = dataset;
+                       this.dataSet._xmlDataDocument = this;
 
-               public DataSet DataSet {
-                       [MonoTODO]
-                       get {
-                               return dataSet;
+                       XmlElement docElem = CreateElement (dataSet.Prefix, dataSet.DataSetName, dataSet.Namespace);
+                       foreach (DataTable dt in dataSet.Tables) {
+                               if (dt.ParentRelations.Count > 0)
+                                       continue; // don't add them here
+                               FillNodeRows (docElem, dt, dt.Rows);
                        }
-               }
 
-               // override inheritted method from XmlDocument
-               public override string InnerXml {
-                       [MonoTODO]
-                       get {
-                               throw new NotImplementedException();
-                       }
-                                
-                       [MonoTODO]
-                       set {
-                               throw new NotImplementedException();
-                       }
-               }
+                       // This seems required to avoid Load() error when for
+                       // example empty DataSet will be filled on Load().
+                       if (docElem.ChildNodes.Count > 0)
+                               AppendChild (docElem);
 
-               public override bool IsReadOnly {
-                       [MonoTODO]
-                       get {
-                               return isReadOnly;
+                       foreach (DataTable dt in dataSet.Tables) {
+                               dt.ColumnChanged += columnChanged;
+                               dt.RowDeleted += rowDeleted;
+                               dt.RowChanged += rowChanged;
                        }
 
+                       AddXmlDocumentListeners ();
                }
 
-               // Item indexer
-               public override XmlElement this[string name] {
-                       [MonoTODO]
-                       get {
-                               throw new NotImplementedException();
-                       }
-               }
+               // bool clone. If we are cloning XmlDataDocument then clone should be true.
+               // FIXME: shouldn't DataSet be mapped to at most one document??
+               private XmlDataDocument (DataSet dataset, bool clone)
+               {
+                       InitDelegateFields ();
 
-               // Item indexer
-               public override XmlElement this[string localname, string ns] {
-                       [MonoTODO]
-                       get {
-                               throw new NotImplementedException();
-                       }
-               }
+                       this.dataSet = dataset;
+                       this.dataSet._xmlDataDocument = this;
 
-               public override string LocalName {
-                       [MonoTODO]
-                       get {
-                               throw new NotImplementedException();
+                       foreach (DataTable Table in DataSet.Tables) {
+                               
+                               foreach (DataRow Row in Table.Rows) {
+                                       Row.XmlRowID = dataRowID;
+                                       dataRowIDList.Add (dataRowID);
+                                       dataRowID++;
+                               }
                        }
-               }
 
-               public override string Name {
-                       [MonoTODO]
-                       get {
-                               throw new NotImplementedException();
+                       AddXmlDocumentListeners ();
+
+                       foreach (DataTable Table in dataSet.Tables) {
+                               Table.ColumnChanged += columnChanged;
+                               Table.RowDeleted += rowDeleted;
+                               Table.RowChanged += rowChanged;
                        }
                }
 
-               public override XmlDocument OwnerDocument {
-                       [MonoTODO]
+               #endregion // Constructors
+
+               #region Public Properties
+
+               public DataSet DataSet {
                        get {
-                               return null;
+                               return dataSet;
                        }
                }
 
@@ -134,51 +194,97 @@ namespace System.Xml {
 
                #region Public Methods
 
-               [MonoTODO]
-               public override XmlNode CloneNode(bool deep) 
+               private void FillNodeRows (XmlElement parent, DataTable dt, ICollection rows)
                {
-                       throw new NotImplementedException();
+                       foreach (DataRow dr in dt.Rows) {
+                               XmlDataElement el = new XmlDataElement (dr, dt.Prefix, dt.TableName, dt.Namespace, this);
+                               for (int i = 0; i < dt.Columns.Count; i++) {
+                                       DataColumn col = dt.Columns [i];
+                                       string value = dr.IsNull (col) ? String.Empty : dr [col].ToString ();
+                                       switch (col.ColumnMapping) {
+                                       case MappingType.Element:
+                                               XmlElement cel = CreateElement (col.Prefix, col.ColumnName, col.Namespace);
+                                               cel.InnerText = value;
+                                               el.AppendChild (cel);
+                                               break;
+                                       case MappingType.Attribute:
+                                               XmlAttribute a = CreateAttribute (col.Prefix, col.ColumnName, col.Namespace);
+                                               a.Value = value;
+                                               el.SetAttributeNode (a);
+                                               break;
+                                       case MappingType.SimpleContent:
+                                               XmlText t = CreateTextNode (value);
+                                               el.AppendChild (t);
+                                               break;
+                                       }
+                               }
+                               foreach (DataRelation rel in dt.ChildRelations)
+                                       FillNodeRows (el, rel.ChildTable, dr.GetChildRows (rel));
+                               parent.AppendChild (el);
+                       }
+               }
+
+               public override XmlNode CloneNode (bool deep) 
+               {
+                       XmlDataDocument Document;
+                       if (deep)
+                               Document = new XmlDataDocument (DataSet.Copy (), true);
+                       else
+                               Document = new XmlDataDocument (DataSet.Clone (), true);
+
+                       Document.RemoveXmlDocumentListeners ();
+
+                       Document.PreserveWhitespace = PreserveWhitespace;
+                       if (deep) {
+                               foreach(XmlNode n in ChildNodes)
+                                       Document.AppendChild (Document.ImportNode (n, deep));
+                       }
+
+                       Document.AddXmlDocumentListeners ();
+
+                       return Document;                        
                }
 
                #region overloaded CreateElement methods
 
-               [MonoTODO]
-               public override XmlElement CreateElement(string prefix,
-                               string localName, string namespaceURI) 
+               public override XmlElement CreateElement(
+                        string prefix, string localName, string namespaceURI) 
                {
-                       if ((localName == null) || (localName == String.Empty))
-                               throw new ArgumentException ("The local name for elements or attributes cannot be null" +
-                                                            "or an empty string.");
-                       string pref = prefix != null ? prefix : String.Empty;
-                       return base.CreateElement (pref, localName, namespaceURI != null ? namespaceURI : String.Empty);
-
+                       DataTable dt = DataSet.Tables [localName];
+                       DataRow row = dt != null ? dt.NewRow () : null;
+                       if (row != null)
+                               return GetElementFromRow (row);
+                       else
+                               return base.CreateElement (prefix, localName, namespaceURI);
                }
 
                #endregion // overloaded CreateElement Methods
                        
-               // will not be supported
+               // It is not supported in XmlDataDocument
                public override XmlEntityReference CreateEntityReference(string name) 
                {
-                       throw new NotSupportedException();
+                       throw new NotSupportedException ();
                }
                
-               // will not be supported
-               public override XmlElement GetElementById(string elemId) 
+               // It is not supported in XmlDataDocument
+               public override XmlElement GetElementById (string elemId) 
                {
-                       throw new NotSupportedException();
+                       throw new NotSupportedException ();
                }
 
                // get the XmlElement associated with the DataRow
-               public XmlElement GetElementFromRow(DataRow r) 
+               public XmlElement GetElementFromRow (DataRow r) 
                {
-                       throw new NotImplementedException();
+                       return r.DataElement;
                }
 
                // get the DataRow associated with the XmlElement
-               [MonoTODO]
-               public DataRow GetRowFromElement(XmlElement e)
+               public DataRow GetRowFromElement (XmlElement e)
                {
-                       throw new NotImplementedException();
+                       XmlDataElement el = e as XmlDataElement;
+                       if (el == null)
+                               return null;
+                       return el.DataRow;
                }
 
                #region overload Load methods
@@ -195,268 +301,537 @@ namespace System.Xml {
                        Load (new XmlTextReader (txtReader));
                }
 
-               public override void Load(XmlReader reader) {
-                       
-                       DataTable dt = null;
+               public override void Load (XmlReader reader) 
+               {
+                       if (DocumentElement != null)
+                               throw new InvalidOperationException ("XmlDataDocument does not support multi-time loading. New XmlDadaDocument is always required.");
 
-                       // For reading xml to XmlDocument
-                       XmlTextReader textReader = new XmlTextReader (
-                               reader.BaseURI);
+                       bool OldEC = DataSet.EnforceConstraints;
+                       DataSet.EnforceConstraints = false;
+                       dataSet.Tables.CollectionChanged -= tablesChanged;
 
-                       if (reader.NodeType != XmlNodeType.Element)
-                               reader.MoveToContent ();
+                       base.Load (reader);
 
-                       // TODO: Findout which exception should be throwen
-                       if (reader.NodeType != XmlNodeType.Element)
-                               throw new Exception ();
+                       DataSet.EnforceConstraints = OldEC;
+                       dataSet.Tables.CollectionChanged += tablesChanged;
+               }
+               
+               #endregion // overloaded Load methods
+               #endregion // Public Methods
 
-                       if (dataSet.DataSetName != reader.Name)
-                               throw new Exception ();
+               #region Protected Methods
 
-                       // read to next element
-                       while (reader.Read () && reader.NodeType != XmlNodeType.Element);
+               [MonoTODO ("Create optimized XPathNavigator")]
+               protected override XPathNavigator CreateNavigator(XmlNode node) {
+                       return base.CreateNavigator (node);
+               }
+
+               #endregion // Protected Methods
+               
+               #region XmlDocument event handlers
+
+               private void OnNodeChanging (object sender, XmlNodeChangedEventArgs args)
+               {
+                       if (!this.raiseDocumentEvents)
+                               return;
+                       if (DataSet.EnforceConstraints) 
+                               throw new InvalidOperationException (Locale.GetText ("Please set DataSet.EnforceConstraints == false before trying to edit XmlDataDocument using XML operations."));
+               }
+
+               // Invoked when XmlNode is changed colum is changed
+               private void OnNodeChanged (object sender, XmlNodeChangedEventArgs args)
+               {
+                       if (!raiseDocumentEvents)
+                               return;
+                       bool escapedRaiseDataSetEvents = raiseDataSetEvents;
+                       raiseDataSetEvents = false;
+                       try {
 
-                       do {
+                               if (args.Node == null)
+                                       return;
 
+                               DataRow row = GetRowFromElement ((XmlElement)args.Node.ParentNode.ParentNode);
 
-                               // Find right table from tablecollection
-                               for (int i = 0; i < DataSet.Tables.Count && dt == null; i++) {
+                               if (row == null)
+                                       return;
 
-                                       if (reader.Name == DataSet.Tables [i].TableName) {
+                               if (!row.Table.Columns.Contains (args.Node.ParentNode.Name))
+                                       return;
 
-                                               dt = DataSet.Tables [i];
-                                               dt.ColumnChanged += new DataColumnChangeEventHandler (OnDataTableColumnChanged);
-                                               dt.RowDeleted += new DataRowChangeEventHandler (OnDataTableRowDeleted);
-                                               dt.RowChanged += new DataRowChangeEventHandler (OnDataTableRowChanged);
-                                       }
+                               if (row [args.Node.ParentNode.Name].ToString () != args.Node.InnerText) {
+                                       DataColumn col = row.Table.Columns [args.Node.ParentNode.Name];
+                                       row [col] = StringToObject (col.DataType, args.Node.InnerText);
                                }
+
+                       } finally {
+                               raiseDataSetEvents = escapedRaiseDataSetEvents;
+                       }
+               }
+
+               private void OnNodeRemoving (object sender, XmlNodeChangedEventArgs args) 
+               {
+                       if (!this.raiseDocumentEvents)
+                               return;
+                       if (DataSet.EnforceConstraints) 
+                               throw new InvalidOperationException (Locale.GetText ("Please set DataSet.EnforceConstraints == false before trying to edit XmlDataDocument using XML operations."));
+                       
+               }
+               
+               // Invoked when XmlNode is removed
+               private void OnNodeRemoved (object sender, XmlNodeChangedEventArgs args)
+               {
+                       if (!raiseDocumentEvents)
+                               return;
+                       bool escapedRaiseDataSetEvents = raiseDataSetEvents;
+                       raiseDataSetEvents = false;
+
+                       try {
+                               if (args.OldParent == null)
+                                       return;
+
+                               XmlElement oldParentElem = args.OldParent as XmlElement;
+                               if (oldParentElem == null)
+                                       return;
                                
-                               // TODO: Findout what kind of exception 
-                               if (dt == null) 
-                                       throw new Exception (); // there were no correct table
-
-                               // Read rows to table
-                               DataRow tempRow = dt.NewRow ();
-                               while ((reader.NodeType != XmlNodeType.EndElement ||
-                                       reader.Name != dt.TableName) && reader.Read()) {
-                                       
-                                       switch (reader.NodeType) {
-                                               
-                                       case XmlNodeType.Element:
-                                               // Add column to DataRow
-                                               LoadRow (reader, ref tempRow);
-                                               break;
-                                       default:
-                                               break;
-                                       }                       
+                               // detach child row (if exists)
+                               XmlElement childElem = args.Node as XmlElement;
+                               if (childElem != null) {
+                                       DataRow childRow = GetRowFromElement (childElem);
+                                       if (childRow != null)
+                                               childRow.Table.Rows.Remove (childRow);
                                }
 
-                               // Every row must have unique id.
-                               tempRow.XmlRowID = dataRowID;
-                               dataRowIDList.Add (dataRowID);
-                               dt.Rows.Add (tempRow);
-                               dataRowID++;                                    
-                               
+                               DataRow row = GetRowFromElement (oldParentElem);
                                
-                       } while (reader.Read ());
+                               if (row == null)
+                                       return ;
 
-                       base.Load (textReader);
-               }
-               
-               #endregion // overloaded Load methods
+                               row [args.Node.Name] = null;
 
-               [MonoTODO]
-               public override void WriteContentTo(XmlWriter xw) {
-                       base.WriteContentTo (xw);
+                       } finally {
+                               raiseDataSetEvents = escapedRaiseDataSetEvents;
+                       }
                }
 
-               [MonoTODO]
-               public override void WriteTo(XmlWriter w) {
-                       base.WriteTo (w);
+               private void OnNodeInserting (object sender, XmlNodeChangedEventArgs args) 
+               {
+                       if (!this.raiseDocumentEvents)
+                               return;
+                       if (DataSet.EnforceConstraints) 
+                               throw new InvalidOperationException (Locale.GetText ("Please set DataSet.EnforceConstraints == false before trying to edit XmlDataDocument using XML operations."));
+                       
                }
+               
+               private void OnNodeInserted (object sender, XmlNodeChangedEventArgs args)
+               {
+                       if (!raiseDocumentEvents)
+                               return;
+                       bool escapedRaiseDataSetEvents = raiseDataSetEvents;
+                       raiseDataSetEvents = false;
+
+                       // If the parent node is mapped to a DataTable, then
+                       // add a DataRow and map the parent element to it.
+                       //
+                       // AND If the child node is mapped to a DataTable, then
+                       // 1. if it is mapped to a DataTable and relation, add
+                       // a new DataRow and map the child element to it.
+                       // 2. if it is mapped to a DataColumn, set the column
+                       // value of the parent DataRow as the child
+
+                       try {
+                               if (! (args.NewParent is XmlElement)) {
+                                       // i.e. adding document element
+                                       foreach (XmlNode table in args.Node.ChildNodes)
+                                               CheckDescendantRelationship (table);
+                                       return;
+                               }
 
-               #endregion // Public Methods
-
-               #region Protected Methods
+                               DataRow row = GetRowFromElement (args.NewParent as XmlElement);
+                               if (row == null) {
+                                       // That happens only when adding table to existing DocumentElement (aka DataSet element)
+                                       if (args.NewParent == DocumentElement)
+                                               CheckDescendantRelationship (args.Node);
+                                       return;
+                               }
 
-               //FIXME: how do you handle this?
-               //[MonoTODO]
-               //protected internal override XPathNavigator CreateNavigator(XmlNode node) {
-               //      throw new NotImplementedException();
-               //}
+                               XmlAttribute attr = args.Node as XmlAttribute;
+                               if (attr != null) { // fill attribute value
+                                       DataColumn col = row.Table.Columns [attr.LocalName];
+                                       if (col != null)
+                                               row [col] = StringToObject (col.DataType, args.Node.Value);
+                               } else {
+                                       DataRow childRow = GetRowFromElement (args.Node as XmlElement);
+                                       if (childRow != null) {
+                                               // child might be a table row.
+                                               // I might be impossible to set parent
+                                               // since either of them might be detached
+                                               if (childRow.RowState != DataRowState.Detached && row.RowState != DataRowState.Detached) {
+                                                       FillRelationship (row, childRow, args.NewParent, args.Node);
+                                               }
+                                       } else if (args.Node.NodeType == XmlNodeType.Element) {
+                                               // child element might be a column
+                                               DataColumn col = row.Table.Columns [args.Node.LocalName];
+                                               if (col != null)
+                                                       row [col] = StringToObject (col.DataType, args.Node.InnerText);
+                                       } else if (args.Node is XmlCharacterData) {
+                                               if (args.Node.NodeType != XmlNodeType.Comment) {
+                                                       for (int i = 0; i < row.Table.Columns.Count; i++) {
+                                                               DataColumn col = row.Table.Columns [i];
+                                                               if (col.ColumnMapping == MappingType.SimpleContent)
+                                                                       row [col] = StringToObject (col.DataType, args.Node.Value);
+                                                       }
+                                               }
+                                       }
+                               }
+                       } finally {
+                               raiseDataSetEvents = escapedRaiseDataSetEvents;
+                       }
+               }
 
-               [MonoTODO]
-               public new XPathNavigator CreateNavigator() {
-                       throw new NotImplementedException();
+               private void CheckDescendantRelationship (XmlNode n)
+               {
+                       XmlElement el = n as XmlElement;
+                       DataRow row = GetRowFromElement (el);
+                       if (row == null)
+                               return;
+                       row.Table.Rows.Add (row); // attach
+                       CheckDescendantRelationship (n, row);
                }
 
-               #endregion // Protected Methods
-               
-               #region DataSet event handlers
+               private void CheckDescendantRelationship (XmlNode p, DataRow row)
+               {
+                       foreach (XmlNode n in p.ChildNodes) {
+                               XmlElement el = n as XmlElement;
+                               if (el == null)
+                                       continue;
+                               DataRow childRow = GetRowFromElement (el);
+                               if (childRow == null)
+                                       continue;
+                               childRow.Table.Rows.Add (childRow);
+                               FillRelationship (row, childRow, p, el);
+                       }
+               }
 
-               // this changed datatable values when some of xmldocument elements is changed
-               private void OnXmlDataColumnChanged (object sender, XmlNodeChangedEventArgs args)
+               private void FillRelationship (DataRow row, DataRow childRow, XmlNode parentNode, XmlNode childNode)
                {
-                       int i = 0;
-                       XPathNavigator nodeNavigator = args.Node.CreateNavigator ();
-                       int c = GetElementsByTagName (args.Node.Name).Count;
+                       for (int i = 0; i < childRow.Table.ParentRelations.Count; i++) {
+                               DataRelation rel = childRow.Table.ParentRelations [i];
+                               if (rel.ParentTable == row.Table) {
+                                       childRow.SetParentRow (row);
+                                       break;
+                               }
+                       }
+                       CheckDescendantRelationship (childNode, childRow);
+               }
+               #endregion // DataSet event handlers
 
-                       // FIXME: I dont know which way it should be but this work on linux.
-                       // could be also GetElementsByTagName (args.OldParent.Name) []
-                       XmlNodeList nodeList = GetElementsByTagName (args.Node.Name);
-                       
+               #region DataSet event handlers
 
-                       bool isSame = false;
-                       
-                       // Find right row                                       
-                       while ((i < c) && !isSame ) {
-
-                               XPathNavigator docNavigator = nodeList [i].CreateNavigator ();
-                               isSame = docNavigator.IsSamePosition (nodeNavigator);
-                               docNavigator = nodeList [i].CreateNavigator ();
-                               i++;
-                       } 
-                       
-                       // if there wasnt such row it is just added and we dont need to care about it
-                       if (!isSame) 
+               // If DataTable is added or removed from DataSet
+               private void OnDataTableChanged (object sender, CollectionChangeEventArgs eventArgs)
+               {
+                       if (!raiseDataSetEvents)
                                return;
-
-                       // now we know rownum
-                       int xmlrowid = (int)dataRowIDList [i];
-                      
-                       DataTable dt = DataSet.Tables [args.OldParent.Name];                           
-                       foreach (DataRow r in dt.Rows) {
-                               if (xmlrowid == r.XmlRowID) {
-                                       
-                                       // change value only when have to.
-                                       if ((string)r [args.Node.Name] != (string)args.Node.InnerText)
-                                               r [args.Node.Name] = args.Node.InnerText;
+                       bool escapedRaiseDocumentEvents = raiseDocumentEvents;
+                       raiseDocumentEvents = false;
+
+                       try {
+                               DataTable Table = (DataTable)eventArgs.Element;
+                               switch (eventArgs.Action) {
+                               case CollectionChangeAction.Add:
+                                       Table.ColumnChanged += columnChanged;
+                                       Table.RowDeleted += rowDeleted;
+                                       Table.RowChanged += rowChanged;
+                                       break;
+                               case CollectionChangeAction.Remove:
+                                       Table.ColumnChanged -= columnChanged;
+                                       Table.RowDeleted -= rowDeleted;
+                                       Table.RowChanged -= rowChanged;
+                                       break;
                                }
-                       }                       
+                       } finally {
+                               raiseDocumentEvents = escapedRaiseDocumentEvents;
+                       }
                }
 
-               [MonoTODO]
-               private void OnDataTableColumnChanged(object sender, 
+               // If column has changed 
+               private void OnDataTableColumnChanged (object sender, 
                                                             DataColumnChangeEventArgs eventArgs)
                {
-                       // row is not yet in datatable
-                       if (eventArgs.Row.XmlRowID == 0)
+                       if (!raiseDataSetEvents)
                                return;
-
-                       // TODO: Here should be some kind of error checking.
-                       GetElementsByTagName (eventArgs.Column.ToString ()) [dataRowIDList.IndexOf (
-                               eventArgs.Row.XmlRowID)].InnerText = (string)eventArgs.ProposedValue;
+                       bool escapedRaiseDocumentEvents = raiseDocumentEvents;
+                       raiseDocumentEvents = false;
+
+                       try {
+                               DataRow row = eventArgs.Row;
+                               XmlElement el = GetElementFromRow (row);
+                               if (el == null)
+                                       return;
+                               DataColumn col = eventArgs.Column;
+                               string value = row.IsNull (col) ? String.Empty : row [col].ToString ();
+                               switch (col.ColumnMapping) {
+                               case MappingType.Attribute:
+                                       el.SetAttribute (col.ColumnName, col.Namespace, value);
+                                       break;
+                               case MappingType.SimpleContent:
+                                       el.InnerText = value;
+                                       break;
+                               case MappingType.Element:
+                                       bool exists = false;
+                                       for (int i = 0; i < el.ChildNodes.Count; i++) {
+                                               XmlElement c = el.ChildNodes [i] as XmlElement;
+                                               if (c != null && c.LocalName == col.ColumnName && c.NamespaceURI == col.Namespace) {
+                                                       exists = true;
+                                                       c.InnerText = value;
+                                                       break;
+                                               }
+                                       }
+                                       if (!exists) {
+                                               XmlElement cel = CreateElement (col.Prefix, col.ColumnName, col.Namespace);
+                                               cel.InnerText = value;
+                                               el.AppendChild (cel);
+                                       }
+                                       break;
+                               // FIXME: how to handle hidden?
+                               }
+                       } finally {
+                               raiseDocumentEvents = escapedRaiseDocumentEvents;
+                       }
                }
        
-               [MonoTODO]
-               private void OnDataTableRowDeleted(object sender,
+               private void OnDataTableRowDeleted (object sender,
                                                          DataRowChangeEventArgs eventArgs)
                {
-                       DataRow deletedRow = null;
-                       deletedRow = eventArgs.Row;
-
-                       if (eventArgs.Row.XmlRowID == 0)
+                       if (!raiseDataSetEvents)
                                return;
+                       bool escapedRaiseDocumentEvents = raiseDocumentEvents;
+                       raiseDocumentEvents = false;
+
+                       try {
+                               // This code is obsolete XmlDataDocument one
+
+                               DataRow deletedRow = null;
+                               deletedRow = eventArgs.Row;
 
-                       int rowIndex = dataRowIDList.IndexOf (eventArgs.Row.XmlRowID);
+                               XmlElement el = GetElementFromRow (eventArgs.Row);
+                               if (el == null)
+                                       return;
 
-                       // Remove element from xmldocument and row indexlist
-                       GetElementsByTagName (deletedRow.Table.TableName) [rowIndex].RemoveAll ();
-                       dataRowIDList.RemoveAt (rowIndex);
+                               el.ParentNode.RemoveChild (el);
+                       } finally {
+                               raiseDocumentEvents = escapedRaiseDocumentEvents;
+                       }
                }
                
-               [MonoTODO]
-               private void OnDataTableRowChanged(object sender, DataRowChangeEventArgs eventArgs)
+               [MonoTODO ("Need to handle hidden columns? - see comments on each private method")]
+               private void OnDataTableRowChanged (object sender, DataRowChangeEventArgs eventArgs)
                {
-                       switch (eventArgs.Action) {
+                       if (!raiseDataSetEvents)
+                               return;
+                       bool escapedRaiseDocumentEvents = raiseDocumentEvents;
+                       raiseDocumentEvents = false;
+                       try {
+
+                               switch (eventArgs.Action) {
 
-                               case DataRowAction.Delete:
+                               case DataRowAction.Delete:
                                        OnDataTableRowDeleted (sender, eventArgs);
                                        break;
 
-                               case DataRowAction.Add:
+                               case DataRowAction.Add:
                                        OnDataTableRowAdded (eventArgs);
                                        break;
 
-                               case DataRowAction.Rollback:
+                               case DataRowAction.Rollback:
                                        OnDataTableRowRollback (eventArgs);
                                        break;
-                               default:
+                               default:
                                        break;
-                       } 
+                               } 
+                       } finally {
+                               raiseDocumentEvents = escapedRaiseDocumentEvents;
+                       }
                }
 
-               // Added
-               [MonoTODO]
+               // Added - see FillNodeChildrenFromRow comment
                private void OnDataTableRowAdded (DataRowChangeEventArgs args)
                {
-                       // If XmlRowID is != 0 then it is already added
-                       if (args.Row.XmlRowID != 0)
+                       if (!raiseDataSetEvents)
                                return;
-                       
-                       // Create row element. Row's name same as TableName                                     
-                       DataRow row = args.Row;
-                       row.XmlRowID = dataRowID;
-                       dataRowID++;
-                       XmlElement element = CreateElement (args.Row.Table.TableName);
-                       DocumentElement.AppendChild (element);
-                       
-                       XmlElement rowElement = null;
-                       for (int i = 0; i < row.Table.Columns.Count; i++) {
+                       bool escapedRaiseDocumentEvents = raiseDocumentEvents;
+                       raiseDocumentEvents = false;
+
+                       try {
+
+                               // Create row element. Row's name same as TableName                                     
+                               DataRow row = args.Row;
+
+                               // create document element if it does not exist
+                               if (DocumentElement == null)
+                                       this.AppendChild (CreateElement (DataSet.DataSetName));
+
+                               DataTable table= args.Row.Table;
+                               XmlElement element = GetElementFromRow (row);
+                               if (element == null)
+                                       element = CreateElement (table.Prefix, table.TableName, table.Namespace);
+                               if (element.ParentNode == null) {
+                                       // parent is not always DocumentElement.
+                                       XmlElement parent = null;
+
+                                       if (table.ParentRelations.Count > 0) {
+                                               for (int i = 0; i < table.ParentRelations.Count; i++) {
+                                                       DataRelation rel = table.ParentRelations [i];
+                                                       DataRow parentRow = row.GetParentRow (rel);
+                                                       if (parentRow == null)
+                                                               continue;
+                                                       parent = GetElementFromRow (parentRow);
+                                               }
+                                       }
+
+                                       // The row might be orphan. In such case, the 
+                                       // element is appended to DocumentElement.
+                                       if (parent == null)
+                                               parent = DocumentElement;
+                                       parent.AppendChild (element);
+                               }
+                       } finally {                     
+                               raiseDocumentEvents = escapedRaiseDocumentEvents;
+                       }
+               }
 
-                               rowElement = CreateElement (row.Table.Columns [i].ToString ());
-                               rowElement.InnerText = (string)row [i];
-                               element.AppendChild (rowElement);
+               private void FillNodeChildrenFromRow (DataRow row, XmlElement element)
+               {
+                       DataTable table = row.Table;
+                       // fill columns for the row
+                       for (int i = 0; i < table.Columns.Count; i++) {
+                               DataColumn col = table.Columns [i];
+                               string value = row.IsNull (col) ? String.Empty : row [col].ToString ();
+                               switch (col.ColumnMapping) {
+                               case MappingType.Element:
+                                       XmlElement el = CreateElement (col.Prefix, col.ColumnName, col.Namespace);
+                                       el.InnerText = value;
+                                       element.AppendChild (el);
+                                       break;
+                               case MappingType.Attribute:
+                                       XmlAttribute attr = CreateAttribute (col.Prefix, col.ColumnName, col.Namespace);
+                                       attr.Value = value;
+                                       element.SetAttributeNode (attr);
+                                       break;
+                               case MappingType.SimpleContent:
+                                       XmlText text = CreateTextNode (value);
+                                       element.AppendChild (text);
+                                       break;
+                               // FIXME: how to handle hidden?
+                               }
                        }
                }
 
                // Rollback
-               [MonoTODO]
+               [MonoTODO ("It does not look complete.")]
                private void OnDataTableRowRollback (DataRowChangeEventArgs args)
                {
-                       DataRow row = args.Row;
-                       
-                       int rowid = dataRowIDList.IndexOf (row.XmlRowID);
-                       
-                       // find right element in xmldocument
-                       XmlNode node = GetElementsByTagName (row.Table.TableName) [rowid];
-
-                       int rowValue = 0;
-                       for (int i = 0; i < node.ChildNodes.Count; i++) {
-
-                               XmlNode child = node.ChildNodes [i];
-                               if (child.NodeType != XmlNodeType.Whitespace)                           
-                                       child.InnerText = (string)row [rowValue++];
-                               
+                       if (!raiseDataSetEvents)
+                               return;
+                       bool escapedRaiseDocumentEvents = raiseDocumentEvents;
+                       raiseDocumentEvents = false;
+
+                       try {
+                               DataRow r = args.Row;
+                               XmlElement el = GetElementFromRow (r);
+                               if (el == null)
+                                       return;
+                               DataTable tab = r.Table;
+                               ArrayList al = new ArrayList ();
+                               foreach (XmlAttribute attr in el.Attributes) {
+                                       DataColumn col = tab.Columns [attr.LocalName];
+                                       if (col != null) {
+                                               if (r.IsNull (col))
+                                                       // should be removed
+                                                       al.Add (attr);
+                                               else
+                                                       attr.Value = r [col].ToString ();
+                                       }
+                               }
+                               foreach (XmlAttribute attr in al)
+                                       el.RemoveAttributeNode (attr);
+                               al.Clear ();
+                               foreach (XmlNode child in el.ChildNodes) {
+                                       if (child.NodeType == XmlNodeType.Element) {
+                                               DataColumn col = tab.Columns [child.LocalName];
+                                               if (col != null) {
+                                                       if (r.IsNull (col))
+                                                               al.Add (child);
+                                                       else
+                                                               child.InnerText = r [col].ToString ();
+                                               }
+                                       }
+                               }
+                               foreach (XmlNode n in al)
+                                       el.RemoveChild (n);
+                       } finally {
+                               raiseDocumentEvents = escapedRaiseDocumentEvents;
                        }
                }
 
                #endregion // DataSet event handlers
 
                #region Private methods
+               private void InitDelegateFields ()
+               {
+                       columnChanged = new DataColumnChangeEventHandler (OnDataTableColumnChanged);
+                       rowDeleted = new DataRowChangeEventHandler (OnDataTableRowDeleted);
+                       rowChanged = new DataRowChangeEventHandler (OnDataTableRowChanged);
+                       tablesChanged = new CollectionChangeEventHandler (OnDataTableChanged);
+               }
+               
+               private void RemoveXmlDocumentListeners ()
+               {
+                       this.NodeInserting -= new XmlNodeChangedEventHandler (OnNodeInserting);
+                       this.NodeInserted -= new XmlNodeChangedEventHandler (OnNodeInserted);
+                       this.NodeChanging -= new XmlNodeChangedEventHandler (OnNodeChanging);
+                       this.NodeChanged -= new XmlNodeChangedEventHandler (OnNodeChanged);
+                       this.NodeRemoving -= new XmlNodeChangedEventHandler (OnNodeRemoving);
+                       this.NodeRemoved -= new XmlNodeChangedEventHandler (OnNodeRemoved);
+               }
 
-               [MonoTODO]
-               private void LoadRow (XmlReader reader, ref DataRow row)
-               {                       
-                       // dt.Rows.Add (LoadRow (reader, dt.NewRow ()));
-                       // This method returns DataRow filled by values
-                       // from xmldocument
-                       string rowname = reader.Name;
-                       string column = "";
-                       
-                       if (reader.NodeType == XmlNodeType.Element)
-                               column = reader.Name;
-                       
-                       reader.Read ();
-                       
-                       if (reader.NodeType == XmlNodeType.Text) {
-                               
-                               string val = reader.Value;
-                               if (row.Table.Columns.Contains (column))
-                                       row [column] = val;
+               private void AddXmlDocumentListeners ()
+               {
+                       this.NodeInserting += new XmlNodeChangedEventHandler (OnNodeInserting);
+                       this.NodeInserted += new XmlNodeChangedEventHandler (OnNodeInserted);
+                       this.NodeChanging += new XmlNodeChangedEventHandler (OnNodeChanging);
+                       this.NodeChanged += new XmlNodeChangedEventHandler (OnNodeChanged);
+                       this.NodeRemoving += new XmlNodeChangedEventHandler (OnNodeRemoving);
+                       this.NodeRemoved += new XmlNodeChangedEventHandler (OnNodeRemoved);
+               }
+
+               internal static object StringToObject (Type type, string value)
+               {
+                       if (type == null) return value;
+
+                       switch (Type.GetTypeCode (type)) {
+                               case TypeCode.Boolean: return XmlConvert.ToBoolean (value);
+                               case TypeCode.Byte: return XmlConvert.ToByte (value);
+                               case TypeCode.Char: return (char)XmlConvert.ToInt32 (value);
+                               case TypeCode.DateTime: return XmlConvert.ToDateTime (value);
+                               case TypeCode.Decimal: return XmlConvert.ToDecimal (value);
+                               case TypeCode.Double: return XmlConvert.ToDouble (value);
+                               case TypeCode.Int16: return XmlConvert.ToInt16 (value);
+                               case TypeCode.Int32: return XmlConvert.ToInt32 (value);
+                               case TypeCode.Int64: return XmlConvert.ToInt64 (value);
+                               case TypeCode.SByte: return XmlConvert.ToSByte (value);
+                               case TypeCode.Single: return XmlConvert.ToSingle (value);
+                               case TypeCode.UInt16: return XmlConvert.ToUInt16 (value);
+                               case TypeCode.UInt32: return XmlConvert.ToUInt32 (value);
+                               case TypeCode.UInt64: return XmlConvert.ToUInt64 (value);
                        }
+
+                       if (type == typeof (TimeSpan)) return XmlConvert.ToTimeSpan (value);
+                       if (type == typeof (Guid)) return XmlConvert.ToGuid (value);
+                       if (type == typeof (byte[])) return Convert.FromBase64String (value);
+
+                       return Convert.ChangeType (value, type);
                }
-               
                #endregion // Private methods
        }
 }