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 // Read DataSet in as document if there is data in tables
67 bool HaveRows = false;
68 foreach (DataTable T in dataSet.Tables) {
70 if (T.Rows.Count > 0) {
78 XmlReader xmlReader = new XmlTextReader (new StringReader (dataSet.GetXml ()));
80 // Load DataSet's xml-data
81 base.Load (xmlReader);
85 foreach (DataTable Table in DataSet.Tables) {
87 foreach (DataRow Row in Table.Rows) {
88 Row.XmlRowID = dataRowID;
89 dataRowIDList.Add (dataRowID);
94 this.NodeChanged += new XmlNodeChangedEventHandler (OnNodeChanged);
95 this.NodeChanging += new XmlNodeChangedEventHandler (OnNodeChanging);
96 this.NodeInserting += new XmlNodeChangedEventHandler (OnNodeInserting);
97 this.NodeRemoved += new XmlNodeChangedEventHandler (OnNodeRemoved);
98 this.NodeInserted += new XmlNodeChangedEventHandler (OnNodeInserted);
100 foreach (DataTable Table in dataSet.Tables) {
101 Table.ColumnChanged += new DataColumnChangeEventHandler (OnDataTableColumnChanged);
102 Table.RowDeleted += new DataRowChangeEventHandler (OnDataTableRowDeleted);
103 Table.RowChanged += new DataRowChangeEventHandler (OnDataTableRowChanged);
107 // bool clone. If we are cloning XmlDataDocument then clone should be true.
108 private XmlDataDocument (DataSet dataset, bool clone)
110 this.dataSet = dataset;
112 foreach (DataTable Table in DataSet.Tables) {
114 foreach (DataRow Row in Table.Rows) {
115 Row.XmlRowID = dataRowID;
116 dataRowIDList.Add (dataRowID);
121 this.NodeChanged += new XmlNodeChangedEventHandler (OnNodeChanged);
122 this.NodeChanging += new XmlNodeChangedEventHandler (OnNodeChanging);
123 this.NodeInserting += new XmlNodeChangedEventHandler (OnNodeInserting);
124 this.NodeRemoved += new XmlNodeChangedEventHandler (OnNodeRemoved);
125 this.NodeInserted += new XmlNodeChangedEventHandler (OnNodeInserted);
127 foreach (DataTable Table in dataSet.Tables) {
128 Table.ColumnChanged += new DataColumnChangeEventHandler (OnDataTableColumnChanged);
129 Table.RowDeleted += new DataRowChangeEventHandler (OnDataTableRowDeleted);
130 Table.RowChanged += new DataRowChangeEventHandler (OnDataTableRowChanged);
134 #endregion // Constructors
136 #region Public Properties
138 public override string BaseURI {
141 // TODO: why are we overriding?
146 public DataSet DataSet {
152 // override inheritted method from XmlDocument
153 public override string InnerXml {
154 [MonoTODO("override???")]
156 return base.InnerXml;
161 base.InnerXml = value;
165 public override bool IsReadOnly {
166 [MonoTODO("override???")]
174 public override XmlElement this[string name] {
175 [MonoTODO("override???")]
182 public override XmlElement this[string localname, string ns] {
183 [MonoTODO("override???")]
185 return base [localname, ns];
189 public override string LocalName {
190 [MonoTODO("override???")]
192 return base.LocalName;
196 public override string Name {
197 [MonoTODO("override??")]
203 public override XmlDocument OwnerDocument {
209 #endregion // Public Properties
211 #region Public Methods
214 public override XmlNode CloneNode(bool deep)
216 XmlDataDocument Document;
218 Document = new XmlDataDocument (DataSet.Copy (), true);
220 Document = new XmlDataDocument (DataSet.Clone (), true);
222 RemoveXmlDocumentListeners ();
224 Document.PreserveWhitespace = PreserveWhitespace;
226 foreach(XmlNode n in ChildNodes)
227 Document.AppendChild (Document.ImportNode (n, deep));
230 AddXmlDocumentListeners ();
235 #region overloaded CreateElement methods
237 [MonoTODO ("why this is override?")]
238 public override XmlElement CreateElement(string prefix,
239 string localName, string namespaceURI)
241 if ((localName == null) || (localName == String.Empty))
242 throw new ArgumentException ("The local name for elements or attributes cannot be null" +
243 "or an empty string.");
244 string pref = prefix != null ? prefix : String.Empty;
245 return base.CreateElement (pref, localName, namespaceURI != null ? namespaceURI : String.Empty);
248 #endregion // overloaded CreateElement Methods
250 // will not be supported
251 public override XmlEntityReference CreateEntityReference(string name)
253 throw new NotSupportedException();
256 // will not be supported
257 public override XmlElement GetElementById(string elemId)
259 throw new NotSupportedException();
262 // get the XmlElement associated with the DataRow
263 [MonoTODO ("Exceptions")]
264 public XmlElement GetElementFromRow(DataRow r)
266 if (r.XmlRowID == 0) // datarow was not in xmldatadocument
267 throw new Exception ();
269 int elementRow = dataRowIDList.IndexOf (r.XmlRowID);
271 return (XmlElement)GetElementsByTagName (r.Table.TableName) [elementRow];
274 // get the DataRow associated with the XmlElement
275 [MonoTODO ("Exceptions")]
276 public DataRow GetRowFromElement(XmlElement e)
282 XPathNavigator nodeNavigator = node.CreateNavigator ();
283 int c = GetElementsByTagName (node.Name).Count;
288 XmlNodeList nodeList = GetElementsByTagName (node.Name);
293 while (i < c && !isSame) {
295 XPathNavigator docNavigator = nodeList [i].CreateNavigator ();
296 isSame = docNavigator.IsSamePosition (nodeNavigator);
297 docNavigator = nodeList [i].CreateNavigator ();
305 if (i >= dataRowIDList.Count)
308 // now we know rownum
309 int xmlrowid = (int)dataRowIDList [i];
313 DataTable dt = DataSet.Tables [node.Name];
319 foreach (DataRow r in dt.Rows) {
320 if (xmlrowid == r.XmlRowID) {
328 #region overload Load methods
330 public override void Load(Stream inStream) {
331 Load (new XmlTextReader (inStream));
334 public override void Load(string filename) {
335 Load (new XmlTextReader (filename));
338 public override void Load(TextReader txtReader) {
339 Load (new XmlTextReader (txtReader));
342 public override void Load(XmlReader reader) {
344 bool OldEC = DataSet.EnforceConstraints;
345 DataSet.EnforceConstraints = false;
347 dataSet.Tables.CollectionChanged -= new CollectionChangeEventHandler (OnDataTableChanged);
349 // For reading xml to XmlDocument
350 XmlTextReader textReader = new XmlTextReader (
353 // dont listen these events
354 RemoveXmlDocumentListeners ();
358 if (reader.NodeType != XmlNodeType.Element)
359 reader.MoveToContent ();
361 // read to next element
362 while (reader.Read () && reader.NodeType != XmlNodeType.Element);
365 // Find right table from tablecollection
366 if (DataSet.Tables.Contains (reader.LocalName)) {
368 dt = DataSet.Tables [reader.LocalName];
370 // Make sure event handlers are not added twice
371 dt.ColumnChanged -= new DataColumnChangeEventHandler (OnDataTableColumnChanged);
372 dt.ColumnChanged += new DataColumnChangeEventHandler (OnDataTableColumnChanged);
374 dt.RowDeleted -= new DataRowChangeEventHandler (OnDataTableRowDeleted);
375 dt.RowDeleted += new DataRowChangeEventHandler (OnDataTableRowDeleted);
377 dt.RowChanged -= new DataRowChangeEventHandler (OnDataTableRowChanged);
378 dt.RowChanged += new DataRowChangeEventHandler (OnDataTableRowChanged);
383 // Read rows to table
384 DataRow tempRow = dt.NewRow ();
385 while ((reader.NodeType != XmlNodeType.EndElement ||
386 reader.Name != dt.TableName) && reader.Read()) {
388 switch (reader.NodeType) {
390 case XmlNodeType.Element:
391 // Add column to DataRow
392 LoadRow (reader, ref tempRow);
399 // Every row must have unique id.
400 tempRow.XmlRowID = dataRowID;
401 dataRowIDList.Add (dataRowID);
402 dt.Rows.Add (tempRow);
406 } while (reader.Read ());
408 base.Load (textReader);
411 DataSet.EnforceConstraints = OldEC;
412 AddXmlDocumentListeners ();
413 dataSet.Tables.CollectionChanged += new CollectionChangeEventHandler (OnDataTableChanged);
416 #endregion // overloaded Load methods
419 public override void WriteContentTo(XmlWriter xw) {
420 base.WriteContentTo (xw);
424 public override void WriteTo(XmlWriter w) {
428 #endregion // Public Methods
430 #region Protected Methods
432 //FIXME: when internal protected bug is fixed uncomment this
434 //protected internal override XPathNavigator CreateNavigator(XmlNode node) {
435 // throw new NotImplementedException();
438 #endregion // Protected Methods
440 #region XmlDocument event handlers
442 private void OnNodeChanging (object sender, XmlNodeChangedEventArgs args)
444 if (DataSet.EnforceConstraints)
445 throw new InvalidOperationException (Locale.GetText ("Please set DataSet.EnforceConstraints == false " +
446 "before trying to edit XmlDataDocument using " +
450 // Invoked when XmlNode is changed colum is changed
452 private void OnNodeChanged (object sender, XmlNodeChangedEventArgs args)
455 if (args.Node == null)
458 DataRow row = GetRowFromElement ((XmlElement)args.Node.ParentNode.ParentNode);
463 if (!row.Table.Columns.Contains (args.Node.ParentNode.Name))
466 row.Table.ColumnChanged -= new DataColumnChangeEventHandler (OnDataTableColumnChanged);
468 if (row [args.Node.ParentNode.Name].ToString () != args.Node.InnerText)
469 row [args.Node.ParentNode.Name] = args.Node.InnerText;
471 row.Table.ColumnChanged += new DataColumnChangeEventHandler (OnDataTableColumnChanged);
474 // Invoked when XmlNode is removed
476 private void OnNodeRemoved (object sender, XmlNodeChangedEventArgs args)
478 if (args.OldParent == null)
481 if (!(args.OldParent is XmlElement))
484 DataRow row = GetRowFromElement ((XmlElement)args.OldParent);
489 // Dont trig event again
490 row.Table.ColumnChanged -= new DataColumnChangeEventHandler (OnDataTableColumnChanged);
491 row [args.Node.Name] = null;
492 row.Table.ColumnChanged += new DataColumnChangeEventHandler (OnDataTableColumnChanged);
495 private void OnNodeInserting (object sender, XmlNodeChangedEventArgs args)
497 if (DataSet.EnforceConstraints)
498 throw new InvalidOperationException (Locale.GetText ("Please set DataSet.EnforceConstraints == false " +
499 "before trying to edit XmlDataDocument using " +
504 private void OnNodeInserted (object sender, XmlNodeChangedEventArgs args)
507 // this is table element
508 if (DataSet.Tables.Contains (args.NewParent.Name)) {
511 if (TempTable.ContainsKey (args.NewParent.Name)) {
513 // if TempTable contains table name, get it and remove it from hashtable
514 // so we can later add it :)
515 ht = TempTable [args.NewParent.Name] as Hashtable;
516 TempTable.Remove (args.NewParent.Name);
519 ht = new Hashtable ();
521 ht.Add (args.Node.Name, args.Node.InnerText);
522 TempTable.Add (args.NewParent.Name, ht);
524 else if (DataSet.Tables.Contains (args.Node.Name)) {
526 // if nodes name is same as some table in the list is is time to
527 // add row to datatable
529 DataTable dt = DataSet.Tables [args.Node.Name];
530 dt.RowChanged -= new DataRowChangeEventHandler (OnDataTableRowChanged);
532 DataRow row = dt.NewRow ();
533 Hashtable ht = TempTable [args.Node.Name] as Hashtable;
535 IDictionaryEnumerator enumerator = ht.GetEnumerator ();
536 while (enumerator.MoveNext ()) {
537 if (dt.Columns.Contains (enumerator.Key.ToString ()))
538 row [enumerator.Key.ToString ()] = enumerator.Value.ToString ();
541 DataSet.Tables [args.Node.Name].Rows.Add (row);
542 dt.RowChanged += new DataRowChangeEventHandler (OnDataTableRowChanged);
547 #endregion // DataSet event handlers
549 #region DataSet event handlers
551 // If DataTable is added or removed from DataSet
552 private void OnDataTableChanged (object sender, CollectionChangeEventArgs eventArgs)
554 DataTable Table = (DataTable)eventArgs.Element;
555 if (eventArgs.Action == CollectionChangeAction.Add) {
556 Table.ColumnChanged += new DataColumnChangeEventHandler (OnDataTableColumnChanged);
557 Table.RowDeleted += new DataRowChangeEventHandler (OnDataTableRowDeleted);
558 Table.RowChanged += new DataRowChangeEventHandler (OnDataTableRowChanged);
562 // If column has changed
564 private void OnDataTableColumnChanged(object sender,
565 DataColumnChangeEventArgs eventArgs)
567 RemoveXmlDocumentListeners ();
569 // row is not yet in datatable
570 if (eventArgs.Row.XmlRowID == 0)
573 // TODO: Here should be some kind of error checking.
574 GetElementsByTagName (eventArgs.Column.ColumnName) [dataRowIDList.IndexOf (
575 eventArgs.Row.XmlRowID)].InnerText = eventArgs.ProposedValue.ToString ();
577 AddXmlDocumentListeners ();
581 private void OnDataTableRowDeleted(object sender,
582 DataRowChangeEventArgs eventArgs)
585 DataRow deletedRow = null;
586 deletedRow = eventArgs.Row;
588 if (eventArgs.Row.XmlRowID == 0)
591 int rowIndex = dataRowIDList.IndexOf (eventArgs.Row.XmlRowID);
592 if (rowIndex == -1 || eventArgs.Row.XmlRowID == 0 ||
593 rowIndex > GetElementsByTagName (deletedRow.Table.TableName).Count - 1)
596 // Remove element from xmldocument and row indexlist
597 // FIXME: this is one way to do this, but i hope someday i find out much better way.
598 XmlNode p = GetElementsByTagName (deletedRow.Table.TableName) [rowIndex].ParentNode;
600 p.RemoveChild (GetElementsByTagName (deletedRow.Table.TableName) [rowIndex]);
601 dataRowIDList.RemoveAt (rowIndex);
606 private void OnDataTableRowChanged(object sender, DataRowChangeEventArgs eventArgs)
608 switch (eventArgs.Action) {
610 case DataRowAction.Delete:
611 OnDataTableRowDeleted (sender, eventArgs);
614 case DataRowAction.Add:
615 OnDataTableRowAdded (eventArgs);
618 case DataRowAction.Rollback:
619 OnDataTableRowRollback (eventArgs);
628 private void OnDataTableRowAdded (DataRowChangeEventArgs args)
630 RemoveXmlDocumentListeners ();
632 // If XmlRowID is != 0 then it is already added
633 if (args.Row.XmlRowID != 0)
636 // Create row element. Row's name same as TableName
637 DataRow row = args.Row;
638 row.XmlRowID = dataRowID;
639 dataRowIDList.Add (dataRowID);
642 if (DocumentElement == null)
643 this.AppendChild (CreateElement (DataSet.DataSetName));
645 XmlElement element = CreateElement (args.Row.Table.TableName);
646 DocumentElement.AppendChild (element);
648 XmlElement rowElement = null;
650 for (int i = 0; i < row.Table.Columns.Count; i++) {
652 rowElement = CreateElement (row.Table.Columns [i].ColumnName);
653 rowElement.InnerText = (string)row [i];
654 element.AppendChild (rowElement);
657 AddXmlDocumentListeners ();
662 private void OnDataTableRowRollback (DataRowChangeEventArgs args)
664 RemoveXmlDocumentListeners ();
666 DataRow row = args.Row;
667 int rowid = dataRowIDList.IndexOf (row.XmlRowID);
669 // find right element in xmldocument
670 if (rowid == 0 || rowid >= GetElementsByTagName (row.Table.TableName).Count)
673 XmlNode node = GetElementsByTagName (row.Table.TableName) [rowid];
676 for (int i = 0; i < node.ChildNodes.Count; i++) {
678 XmlNode child = node.ChildNodes [i];
679 if (child.NodeType != XmlNodeType.Whitespace) {
680 child.InnerText = (string)row [rowValue++];
684 AddXmlDocumentListeners ();
687 #endregion // DataSet event handlers
689 #region Private methods
692 private void LoadRow (XmlReader reader, ref DataRow row)
694 // dt.Rows.Add (LoadRow (reader, dt.NewRow ()));
695 // This method returns DataRow filled by values
697 string rowname = reader.Name;
700 if (reader.NodeType == XmlNodeType.Element)
701 column = reader.Name;
705 if (reader.NodeType == XmlNodeType.Text) {
707 string val = reader.Value;
708 if (row.Table.Columns.Contains (column))
713 private void RemoveXmlDocumentListeners ()
715 this.NodeInserting -= new XmlNodeChangedEventHandler (OnNodeInserting);
716 this.NodeInserted -= new XmlNodeChangedEventHandler (OnNodeInserted);
717 this.NodeChanged -= new XmlNodeChangedEventHandler (OnNodeChanged);
718 this.NodeChanging -= new XmlNodeChangedEventHandler (OnNodeChanging);
721 private void AddXmlDocumentListeners ()
723 this.NodeInserting += new XmlNodeChangedEventHandler (OnNodeInserting);
724 this.NodeInserted += new XmlNodeChangedEventHandler (OnNodeInserted);
725 this.NodeChanged += new XmlNodeChangedEventHandler (OnNodeChanged);
726 this.NodeChanging += new XmlNodeChangedEventHandler (OnNodeChanging);
728 #endregion // Private methods