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>
14 // Atsushi Enomoto <atsushi@ximian.com>
16 // (c)copyright 2002 Daniel Morgan
17 // (c)copyright 2003 Ville Palo
18 // (c)2004 Novell Inc.
20 // XmlDataDocument is included within the Mono Class Library.
27 using System.Xml.XPath;
28 using System.Collections;
29 using System.Globalization;
30 using System.ComponentModel;
35 public class XmlDataDocument : XmlDocument
37 // Should we consider overriding CloneNode() ? By default
38 // base CloneNode() will be invoked and thus no DataRow conflict
39 // would happen, that sounds the best (that means, no mapped
40 // DataRow will be provided).
41 internal class XmlDataElement : XmlElement
45 internal XmlDataElement (DataRow row, string prefix, string localName, string ns, XmlDataDocument doc)
46 : base (prefix, localName, ns, doc)
49 // Embed row ID only when the element is mapped to
52 row.DataElement = this;
53 row.XmlRowID = doc.dataRowID;
54 doc.dataRowIDList.Add (row.XmlRowID);
55 // It should not be done here. The node is detached
56 // dt.Rows.Add (tempRow);
61 internal DataRow DataRow {
68 private DataSet dataSet;
70 private int dataRowID = 1;
71 private ArrayList dataRowIDList = new ArrayList ();
73 // this keeps whether table change events should be handles
74 private bool raiseDataSetEvents = true;
75 private bool raiseDocumentEvents = true;
77 // this is needed for inserting new row to datatable via xml
78 private Hashtable TempTable = new Hashtable ();
80 DataColumnChangeEventHandler columnChanged;
81 DataRowChangeEventHandler rowDeleted;
82 DataRowChangeEventHandler rowChanged;
83 CollectionChangeEventHandler tablesChanged;
88 public XmlDataDocument ()
90 InitDelegateFields ();
92 dataSet = new DataSet();
93 dataSet._xmlDataDocument = this;
94 dataSet.Tables.CollectionChanged += tablesChanged;
96 AddXmlDocumentListeners ();
97 DataSet.EnforceConstraints = false;
100 public XmlDataDocument (DataSet dataset)
103 throw new ArgumentException ("Parameter dataset cannot be null.");
\r
104 if (dataset._xmlDataDocument != null)
\r
105 throw new ArgumentException ("DataSet cannot be associated with two or more XmlDataDocument.");
\r
107 InitDelegateFields ();
109 this.dataSet = dataset;
110 this.dataSet._xmlDataDocument = this;
112 XmlElement docElem = CreateElement (dataSet.Prefix, dataSet.DataSetName, dataSet.Namespace);
113 foreach (DataTable dt in dataSet.Tables) {
114 if (dt.ParentRelations.Count > 0)
115 continue; // don't add them here
116 FillNodeRows (docElem, dt, dt.Rows);
119 // This seems required to avoid Load() error when for
120 // example empty DataSet will be filled on Load().
121 if (docElem.ChildNodes.Count > 0)
122 AppendChild (docElem);
124 foreach (DataTable dt in dataSet.Tables) {
125 dt.ColumnChanged += columnChanged;
126 dt.RowDeleted += rowDeleted;
127 dt.RowChanged += rowChanged;
130 AddXmlDocumentListeners ();
133 // bool clone. If we are cloning XmlDataDocument then clone should be true.
134 // FIXME: shouldn't DataSet be mapped to at most one document??
135 private XmlDataDocument (DataSet dataset, bool clone)
137 InitDelegateFields ();
139 this.dataSet = dataset;
140 this.dataSet._xmlDataDocument = this;
142 foreach (DataTable Table in DataSet.Tables) {
144 foreach (DataRow Row in Table.Rows) {
145 Row.XmlRowID = dataRowID;
146 dataRowIDList.Add (dataRowID);
151 AddXmlDocumentListeners ();
153 foreach (DataTable Table in dataSet.Tables) {
154 Table.ColumnChanged += columnChanged;
155 Table.RowDeleted += rowDeleted;
156 Table.RowChanged += rowChanged;
160 #endregion // Constructors
162 #region Public Properties
164 public DataSet DataSet {
170 #endregion // Public Properties
172 #region Public Methods
174 private void FillNodeRows (XmlElement parent, DataTable dt, ICollection rows)
176 foreach (DataRow dr in dt.Rows) {
177 XmlDataElement el = new XmlDataElement (dr, dt.Prefix, dt.TableName, dt.Namespace, this);
178 for (int i = 0; i < dt.Columns.Count; i++) {
179 DataColumn col = dt.Columns [i];
180 string value = dr.IsNull (col) ? String.Empty : dr [col].ToString ();
181 switch (col.ColumnMapping) {
182 case MappingType.Element:
183 XmlElement cel = CreateElement (col.Prefix, col.ColumnName, col.Namespace);
184 cel.InnerText = value;
185 el.AppendChild (cel);
187 case MappingType.Attribute:
188 XmlAttribute a = CreateAttribute (col.Prefix, col.ColumnName, col.Namespace);
190 el.SetAttributeNode (a);
192 case MappingType.SimpleContent:
193 XmlText t = CreateTextNode (value);
198 foreach (DataRelation rel in dt.ChildRelations)
199 FillNodeRows (el, rel.ChildTable, dr.GetChildRows (rel));
200 parent.AppendChild (el);
205 public override XmlNode CloneNode(bool deep)
207 XmlDataDocument Document;
209 Document = new XmlDataDocument (DataSet.Copy (), true);
211 Document = new XmlDataDocument (DataSet.Clone (), true);
213 Document.RemoveXmlDocumentListeners ();
215 Document.PreserveWhitespace = PreserveWhitespace;
217 foreach(XmlNode n in ChildNodes)
218 Document.AppendChild (Document.ImportNode (n, deep));
221 Document.AddXmlDocumentListeners ();
226 #region overloaded CreateElement methods
228 public override XmlElement CreateElement(
229 string prefix, string localName, string namespaceURI)
231 DataTable dt = DataSet.Tables [localName];
232 DataRow row = dt != null ? dt.NewRow () : null;
234 return GetElementFromRow (row);
236 return base.CreateElement (prefix, localName, namespaceURI);
239 #endregion // overloaded CreateElement Methods
241 // It is not supported in XmlDataDocument
242 public override XmlEntityReference CreateEntityReference(string name)
244 throw new NotSupportedException ();
247 // It is not supported in XmlDataDocument
248 public override XmlElement GetElementById (string elemId)
250 throw new NotSupportedException ();
253 // get the XmlElement associated with the DataRow
254 public XmlElement GetElementFromRow (DataRow r)
256 return r.DataElement;
259 // get the DataRow associated with the XmlElement
260 public DataRow GetRowFromElement (XmlElement e)
262 XmlDataElement el = e as XmlDataElement;
268 #region overload Load methods
270 public override void Load(Stream inStream) {
271 Load (new XmlTextReader (inStream));
274 public override void Load(string filename) {
275 Load (new XmlTextReader (filename));
278 public override void Load(TextReader txtReader) {
279 Load (new XmlTextReader (txtReader));
282 public override void Load (XmlReader reader)
284 if (DocumentElement != null)
285 throw new InvalidOperationException ("XmlDataDocument does not support multi-time loading. New XmlDadaDocument is always required.");
287 bool OldEC = DataSet.EnforceConstraints;
288 DataSet.EnforceConstraints = false;
289 dataSet.Tables.CollectionChanged -= tablesChanged;
293 DataSet.EnforceConstraints = OldEC;
294 dataSet.Tables.CollectionChanged += tablesChanged;
297 #endregion // overloaded Load methods
298 #endregion // Public Methods
300 #region Protected Methods
302 [MonoTODO ("Create optimized XPathNavigator")]
303 protected override XPathNavigator CreateNavigator(XmlNode node) {
304 return base.CreateNavigator (node);
307 #endregion // Protected Methods
309 #region XmlDocument event handlers
311 private void OnNodeChanging (object sender, XmlNodeChangedEventArgs args)
313 if (!this.raiseDocumentEvents)
315 if (DataSet.EnforceConstraints)
316 throw new InvalidOperationException (Locale.GetText ("Please set DataSet.EnforceConstraints == false before trying to edit XmlDataDocument using XML operations."));
319 // Invoked when XmlNode is changed colum is changed
321 private void OnNodeChanged (object sender, XmlNodeChangedEventArgs args)
323 if (!raiseDocumentEvents)
325 bool escapedRaiseDataSetEvents = raiseDataSetEvents;
326 raiseDataSetEvents = false;
329 if (args.Node == null)
332 DataRow row = GetRowFromElement ((XmlElement)args.Node.ParentNode.ParentNode);
337 if (!row.Table.Columns.Contains (args.Node.ParentNode.Name))
340 row.Table.ColumnChanged -= columnChanged;
342 if (row [args.Node.ParentNode.Name].ToString () != args.Node.InnerText)
343 row [args.Node.ParentNode.Name] = args.Node.InnerText;
345 row.Table.ColumnChanged += columnChanged;
347 raiseDataSetEvents = escapedRaiseDataSetEvents;
351 private void OnNodeRemoving (object sender, XmlNodeChangedEventArgs args)
353 if (!this.raiseDocumentEvents)
355 if (DataSet.EnforceConstraints)
356 throw new InvalidOperationException (Locale.GetText ("Please set DataSet.EnforceConstraints == false before trying to edit XmlDataDocument using XML operations."));
360 // Invoked when XmlNode is removed
362 private void OnNodeRemoved (object sender, XmlNodeChangedEventArgs args)
364 if (!raiseDocumentEvents)
366 bool escapedRaiseDataSetEvents = raiseDataSetEvents;
367 raiseDataSetEvents = false;
370 // FIXME: This code is obsolete one.
372 if (args.OldParent == null)
375 if (!(args.OldParent is XmlElement))
378 DataRow row = GetRowFromElement ((XmlElement)args.OldParent);
383 row [args.Node.Name] = null;
385 // FIXME: Should we detach rows and descendants as well?
387 raiseDataSetEvents = escapedRaiseDataSetEvents;
391 private void OnNodeInserting (object sender, XmlNodeChangedEventArgs args)
393 if (!this.raiseDocumentEvents)
395 if (DataSet.EnforceConstraints)
396 throw new InvalidOperationException (Locale.GetText ("Please set DataSet.EnforceConstraints == false before trying to edit XmlDataDocument using XML operations."));
400 private void OnNodeInserted (object sender, XmlNodeChangedEventArgs args)
402 if (!raiseDocumentEvents)
404 bool escapedRaiseDataSetEvents = raiseDataSetEvents;
405 raiseDataSetEvents = false;
407 // If the parent node is mapped to a DataTable, then
408 // add a DataRow and map the parent element to it.
410 // AND If the child node is mapped to a DataTable, then
411 // 1. if it is mapped to a DataTable and relation, add
412 // a new DataRow and map the child element to it.
413 // 2. if it is mapped to a DataColumn, set the column
414 // value of the parent DataRow as the child
417 if (! (args.NewParent is XmlElement)) {
418 // i.e. adding document element
419 foreach (XmlNode table in args.Node.ChildNodes)
420 CheckDescendantRelationship (table);
424 DataRow row = GetRowFromElement (args.NewParent as XmlElement);
426 // That happens only when adding table to existing DocumentElement (aka DataSet element)
427 if (args.NewParent == DocumentElement)
428 CheckDescendantRelationship (args.Node);
432 XmlAttribute attr = args.Node as XmlAttribute;
433 if (attr != null) { // fill attribute value
434 DataColumn col = row.Table.Columns [attr.LocalName];
436 row [col] = args.Node.Value;
438 DataRow childRow = GetRowFromElement (args.Node as XmlElement);
439 if (childRow != null) {
440 // child might be a table row.
441 // I might be impossible to set parent
442 // since either of them might be detached
443 if (childRow.RowState != DataRowState.Detached && row.RowState != DataRowState.Detached) {
444 FillRelationship (row, childRow, args.NewParent, args.Node);
447 // child might be a column
448 DataColumn col = row.Table.Columns [args.Node.LocalName];
450 row [col] = args.Node.InnerText;
454 raiseDataSetEvents = escapedRaiseDataSetEvents;
458 private void CheckDescendantRelationship (XmlNode n)
460 XmlElement el = n as XmlElement;
461 DataRow row = GetRowFromElement (el);
464 row.Table.Rows.Add (row); // attach
465 CheckDescendantRelationship (n, row);
468 private void CheckDescendantRelationship (XmlNode p, DataRow row)
470 foreach (XmlNode n in p.ChildNodes) {
471 XmlElement el = n as XmlElement;
474 DataRow childRow = GetRowFromElement (el);
475 if (childRow == null)
477 childRow.Table.Rows.Add (childRow);
478 FillRelationship (row, childRow, p, el);
482 private void FillRelationship (DataRow row, DataRow childRow, XmlNode parentNode, XmlNode childNode)
484 for (int i = 0; i < childRow.Table.ParentRelations.Count; i++) {
485 DataRelation rel = childRow.Table.ParentRelations [i];
486 if (rel.ParentTable == row.Table) {
487 childRow.SetParentRow (row);
491 CheckDescendantRelationship (childNode, childRow);
493 #endregion // DataSet event handlers
495 #region DataSet event handlers
497 // If DataTable is added or removed from DataSet
498 private void OnDataTableChanged (object sender, CollectionChangeEventArgs eventArgs)
500 if (!raiseDataSetEvents)
502 bool escapedRaiseDocumentEvents = raiseDocumentEvents;
503 raiseDocumentEvents = false;
506 DataTable Table = (DataTable)eventArgs.Element;
507 switch (eventArgs.Action) {
508 case CollectionChangeAction.Add:
509 Table.ColumnChanged += columnChanged;
510 Table.RowDeleted += rowDeleted;
511 Table.RowChanged += rowChanged;
513 case CollectionChangeAction.Remove:
514 Table.ColumnChanged -= columnChanged;
515 Table.RowDeleted -= rowDeleted;
516 Table.RowChanged -= rowChanged;
520 raiseDocumentEvents = escapedRaiseDocumentEvents;
524 // If column has changed
526 private void OnDataTableColumnChanged(object sender,
527 DataColumnChangeEventArgs eventArgs)
529 if (!raiseDataSetEvents)
531 bool escapedRaiseDocumentEvents = raiseDocumentEvents;
532 raiseDocumentEvents = false;
535 DataRow row = eventArgs.Row;
536 XmlElement el = GetElementFromRow (row);
539 DataColumn col = eventArgs.Column;
540 string value = row.IsNull (col) ? String.Empty : row [col].ToString ();
541 switch (col.ColumnMapping) {
542 case MappingType.Attribute:
543 el.SetAttribute (col.ColumnName, col.Namespace, value);
545 case MappingType.SimpleContent:
546 el.InnerText = value;
548 case MappingType.Element:
550 for (int i = 0; i < el.ChildNodes.Count; i++) {
551 XmlElement c = el.ChildNodes [i] as XmlElement;
552 if (c != null && c.LocalName == col.ColumnName && c.NamespaceURI == col.Namespace) {
559 XmlElement cel = CreateElement (col.Prefix, col.ColumnName, col.Namespace);
560 cel.InnerText = value;
561 el.AppendChild (cel);
564 // FIXME: how to handle hidden?
567 raiseDocumentEvents = escapedRaiseDocumentEvents;
572 private void OnDataTableRowDeleted(object sender,
573 DataRowChangeEventArgs eventArgs)
575 if (!raiseDataSetEvents)
577 bool escapedRaiseDocumentEvents = raiseDocumentEvents;
578 raiseDocumentEvents = false;
581 // This code is obsolete XmlDataDocument one
583 DataRow deletedRow = null;
584 deletedRow = eventArgs.Row;
586 if (eventArgs.Row.XmlRowID == 0)
589 int rowIndex = dataRowIDList.IndexOf (eventArgs.Row.XmlRowID);
590 if (rowIndex == -1 || eventArgs.Row.XmlRowID == 0 ||
591 rowIndex > GetElementsByTagName (deletedRow.Table.TableName).Count - 1)
594 // Remove element from xmldocument and row indexlist
595 // FIXME: this is one way to do this, but i hope someday i find out much better way.
596 XmlNode p = GetElementsByTagName (deletedRow.Table.TableName) [rowIndex].ParentNode;
598 p.RemoveChild (GetElementsByTagName (deletedRow.Table.TableName) [rowIndex]);
599 dataRowIDList.RemoveAt (rowIndex);
602 raiseDocumentEvents = escapedRaiseDocumentEvents;
607 private void OnDataTableRowChanged(object sender, DataRowChangeEventArgs eventArgs)
609 if (!raiseDataSetEvents)
611 bool escapedRaiseDocumentEvents = raiseDocumentEvents;
612 raiseDocumentEvents = false;
615 switch (eventArgs.Action) {
617 case DataRowAction.Delete:
618 OnDataTableRowDeleted (sender, eventArgs);
621 case DataRowAction.Add:
622 OnDataTableRowAdded (eventArgs);
625 case DataRowAction.Rollback:
626 OnDataTableRowRollback (eventArgs);
632 raiseDocumentEvents = escapedRaiseDocumentEvents;
638 private void OnDataTableRowAdded (DataRowChangeEventArgs args)
640 if (!raiseDataSetEvents)
642 bool escapedRaiseDocumentEvents = raiseDocumentEvents;
643 raiseDocumentEvents = false;
647 // Create row element. Row's name same as TableName
648 DataRow row = args.Row;
650 // create document element if it does not exist
651 if (DocumentElement == null)
652 this.AppendChild (CreateElement (DataSet.DataSetName));
654 DataTable table= args.Row.Table;
655 XmlElement element = GetElementFromRow (row);
657 element = CreateElement (table.Prefix, table.TableName, table.Namespace);
658 if (element.ParentNode == null) {
659 // parent is not always DocumentElement.
660 XmlElement parent = null;
662 if (table.ParentRelations.Count > 0) {
663 for (int i = 0; i < table.ParentRelations.Count; i++) {
664 DataRelation rel = table.ParentRelations [i];
665 DataRow parentRow = row.GetParentRow (rel);
666 if (parentRow == null)
668 parent = GetElementFromRow (parentRow);
672 // The row might be orphan. In such case, the
673 // element is appended to DocumentElement.
675 parent = DocumentElement;
676 parent.AppendChild (element);
679 raiseDocumentEvents = escapedRaiseDocumentEvents;
683 private void FillNodeChildrenFromRow (DataRow row, XmlElement element)
685 DataTable table = row.Table;
686 // fill columns for the row
687 for (int i = 0; i < table.Columns.Count; i++) {
688 DataColumn col = table.Columns [i];
689 string value = row.IsNull (col) ? String.Empty : row [col].ToString ();
690 switch (col.ColumnMapping) {
691 case MappingType.Element:
692 XmlElement el = CreateElement (col.Prefix, col.ColumnName, col.Namespace);
693 el.InnerText = value;
694 element.AppendChild (el);
696 case MappingType.Attribute:
697 XmlAttribute attr = CreateAttribute (col.Prefix, col.ColumnName, col.Namespace);
699 element.SetAttributeNode (attr);
701 case MappingType.SimpleContent:
702 XmlText text = CreateTextNode (value);
703 element.AppendChild (text);
705 // FIXME: how to handle hidden?
712 private void OnDataTableRowRollback (DataRowChangeEventArgs args)
714 if (!raiseDataSetEvents)
716 bool escapedRaiseDocumentEvents = raiseDocumentEvents;
717 raiseDocumentEvents = false;
720 // This code is obsolete XmlDataDocument one.
722 DataRow row = args.Row;
723 int rowid = dataRowIDList.IndexOf (row.XmlRowID);
725 // find right element in xmldocument
726 if (rowid == 0 || rowid >= GetElementsByTagName (row.Table.TableName).Count)
729 XmlNode node = GetElementsByTagName (row.Table.TableName) [rowid];
732 for (int i = 0; i < node.ChildNodes.Count; i++) {
734 XmlNode child = node.ChildNodes [i];
735 if (child.NodeType != XmlNodeType.Whitespace) {
736 child.InnerText = (string)row [rowValue++];
740 raiseDocumentEvents = escapedRaiseDocumentEvents;
744 #endregion // DataSet event handlers
746 #region Private methods
747 private void InitDelegateFields ()
749 columnChanged = new DataColumnChangeEventHandler (OnDataTableColumnChanged);
750 rowDeleted = new DataRowChangeEventHandler (OnDataTableRowDeleted);
751 rowChanged = new DataRowChangeEventHandler (OnDataTableRowChanged);
752 tablesChanged = new CollectionChangeEventHandler (OnDataTableChanged);
755 private void RemoveXmlDocumentListeners ()
757 this.NodeInserting -= new XmlNodeChangedEventHandler (OnNodeInserting);
758 this.NodeInserted -= new XmlNodeChangedEventHandler (OnNodeInserted);
759 this.NodeChanging -= new XmlNodeChangedEventHandler (OnNodeChanging);
760 this.NodeChanged -= new XmlNodeChangedEventHandler (OnNodeChanged);
761 this.NodeRemoving -= new XmlNodeChangedEventHandler (OnNodeRemoving);
762 this.NodeRemoved -= new XmlNodeChangedEventHandler (OnNodeRemoved);
765 private void AddXmlDocumentListeners ()
767 this.NodeInserting += new XmlNodeChangedEventHandler (OnNodeInserting);
768 this.NodeInserted += new XmlNodeChangedEventHandler (OnNodeInserted);
769 this.NodeChanging += new XmlNodeChangedEventHandler (OnNodeChanging);
770 this.NodeChanged += new XmlNodeChangedEventHandler (OnNodeChanged);
771 this.NodeRemoving += new XmlNodeChangedEventHandler (OnNodeRemoving);
772 this.NodeRemoved += new XmlNodeChangedEventHandler (OnNodeRemoved);
774 #endregion // Private methods