2004-03-15 Umadevi S (sumadevi@novell.com)
[mono.git] / mcs / class / System.Data / System.Xml / XmlDataDocument.cs
1 //
2 // mcs/class/System.Data/System.Xml/XmlDataDocument.cs
3 //
4 // Purpose: Provides a W3C XML DOM Document to interact with
5 //          relational data in a DataSet
6 //
7 // class: XmlDataDocument
8 // assembly: System.Data.dll
9 // namespace: System.Xml
10 //
11 // Author:
12 //     Daniel Morgan <danmorg@sc.rr.com>
13 //     Ville Palo <vi64pa@koti.soon.fi>
14 //
15 // (c)copyright 2002 Daniel Morgan
16 // (c)copyright 2003 Ville Palo
17 //
18 // XmlDataDocument is included within the Mono Class Library.
19 //
20
21 using System;
22 using System.Data;
23 using System.IO;
24 using System.Text;
25 using System.Xml.XPath;
26 using System.Collections;
27 using System.Globalization;
28 using System.ComponentModel;
29
30 namespace System.Xml {
31
32         public class XmlDataDocument : XmlDocument {
33
34                 #region Fields
35
36                 private DataSet dataSet;
37                 private bool isReadOnly = false;
38
39                 private int dataRowID = 1;
40                 private ArrayList dataRowIDList = new ArrayList ();
41
42                 // this is needed for inserting new row to datatable via xml
43                 private Hashtable TempTable = new Hashtable ();
44
45                 #endregion // Fields
46
47                 #region Constructors
48
49                 public XmlDataDocument() {
50
51                         dataSet = new DataSet();
52                         dataSet.Tables.CollectionChanged += new CollectionChangeEventHandler (OnDataTableChanged);
53                         
54                         this.NodeChanged += new XmlNodeChangedEventHandler (OnNodeChanged);
55                         this.NodeChanging += new XmlNodeChangedEventHandler (OnNodeChanging);
56                         this.NodeInserting += new XmlNodeChangedEventHandler (OnNodeInserting);
57                         this.NodeRemoved += new XmlNodeChangedEventHandler (OnNodeRemoved);
58                         this.NodeInserted += new XmlNodeChangedEventHandler (OnNodeInserted);
59                         DataSet.EnforceConstraints = false;
60                 }
61
62                 public XmlDataDocument(DataSet dataset) {
63
64                         this.dataSet = dataset;
65
66                         XmlReader xmlReader = new XmlTextReader (new StringReader (dataSet.GetXml ()));
67
68                         // Load DataSet's xml-data
69                         base.Load (xmlReader);
70                         xmlReader.Close ();
71
72                         foreach (DataTable Table in DataSet.Tables) {
73                                 
74                                 foreach (DataRow Row in Table.Rows) {
75                                         Row.XmlRowID = dataRowID;
76                                         dataRowIDList.Add (dataRowID);
77                                         dataRowID++;
78                                 }
79                         }
80                                                 
81                         this.NodeChanged += new XmlNodeChangedEventHandler (OnNodeChanged);
82                         this.NodeChanging += new XmlNodeChangedEventHandler (OnNodeChanging);
83                         this.NodeInserting += new XmlNodeChangedEventHandler (OnNodeInserting);
84                         this.NodeRemoved += new XmlNodeChangedEventHandler (OnNodeRemoved);
85                         this.NodeInserted += new XmlNodeChangedEventHandler (OnNodeInserted);
86
87                         foreach (DataTable Table in dataSet.Tables) {
88                                 Table.ColumnChanged += new DataColumnChangeEventHandler (OnDataTableColumnChanged);
89                                 Table.RowDeleted += new DataRowChangeEventHandler (OnDataTableRowDeleted);
90                                 Table.RowChanged += new DataRowChangeEventHandler (OnDataTableRowChanged);
91                         }
92                 }
93
94                 // bool clone. If we are cloning XmlDataDocument then clone should be true.
95                 private XmlDataDocument (DataSet dataset, bool clone)
96                 {
97                         this.dataSet = dataset;
98
99                         foreach (DataTable Table in DataSet.Tables) {
100                                 
101                                 foreach (DataRow Row in Table.Rows) {
102                                         Row.XmlRowID = dataRowID;
103                                         dataRowIDList.Add (dataRowID);
104                                         dataRowID++;
105                                 }
106                         }
107                                                 
108                         this.NodeChanged += new XmlNodeChangedEventHandler (OnNodeChanged);
109                         this.NodeChanging += new XmlNodeChangedEventHandler (OnNodeChanging);           
110                         this.NodeInserting += new XmlNodeChangedEventHandler (OnNodeInserting);
111                         this.NodeRemoved += new XmlNodeChangedEventHandler (OnNodeRemoved);
112                         this.NodeInserted += new XmlNodeChangedEventHandler (OnNodeInserted);
113
114                         foreach (DataTable Table in dataSet.Tables) {
115                                 Table.ColumnChanged += new DataColumnChangeEventHandler (OnDataTableColumnChanged);
116                                 Table.RowDeleted += new DataRowChangeEventHandler (OnDataTableRowDeleted);
117                                 Table.RowChanged += new DataRowChangeEventHandler (OnDataTableRowChanged);
118                         }
119                 }
120
121                 #endregion // Constructors
122
123                 #region Public Properties
124
125                 public DataSet DataSet {
126                         get {
127                                 return dataSet;
128                         }
129                 }
130
131                 #endregion // Public Properties
132
133                 #region Public Methods
134
135                 [MonoTODO]
136                 public override XmlNode CloneNode(bool deep) 
137                 {
138                         XmlDataDocument Document;
139                         if (deep)
140                                 Document = new XmlDataDocument (DataSet.Copy (), true);
141                         else
142                                 Document = new XmlDataDocument (DataSet.Clone (), true);
143
144                         RemoveXmlDocumentListeners ();
145
146                         Document.PreserveWhitespace = PreserveWhitespace;
147                         if (deep) {
148                                 foreach(XmlNode n in ChildNodes)
149                                         Document.AppendChild (Document.ImportNode (n, deep));
150                         }
151
152                         AddXmlDocumentListeners ();
153
154                         return Document;                        
155                 }
156
157                 #region overloaded CreateElement methods
158
159                 public override XmlElement CreateElement(
160                         string prefix, string localName, string namespaceURI) 
161                 {
162                         return base.CreateElement (prefix, localName, namespaceURI);
163                 }
164
165                 #endregion // overloaded CreateElement Methods
166                         
167                 // will not be supported
168                 public override XmlEntityReference CreateEntityReference(string name) 
169                 {
170                         throw new NotSupportedException();
171                 }
172                 
173                 // will not be supported
174                 public override XmlElement GetElementById(string elemId) 
175                 {
176                         throw new NotSupportedException();
177                 }
178
179                 // get the XmlElement associated with the DataRow
180                 [MonoTODO ("Exceptions")]
181                 public XmlElement GetElementFromRow(DataRow r) 
182                 {
183                         if (r.XmlRowID == 0) // datarow was not in xmldatadocument
184                                 throw new Exception ();
185
186                         int elementRow = dataRowIDList.IndexOf (r.XmlRowID);
187                         
188                         return (XmlElement)GetElementsByTagName (r.Table.TableName) [elementRow];
189                 }
190
191                 // get the DataRow associated with the XmlElement
192                 [MonoTODO ("Exceptions")]
193                 public DataRow GetRowFromElement(XmlElement e)
194                 {
195                         XmlElement node = e;
196                         if (node == null)
197                                 return null;
198
199                         XPathNavigator nodeNavigator = node.CreateNavigator ();
200                         int c  = GetElementsByTagName (node.Name).Count;
201                         
202                         if (c == 0)
203                                 return null;
204
205                         XmlNodeList nodeList = GetElementsByTagName (node.Name);
206
207                         int i = 0;
208                         bool isSame = false;
209
210                         while (i < c && !isSame) {
211
212                                 XPathNavigator docNavigator = nodeList [i].CreateNavigator ();
213                                 isSame = docNavigator.IsSamePosition (nodeNavigator);
214                                 docNavigator = nodeList [i].CreateNavigator ();
215                                 if (!isSame)
216                                         i++;
217                         }
218
219                         if (!isSame)
220                                 return null;
221
222                         if (i >= dataRowIDList.Count)
223                                 return null;
224
225                         // now we know rownum                   
226                         int xmlrowid = (int)dataRowIDList [i];
227                         if (xmlrowid <= 0)
228                                 return null;
229
230                         DataTable dt = DataSet.Tables [node.Name];
231                         DataRow row = null;
232
233                         if (dt == null)
234                                 return null;
235
236                         foreach (DataRow r in dt.Rows) {
237                                 if (xmlrowid == r.XmlRowID) {
238                                         row = r;
239                                 }
240                         }
241
242                         return row;                     
243                 }
244
245                 #region overload Load methods
246
247                 public override void Load(Stream inStream) {
248                         Load (new XmlTextReader (inStream));
249                 }
250
251                 public override void Load(string filename) {
252                         Load (new XmlTextReader (filename));
253                 }
254
255                 public override void Load(TextReader txtReader) {
256                         Load (new XmlTextReader (txtReader));
257                 }
258
259                 public override void Load(XmlReader reader) {
260
261                         bool OldEC = DataSet.EnforceConstraints;
262                         DataSet.EnforceConstraints = false;
263
264                         dataSet.Tables.CollectionChanged -= new CollectionChangeEventHandler (OnDataTableChanged);
265
266                         // For reading xml to XmlDocument
267                         XmlTextReader textReader = new XmlTextReader (
268                                 reader.BaseURI);
269                         
270                         // dont listen these events
271                         RemoveXmlDocumentListeners ();
272                         DataTable dt = null;
273
274
275                         if (reader.NodeType != XmlNodeType.Element)
276                                 reader.MoveToContent ();
277
278                         // read to next element
279                         while (reader.Read () && reader.NodeType != XmlNodeType.Element);
280
281                         do {
282                                 // Find right table from tablecollection
283                                 if (DataSet.Tables.Contains (reader.LocalName)) {
284
285                                         dt = DataSet.Tables [reader.LocalName];
286
287                                         // Make sure event handlers are not added twice
288                                         dt.ColumnChanged -= new DataColumnChangeEventHandler (OnDataTableColumnChanged);
289                                         dt.ColumnChanged += new DataColumnChangeEventHandler (OnDataTableColumnChanged);
290
291                                         dt.RowDeleted -= new DataRowChangeEventHandler (OnDataTableRowDeleted);
292                                         dt.RowDeleted += new DataRowChangeEventHandler (OnDataTableRowDeleted);
293                                         
294                                         dt.RowChanged -= new DataRowChangeEventHandler (OnDataTableRowChanged);
295                                         dt.RowChanged += new DataRowChangeEventHandler (OnDataTableRowChanged);
296                                 }
297                                 else
298                                         continue;
299
300                                 // Read rows to table
301                                 DataRow tempRow = dt.NewRow ();
302                                 while ((reader.NodeType != XmlNodeType.EndElement ||
303                                         reader.Name != dt.TableName) && reader.Read()) {
304                                         
305                                         switch (reader.NodeType) {
306                                                 
307                                         case XmlNodeType.Element:
308                                                 // Add column to DataRow
309                                                 LoadRow (reader, ref tempRow);
310                                                 break;
311                                         default:
312                                                 break;
313                                         }                       
314                                 }
315
316                                 // Every row must have unique id.
317                                 tempRow.XmlRowID = dataRowID;
318                                 dataRowIDList.Add (dataRowID);
319                                 dt.Rows.Add (tempRow);
320                                 dataRowID++;                                    
321                                 
322                                 
323                         } while (reader.Read ());
324
325                         base.Load (textReader);
326                         textReader.Close ();
327
328                         DataSet.EnforceConstraints = OldEC;
329                         AddXmlDocumentListeners ();
330                         dataSet.Tables.CollectionChanged += new CollectionChangeEventHandler (OnDataTableChanged);
331                 }
332                 
333                 #endregion // overloaded Load methods
334                 #endregion // Public Methods
335
336                 #region Protected Methods
337
338                 //FIXME: when internal protected bug is fixed uncomment this
339                 //[MonoTODO]
340                 //protected internal override XPathNavigator CreateNavigator(XmlNode node) {
341                 //      throw new NotImplementedException();
342                 //}
343
344                 #endregion // Protected Methods
345                 
346                 #region XmlDocument event handlers
347
348                 private void OnNodeChanging (object sender, XmlNodeChangedEventArgs args)
349                 {
350                         if (DataSet.EnforceConstraints) 
351                                 throw new InvalidOperationException (Locale.GetText ("Please set DataSet.EnforceConstraints == false " +
352                                                                                      "before trying to edit XmlDataDocument using " +
353                                                                                      "XML operations."));                       
354                 }
355
356                 // Invoked when XmlNode is changed colum is changed
357                 [MonoTODO]
358                 private void OnNodeChanged (object sender, XmlNodeChangedEventArgs args)
359                 {
360
361                         if (args.Node == null)
362                                 return;
363
364                         DataRow row = GetRowFromElement ((XmlElement)args.Node.ParentNode.ParentNode);
365
366                         if (row == null)
367                                 return;
368
369                         if (!row.Table.Columns.Contains (args.Node.ParentNode.Name))
370                                 return;
371
372                         row.Table.ColumnChanged -= new DataColumnChangeEventHandler (OnDataTableColumnChanged);
373
374                         if (row [args.Node.ParentNode.Name].ToString () != args.Node.InnerText)         
375                                 row [args.Node.ParentNode.Name] = args.Node.InnerText;          
376
377                         row.Table.ColumnChanged += new DataColumnChangeEventHandler (OnDataTableColumnChanged);
378                 }
379
380                 // Invoked when XmlNode is removed
381                 [MonoTODO]
382                 private void OnNodeRemoved (object sender, XmlNodeChangedEventArgs args)
383                 {
384                         if (args.OldParent == null)
385                                 return;
386
387                         if (!(args.OldParent is XmlElement))
388                                 return;
389                         
390                         DataRow row = GetRowFromElement ((XmlElement)args.OldParent);
391                         
392                         if (row == null)
393                                 return ;
394
395                         // Dont trig event again
396                         row.Table.ColumnChanged -= new DataColumnChangeEventHandler (OnDataTableColumnChanged);
397                         row [args.Node.Name] = null;
398                         row.Table.ColumnChanged += new DataColumnChangeEventHandler (OnDataTableColumnChanged);
399                 }
400
401                 private void OnNodeInserting (object sender, XmlNodeChangedEventArgs args) 
402                 {
403                         if (DataSet.EnforceConstraints) 
404                                 throw new InvalidOperationException (Locale.GetText ("Please set DataSet.EnforceConstraints == false " +
405                                                                                      "before trying to edit XmlDataDocument using " +
406                                                                                      "XML operations."));
407                         
408                 }
409                 
410                 private void OnNodeInserted (object sender, XmlNodeChangedEventArgs args)
411                 {
412
413                         // this is table element 
414                         if (DataSet.Tables.Contains (args.NewParent.Name)) {
415
416                                 Hashtable ht = null;
417                                 if (TempTable.ContainsKey (args.NewParent.Name)) {
418
419                                         // if TempTable contains table name, get it and remove it from hashtable
420                                         // so we can later add it :)
421                                         ht = TempTable [args.NewParent.Name] as Hashtable;
422                                         TempTable.Remove (args.NewParent.Name);
423                                 }
424                                 else 
425                                         ht = new Hashtable ();
426
427                                 ht.Add (args.Node.Name, args.Node.InnerText);                           
428                                 TempTable.Add (args.NewParent.Name, ht);
429                         } 
430                         else if (DataSet.Tables.Contains (args.Node.Name)) {
431                                 
432                                 // if nodes name is same as some table in the list is is time to 
433                                 // add row to datatable
434
435                                 DataTable dt = DataSet.Tables [args.Node.Name];
436                                 dt.RowChanged -= new DataRowChangeEventHandler (OnDataTableRowChanged);
437
438                                 DataRow row = dt.NewRow ();
439                                 Hashtable ht = TempTable [args.Node.Name] as Hashtable;
440                                 
441                                 IDictionaryEnumerator enumerator = ht.GetEnumerator ();
442                                 while (enumerator.MoveNext ()) {
443                                         if (dt.Columns.Contains (enumerator.Key.ToString ()))
444                                                 row [enumerator.Key.ToString ()] = enumerator.Value.ToString ();
445                                 }
446                                 
447                                 DataSet.Tables [args.Node.Name].Rows.Add (row);
448                                 dt.RowChanged += new DataRowChangeEventHandler (OnDataTableRowChanged);
449                         } 
450
451                 }
452
453                 #endregion // DataSet event handlers
454
455                 #region DataSet event handlers
456
457                 // If DataTable is added or removed from DataSet
458                 private void OnDataTableChanged (object sender, CollectionChangeEventArgs eventArgs)
459                 {
460                         DataTable Table = (DataTable)eventArgs.Element;
461                         if (eventArgs.Action == CollectionChangeAction.Add) {
462                                 Table.ColumnChanged += new DataColumnChangeEventHandler (OnDataTableColumnChanged);
463                                 Table.RowDeleted += new DataRowChangeEventHandler (OnDataTableRowDeleted);
464                                 Table.RowChanged += new DataRowChangeEventHandler (OnDataTableRowChanged);
465                         }
466                 }
467
468                 // If column has changed 
469                 [MonoTODO]                      
470                 private void OnDataTableColumnChanged(object sender, 
471                                                              DataColumnChangeEventArgs eventArgs)
472                 {
473                         RemoveXmlDocumentListeners ();
474
475                         // row is not yet in datatable
476                         if (eventArgs.Row.XmlRowID == 0)
477                                 return;
478
479                         // TODO: Here should be some kind of error checking.
480                         GetElementsByTagName (eventArgs.Column.ColumnName) [dataRowIDList.IndexOf (
481                                 eventArgs.Row.XmlRowID)].InnerText = eventArgs.ProposedValue.ToString ();
482                         
483                         AddXmlDocumentListeners ();
484                 }
485         
486                 [MonoTODO]
487                 private void OnDataTableRowDeleted(object sender,
488                                                           DataRowChangeEventArgs eventArgs)
489                 {
490
491                         DataRow deletedRow = null;
492                         deletedRow = eventArgs.Row;
493
494                         if (eventArgs.Row.XmlRowID == 0)
495                                 return;
496                         
497                         int rowIndex = dataRowIDList.IndexOf (eventArgs.Row.XmlRowID);
498                         if (rowIndex == -1 || eventArgs.Row.XmlRowID == 0 || 
499                             rowIndex > GetElementsByTagName (deletedRow.Table.TableName).Count - 1)
500                                 return;
501                         
502                         // Remove element from xmldocument and row indexlist
503                         // FIXME: this is one way to do this, but i hope someday i find out much better way.
504                         XmlNode p = GetElementsByTagName (deletedRow.Table.TableName) [rowIndex].ParentNode;
505                         if (p != null) {
506                                 p.RemoveChild (GetElementsByTagName (deletedRow.Table.TableName) [rowIndex]);
507                                 dataRowIDList.RemoveAt (rowIndex);
508                         }
509                 }
510                 
511                 [MonoTODO]
512                 private void OnDataTableRowChanged(object sender, DataRowChangeEventArgs eventArgs)
513                 {
514                         switch (eventArgs.Action) {
515
516                                 case DataRowAction.Delete:
517                                         OnDataTableRowDeleted (sender, eventArgs);
518                                         break;
519
520                                 case DataRowAction.Add:
521                                         OnDataTableRowAdded (eventArgs);
522                                         break;
523
524                                 case DataRowAction.Rollback:
525                                         OnDataTableRowRollback (eventArgs);
526                                         break;
527                                 default:
528                                         break;
529                         } 
530                 }
531
532                 // Added
533                 [MonoTODO]
534                 private void OnDataTableRowAdded (DataRowChangeEventArgs args)
535                 {
536                         RemoveXmlDocumentListeners ();
537
538                         // If XmlRowID is != 0 then it is already added
539                         if (args.Row.XmlRowID != 0)
540                                 return;
541                         
542                         // Create row element. Row's name same as TableName                                     
543                         DataRow row = args.Row;
544                         row.XmlRowID = dataRowID;
545                         dataRowIDList.Add (dataRowID);
546                         dataRowID++;
547
548                         if (DocumentElement == null)
549                                 this.AppendChild (CreateElement (DataSet.DataSetName));
550
551                         XmlElement element = CreateElement (args.Row.Table.TableName);
552                         DocumentElement.AppendChild (element);
553
554                         XmlElement rowElement = null;
555
556                         for (int i = 0; i < row.Table.Columns.Count; i++) {
557
558                                 rowElement = CreateElement (row.Table.Columns [i].ColumnName);
559                                 rowElement.InnerText = (string)row [i];
560                                 element.AppendChild (rowElement);
561                         }
562                         
563                         AddXmlDocumentListeners ();
564                 }
565
566                 // Rollback
567                 [MonoTODO]
568                 private void OnDataTableRowRollback (DataRowChangeEventArgs args)
569                 {
570                         RemoveXmlDocumentListeners ();
571
572                         DataRow row = args.Row;                 
573                         int rowid = dataRowIDList.IndexOf (row.XmlRowID);
574
575                         // find right element in xmldocument
576                         if (rowid == 0 || rowid >= GetElementsByTagName (row.Table.TableName).Count)
577                                 return;
578
579                         XmlNode node = GetElementsByTagName (row.Table.TableName) [rowid];
580                         
581                         int rowValue = 0;
582                         for (int i = 0; i < node.ChildNodes.Count; i++) {
583                                 
584                                 XmlNode child = node.ChildNodes [i];
585                                 if (child.NodeType != XmlNodeType.Whitespace) {
586                                         child.InnerText = (string)row [rowValue++];
587                                 }
588                         }
589
590                         AddXmlDocumentListeners ();
591                 }
592
593                 #endregion // DataSet event handlers
594
595                 #region Private methods
596
597                 [MonoTODO]
598                 private void LoadRow (XmlReader reader, ref DataRow row)
599                 {                       
600                         // dt.Rows.Add (LoadRow (reader, dt.NewRow ()));
601                         // This method returns DataRow filled by values
602                         // from xmldocument
603                         string rowname = reader.Name;
604                         string column = "";
605                         
606                         if (reader.NodeType == XmlNodeType.Element)
607                                 column = reader.Name;
608                         
609                         reader.Read ();
610                         
611                         if (reader.NodeType == XmlNodeType.Text) {
612                                 
613                                 string val = reader.Value;
614                                 if (row.Table.Columns.Contains (column))
615                                         row [column] = val;
616                         }
617                 }
618                 
619                 private void RemoveXmlDocumentListeners ()
620                 {
621                         this.NodeInserting -= new XmlNodeChangedEventHandler (OnNodeInserting);
622                         this.NodeInserted -= new XmlNodeChangedEventHandler (OnNodeInserted);
623                         this.NodeChanged -= new XmlNodeChangedEventHandler (OnNodeChanged);
624                         this.NodeChanging -= new XmlNodeChangedEventHandler (OnNodeChanging);
625                 }
626
627                 private void AddXmlDocumentListeners ()
628                 {
629                         this.NodeInserting += new XmlNodeChangedEventHandler (OnNodeInserting);
630                         this.NodeInserted += new XmlNodeChangedEventHandler (OnNodeInserted);
631                         this.NodeChanged += new XmlNodeChangedEventHandler (OnNodeChanged);
632                         this.NodeChanging += new XmlNodeChangedEventHandler (OnNodeChanging);
633                 }
634                 #endregion // Private methods
635         }
636 }
637