2 // mcs/class/System.Data/System.Xml/XmlDataDocument.cs
4 // Purpose: Provides a W3C XML DOM Document to interact with
5 // relational data in a DataSet
7 // class: XmlDataDocument
8 // assembly: System.Data.dll
9 // namespace: System.Xml
12 // Daniel Morgan <danmorg@sc.rr.com>
13 // Ville Palo <vi64pa@koti.soon.fi>
15 // (c)copyright 2002 Daniel Morgan
16 // (c)copyright 2003 Ville Palo
18 // XmlDataDocument is included within the Mono Class Library.
25 using System.Xml.XPath;
26 using System.Collections;
27 using System.Globalization;
28 using System.ComponentModel;
30 namespace System.Xml {
32 public class XmlDataDocument : XmlDocument {
36 private DataSet dataSet;
37 private bool isReadOnly = false;
39 private int dataRowID = 1;
40 private ArrayList dataRowIDList = new ArrayList ();
42 // this is needed for inserting new row to datatable via xml
43 private Hashtable TempTable = new Hashtable ();
49 public XmlDataDocument() {
51 dataSet = new DataSet();
52 dataSet.Tables.CollectionChanged += new CollectionChangeEventHandler (OnDataTableChanged);
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;
62 public XmlDataDocument(DataSet dataset) {
64 this.dataSet = dataset;
66 XmlReader xmlReader = new XmlTextReader (new StringReader (dataSet.GetXml ()));
68 // Load DataSet's xml-data
69 base.Load (xmlReader);
72 foreach (DataTable Table in DataSet.Tables) {
74 foreach (DataRow Row in Table.Rows) {
75 Row.XmlRowID = dataRowID;
76 dataRowIDList.Add (dataRowID);
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);
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);
94 // bool clone. If we are cloning XmlDataDocument then clone should be true.
95 private XmlDataDocument (DataSet dataset, bool clone)
97 this.dataSet = dataset;
99 foreach (DataTable Table in DataSet.Tables) {
101 foreach (DataRow Row in Table.Rows) {
102 Row.XmlRowID = dataRowID;
103 dataRowIDList.Add (dataRowID);
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);
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);
121 #endregion // Constructors
123 #region Public Properties
125 public DataSet DataSet {
131 #endregion // Public Properties
133 #region Public Methods
136 public override XmlNode CloneNode(bool deep)
138 XmlDataDocument Document;
140 Document = new XmlDataDocument (DataSet.Copy (), true);
142 Document = new XmlDataDocument (DataSet.Clone (), true);
144 RemoveXmlDocumentListeners ();
146 Document.PreserveWhitespace = PreserveWhitespace;
148 foreach(XmlNode n in ChildNodes)
149 Document.AppendChild (Document.ImportNode (n, deep));
152 AddXmlDocumentListeners ();
157 #region overloaded CreateElement methods
159 public override XmlElement CreateElement(
160 string prefix, string localName, string namespaceURI)
162 return base.CreateElement (prefix, localName, namespaceURI);
165 #endregion // overloaded CreateElement Methods
167 // will not be supported
168 public override XmlEntityReference CreateEntityReference(string name)
170 throw new NotSupportedException();
173 // will not be supported
174 public override XmlElement GetElementById(string elemId)
176 throw new NotSupportedException();
179 // get the XmlElement associated with the DataRow
180 [MonoTODO ("Exceptions")]
181 public XmlElement GetElementFromRow(DataRow r)
183 if (r.XmlRowID == 0) // datarow was not in xmldatadocument
184 throw new Exception ();
186 int elementRow = dataRowIDList.IndexOf (r.XmlRowID);
188 return (XmlElement)GetElementsByTagName (r.Table.TableName) [elementRow];
191 // get the DataRow associated with the XmlElement
192 [MonoTODO ("Exceptions")]
193 public DataRow GetRowFromElement(XmlElement e)
199 XPathNavigator nodeNavigator = node.CreateNavigator ();
200 int c = GetElementsByTagName (node.Name).Count;
205 XmlNodeList nodeList = GetElementsByTagName (node.Name);
210 while (i < c && !isSame) {
212 XPathNavigator docNavigator = nodeList [i].CreateNavigator ();
213 isSame = docNavigator.IsSamePosition (nodeNavigator);
214 docNavigator = nodeList [i].CreateNavigator ();
222 if (i >= dataRowIDList.Count)
225 // now we know rownum
226 int xmlrowid = (int)dataRowIDList [i];
230 DataTable dt = DataSet.Tables [node.Name];
236 foreach (DataRow r in dt.Rows) {
237 if (xmlrowid == r.XmlRowID) {
245 #region overload Load methods
247 public override void Load(Stream inStream) {
248 Load (new XmlTextReader (inStream));
251 public override void Load(string filename) {
252 Load (new XmlTextReader (filename));
255 public override void Load(TextReader txtReader) {
256 Load (new XmlTextReader (txtReader));
259 public override void Load(XmlReader reader) {
261 bool OldEC = DataSet.EnforceConstraints;
262 DataSet.EnforceConstraints = false;
264 dataSet.Tables.CollectionChanged -= new CollectionChangeEventHandler (OnDataTableChanged);
266 // For reading xml to XmlDocument
267 XmlTextReader textReader = new XmlTextReader (
270 // dont listen these events
271 RemoveXmlDocumentListeners ();
275 if (reader.NodeType != XmlNodeType.Element)
276 reader.MoveToContent ();
278 // read to next element
279 while (reader.Read () && reader.NodeType != XmlNodeType.Element);
282 // Find right table from tablecollection
283 if (DataSet.Tables.Contains (reader.LocalName)) {
285 dt = DataSet.Tables [reader.LocalName];
287 // Make sure event handlers are not added twice
288 dt.ColumnChanged -= new DataColumnChangeEventHandler (OnDataTableColumnChanged);
289 dt.ColumnChanged += new DataColumnChangeEventHandler (OnDataTableColumnChanged);
291 dt.RowDeleted -= new DataRowChangeEventHandler (OnDataTableRowDeleted);
292 dt.RowDeleted += new DataRowChangeEventHandler (OnDataTableRowDeleted);
294 dt.RowChanged -= new DataRowChangeEventHandler (OnDataTableRowChanged);
295 dt.RowChanged += new DataRowChangeEventHandler (OnDataTableRowChanged);
300 // Read rows to table
301 DataRow tempRow = dt.NewRow ();
302 while ((reader.NodeType != XmlNodeType.EndElement ||
303 reader.Name != dt.TableName) && reader.Read()) {
305 switch (reader.NodeType) {
307 case XmlNodeType.Element:
308 // Add column to DataRow
309 LoadRow (reader, ref tempRow);
316 // Every row must have unique id.
317 tempRow.XmlRowID = dataRowID;
318 dataRowIDList.Add (dataRowID);
319 dt.Rows.Add (tempRow);
323 } while (reader.Read ());
325 base.Load (textReader);
328 DataSet.EnforceConstraints = OldEC;
329 AddXmlDocumentListeners ();
330 dataSet.Tables.CollectionChanged += new CollectionChangeEventHandler (OnDataTableChanged);
333 #endregion // overloaded Load methods
334 #endregion // Public Methods
336 #region Protected Methods
338 //FIXME: when internal protected bug is fixed uncomment this
340 //protected internal override XPathNavigator CreateNavigator(XmlNode node) {
341 // throw new NotImplementedException();
344 #endregion // Protected Methods
346 #region XmlDocument event handlers
348 private void OnNodeChanging (object sender, XmlNodeChangedEventArgs args)
350 if (DataSet.EnforceConstraints)
351 throw new InvalidOperationException (Locale.GetText ("Please set DataSet.EnforceConstraints == false " +
352 "before trying to edit XmlDataDocument using " +
356 // Invoked when XmlNode is changed colum is changed
358 private void OnNodeChanged (object sender, XmlNodeChangedEventArgs args)
361 if (args.Node == null)
364 DataRow row = GetRowFromElement ((XmlElement)args.Node.ParentNode.ParentNode);
369 if (!row.Table.Columns.Contains (args.Node.ParentNode.Name))
372 row.Table.ColumnChanged -= new DataColumnChangeEventHandler (OnDataTableColumnChanged);
374 if (row [args.Node.ParentNode.Name].ToString () != args.Node.InnerText)
375 row [args.Node.ParentNode.Name] = args.Node.InnerText;
377 row.Table.ColumnChanged += new DataColumnChangeEventHandler (OnDataTableColumnChanged);
380 // Invoked when XmlNode is removed
382 private void OnNodeRemoved (object sender, XmlNodeChangedEventArgs args)
384 if (args.OldParent == null)
387 if (!(args.OldParent is XmlElement))
390 DataRow row = GetRowFromElement ((XmlElement)args.OldParent);
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);
401 private void OnNodeInserting (object sender, XmlNodeChangedEventArgs args)
403 if (DataSet.EnforceConstraints)
404 throw new InvalidOperationException (Locale.GetText ("Please set DataSet.EnforceConstraints == false " +
405 "before trying to edit XmlDataDocument using " +
410 private void OnNodeInserted (object sender, XmlNodeChangedEventArgs args)
413 // this is table element
414 if (DataSet.Tables.Contains (args.NewParent.Name)) {
417 if (TempTable.ContainsKey (args.NewParent.Name)) {
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);
425 ht = new Hashtable ();
427 ht.Add (args.Node.Name, args.Node.InnerText);
428 TempTable.Add (args.NewParent.Name, ht);
430 else if (DataSet.Tables.Contains (args.Node.Name)) {
432 // if nodes name is same as some table in the list is is time to
433 // add row to datatable
435 DataTable dt = DataSet.Tables [args.Node.Name];
436 dt.RowChanged -= new DataRowChangeEventHandler (OnDataTableRowChanged);
438 DataRow row = dt.NewRow ();
439 Hashtable ht = TempTable [args.Node.Name] as Hashtable;
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 ();
447 DataSet.Tables [args.Node.Name].Rows.Add (row);
448 dt.RowChanged += new DataRowChangeEventHandler (OnDataTableRowChanged);
453 #endregion // DataSet event handlers
455 #region DataSet event handlers
457 // If DataTable is added or removed from DataSet
458 private void OnDataTableChanged (object sender, CollectionChangeEventArgs eventArgs)
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);
468 // If column has changed
470 private void OnDataTableColumnChanged(object sender,
471 DataColumnChangeEventArgs eventArgs)
473 RemoveXmlDocumentListeners ();
475 // row is not yet in datatable
476 if (eventArgs.Row.XmlRowID == 0)
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 ();
483 AddXmlDocumentListeners ();
487 private void OnDataTableRowDeleted(object sender,
488 DataRowChangeEventArgs eventArgs)
491 DataRow deletedRow = null;
492 deletedRow = eventArgs.Row;
494 if (eventArgs.Row.XmlRowID == 0)
497 int rowIndex = dataRowIDList.IndexOf (eventArgs.Row.XmlRowID);
498 if (rowIndex == -1 || eventArgs.Row.XmlRowID == 0 ||
499 rowIndex > GetElementsByTagName (deletedRow.Table.TableName).Count - 1)
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;
506 p.RemoveChild (GetElementsByTagName (deletedRow.Table.TableName) [rowIndex]);
507 dataRowIDList.RemoveAt (rowIndex);
512 private void OnDataTableRowChanged(object sender, DataRowChangeEventArgs eventArgs)
514 switch (eventArgs.Action) {
516 case DataRowAction.Delete:
517 OnDataTableRowDeleted (sender, eventArgs);
520 case DataRowAction.Add:
521 OnDataTableRowAdded (eventArgs);
524 case DataRowAction.Rollback:
525 OnDataTableRowRollback (eventArgs);
534 private void OnDataTableRowAdded (DataRowChangeEventArgs args)
536 RemoveXmlDocumentListeners ();
538 // If XmlRowID is != 0 then it is already added
539 if (args.Row.XmlRowID != 0)
542 // Create row element. Row's name same as TableName
543 DataRow row = args.Row;
544 row.XmlRowID = dataRowID;
545 dataRowIDList.Add (dataRowID);
548 if (DocumentElement == null)
549 this.AppendChild (CreateElement (DataSet.DataSetName));
551 XmlElement element = CreateElement (args.Row.Table.TableName);
552 DocumentElement.AppendChild (element);
554 XmlElement rowElement = null;
556 for (int i = 0; i < row.Table.Columns.Count; i++) {
558 rowElement = CreateElement (row.Table.Columns [i].ColumnName);
559 rowElement.InnerText = (string)row [i];
560 element.AppendChild (rowElement);
563 AddXmlDocumentListeners ();
568 private void OnDataTableRowRollback (DataRowChangeEventArgs args)
570 RemoveXmlDocumentListeners ();
572 DataRow row = args.Row;
573 int rowid = dataRowIDList.IndexOf (row.XmlRowID);
575 // find right element in xmldocument
576 if (rowid == 0 || rowid >= GetElementsByTagName (row.Table.TableName).Count)
579 XmlNode node = GetElementsByTagName (row.Table.TableName) [rowid];
582 for (int i = 0; i < node.ChildNodes.Count; i++) {
584 XmlNode child = node.ChildNodes [i];
585 if (child.NodeType != XmlNodeType.Whitespace) {
586 child.InnerText = (string)row [rowValue++];
590 AddXmlDocumentListeners ();
593 #endregion // DataSet event handlers
595 #region Private methods
598 private void LoadRow (XmlReader reader, ref DataRow row)
600 // dt.Rows.Add (LoadRow (reader, dt.NewRow ()));
601 // This method returns DataRow filled by values
603 string rowname = reader.Name;
606 if (reader.NodeType == XmlNodeType.Element)
607 column = reader.Name;
611 if (reader.NodeType == XmlNodeType.Text) {
613 string val = reader.Value;
614 if (row.Table.Columns.Contains (column))
619 private void RemoveXmlDocumentListeners ()
621 this.NodeInserting -= new XmlNodeChangedEventHandler (OnNodeInserting);
622 this.NodeInserted -= new XmlNodeChangedEventHandler (OnNodeInserted);
623 this.NodeChanged -= new XmlNodeChangedEventHandler (OnNodeChanged);
624 this.NodeChanging -= new XmlNodeChangedEventHandler (OnNodeChanging);
627 private void AddXmlDocumentListeners ()
629 this.NodeInserting += new XmlNodeChangedEventHandler (OnNodeInserting);
630 this.NodeInserted += new XmlNodeChangedEventHandler (OnNodeInserted);
631 this.NodeChanged += new XmlNodeChangedEventHandler (OnNodeChanged);
632 this.NodeChanging += new XmlNodeChangedEventHandler (OnNodeChanging);
634 #endregion // Private methods