a575963da9
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
821 lines
24 KiB
C#
821 lines
24 KiB
C#
//
|
|
// mcs/class/System.Data/System.Xml/XmlDataDocument.cs
|
|
//
|
|
// Purpose: Provides a W3C XML DOM Document to interact with
|
|
// relational data in a DataSet
|
|
//
|
|
// class: XmlDataDocument
|
|
// assembly: System.Data.dll
|
|
// namespace: System.Xml
|
|
//
|
|
// 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);
|
|
doc.dataRowID++;
|
|
}
|
|
}
|
|
|
|
internal DataRow DataRow {
|
|
get { return row; }
|
|
}
|
|
}
|
|
|
|
#region Fields
|
|
|
|
private DataSet dataSet;
|
|
|
|
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;
|
|
|
|
DataColumnChangeEventHandler columnChanged;
|
|
DataRowChangeEventHandler rowDeleted;
|
|
DataRowChangeEventHandler rowChanged;
|
|
CollectionChangeEventHandler tablesChanged;
|
|
#endregion // Fields
|
|
|
|
#region Constructors
|
|
|
|
public XmlDataDocument ()
|
|
{
|
|
InitDelegateFields ();
|
|
|
|
dataSet = new DataSet();
|
|
dataSet._xmlDataDocument = this;
|
|
dataSet.Tables.CollectionChanged += tablesChanged;
|
|
|
|
AddXmlDocumentListeners ();
|
|
DataSet.EnforceConstraints = false;
|
|
}
|
|
|
|
public XmlDataDocument (DataSet dataset)
|
|
{
|
|
if (dataset == null)
|
|
throw new ArgumentException ("Parameter dataset cannot be null.");
|
|
if (dataset._xmlDataDocument != null)
|
|
throw new ArgumentException ("DataSet cannot be associated with two or more XmlDataDocument.");
|
|
|
|
InitDelegateFields ();
|
|
|
|
this.dataSet = dataset;
|
|
this.dataSet._xmlDataDocument = this;
|
|
|
|
XmlElement docElem = CreateElement (dataSet.Prefix, XmlHelper.Encode (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);
|
|
}
|
|
|
|
// This seems required to avoid Load() error when for
|
|
// example empty DataSet will be filled on Load().
|
|
if (docElem.ChildNodes.Count > 0)
|
|
AppendChild (docElem);
|
|
|
|
foreach (DataTable dt in dataSet.Tables) {
|
|
dt.ColumnChanged += columnChanged;
|
|
dt.RowDeleted += rowDeleted;
|
|
dt.RowChanged += rowChanged;
|
|
}
|
|
|
|
AddXmlDocumentListeners ();
|
|
}
|
|
|
|
// 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 ();
|
|
|
|
this.dataSet = dataset;
|
|
this.dataSet._xmlDataDocument = this;
|
|
|
|
foreach (DataTable Table in DataSet.Tables) {
|
|
|
|
foreach (DataRow Row in Table.Rows) {
|
|
Row.XmlRowID = dataRowID;
|
|
dataRowIDList.Add (dataRowID);
|
|
dataRowID++;
|
|
}
|
|
}
|
|
|
|
AddXmlDocumentListeners ();
|
|
|
|
foreach (DataTable Table in dataSet.Tables) {
|
|
Table.ColumnChanged += columnChanged;
|
|
Table.RowDeleted += rowDeleted;
|
|
Table.RowChanged += rowChanged;
|
|
}
|
|
}
|
|
|
|
#endregion // Constructors
|
|
|
|
#region Public Properties
|
|
|
|
public DataSet DataSet {
|
|
get {
|
|
return dataSet;
|
|
}
|
|
}
|
|
|
|
#endregion // Public Properties
|
|
|
|
#region Public Methods
|
|
|
|
private void FillNodeRows (XmlElement parent, DataTable dt, ICollection rows)
|
|
{
|
|
foreach (DataRow dr in dt.Rows) {
|
|
XmlDataElement el = dr.DataElement;
|
|
FillNodeChildrenFromRow (dr, el);
|
|
|
|
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
|
|
|
|
public override XmlElement CreateElement(
|
|
string prefix, string localName, string namespaceURI)
|
|
{
|
|
DataTable dt = DataSet.Tables [XmlHelper.Decode (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
|
|
|
|
// It is not supported in XmlDataDocument
|
|
public override XmlEntityReference CreateEntityReference(string name)
|
|
{
|
|
throw new NotSupportedException ();
|
|
}
|
|
|
|
// It is not supported in XmlDataDocument
|
|
public override XmlElement GetElementById (string elemId)
|
|
{
|
|
throw new NotSupportedException ();
|
|
}
|
|
|
|
// get the XmlElement associated with the DataRow
|
|
public XmlElement GetElementFromRow (DataRow r)
|
|
{
|
|
return r.DataElement;
|
|
}
|
|
|
|
// get the DataRow associated with the XmlElement
|
|
public DataRow GetRowFromElement (XmlElement e)
|
|
{
|
|
XmlDataElement el = e as XmlDataElement;
|
|
if (el == null)
|
|
return null;
|
|
return el.DataRow;
|
|
}
|
|
|
|
#region overload Load methods
|
|
|
|
public override void Load(Stream inStream) {
|
|
Load (new XmlTextReader (inStream));
|
|
}
|
|
|
|
public override void Load(string filename) {
|
|
Load (new XmlTextReader (filename));
|
|
}
|
|
|
|
public override void Load(TextReader txtReader) {
|
|
Load (new XmlTextReader (txtReader));
|
|
}
|
|
|
|
public override void Load (XmlReader reader)
|
|
{
|
|
if (DocumentElement != null)
|
|
throw new InvalidOperationException ("XmlDataDocument does not support multi-time loading. New XmlDadaDocument is always required.");
|
|
|
|
bool OldEC = DataSet.EnforceConstraints;
|
|
DataSet.EnforceConstraints = false;
|
|
dataSet.Tables.CollectionChanged -= tablesChanged;
|
|
|
|
base.Load (reader);
|
|
|
|
DataSet.EnforceConstraints = OldEC;
|
|
dataSet.Tables.CollectionChanged += tablesChanged;
|
|
}
|
|
|
|
#endregion // overloaded Load methods
|
|
#endregion // Public Methods
|
|
|
|
#region Protected Methods
|
|
|
|
[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 {
|
|
|
|
if (args.Node == null)
|
|
return;
|
|
|
|
DataRow row = GetRowFromElement ((XmlElement)args.Node.ParentNode.ParentNode);
|
|
|
|
if (row == null)
|
|
return;
|
|
|
|
if (!row.Table.Columns.Contains (args.Node.ParentNode.Name))
|
|
return;
|
|
|
|
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;
|
|
|
|
// 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);
|
|
}
|
|
|
|
DataRow row = GetRowFromElement (oldParentElem);
|
|
|
|
if (row == null)
|
|
return ;
|
|
|
|
row [args.Node.Name] = null;
|
|
|
|
} finally {
|
|
raiseDataSetEvents = escapedRaiseDataSetEvents;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
XmlAttribute attr = args.Node as XmlAttribute;
|
|
if (attr != null) { // fill attribute value
|
|
DataColumn col = row.Table.Columns [XmlHelper.Decode (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 [XmlHelper.Decode (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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
private void FillRelationship (DataRow row, DataRow childRow, XmlNode parentNode, XmlNode childNode)
|
|
{
|
|
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
|
|
|
|
#region DataSet event handlers
|
|
|
|
// If DataTable is added or removed from DataSet
|
|
private void OnDataTableChanged (object sender, CollectionChangeEventArgs eventArgs)
|
|
{
|
|
if (!raiseDataSetEvents)
|
|
return;
|
|
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;
|
|
}
|
|
}
|
|
|
|
// If column has changed
|
|
private void OnDataTableColumnChanged (object sender,
|
|
DataColumnChangeEventArgs eventArgs)
|
|
{
|
|
if (!raiseDataSetEvents)
|
|
return;
|
|
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 (XmlHelper.Encode (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 == XmlHelper.Encode (col.ColumnName) && c.NamespaceURI == col.Namespace) {
|
|
exists = true;
|
|
c.InnerText = value;
|
|
break;
|
|
}
|
|
}
|
|
if (!exists) {
|
|
XmlElement cel = CreateElement (col.Prefix, XmlHelper.Encode (col.ColumnName), col.Namespace);
|
|
cel.InnerText = value;
|
|
el.AppendChild (cel);
|
|
}
|
|
break;
|
|
// FIXME: how to handle hidden?
|
|
}
|
|
} finally {
|
|
raiseDocumentEvents = escapedRaiseDocumentEvents;
|
|
}
|
|
}
|
|
|
|
private void OnDataTableRowDeleted (object sender,
|
|
DataRowChangeEventArgs eventArgs)
|
|
{
|
|
if (!raiseDataSetEvents)
|
|
return;
|
|
bool escapedRaiseDocumentEvents = raiseDocumentEvents;
|
|
raiseDocumentEvents = false;
|
|
|
|
try {
|
|
// This code is obsolete XmlDataDocument one
|
|
|
|
XmlElement el = GetElementFromRow (eventArgs.Row);
|
|
if (el == null)
|
|
return;
|
|
|
|
el.ParentNode.RemoveChild (el);
|
|
} finally {
|
|
raiseDocumentEvents = escapedRaiseDocumentEvents;
|
|
}
|
|
}
|
|
|
|
[MonoTODO ("Need to handle hidden columns? - see comments on each private method")]
|
|
private void OnDataTableRowChanged (object sender, DataRowChangeEventArgs eventArgs)
|
|
{
|
|
if (!raiseDataSetEvents)
|
|
return;
|
|
bool escapedRaiseDocumentEvents = raiseDocumentEvents;
|
|
raiseDocumentEvents = false;
|
|
try {
|
|
|
|
switch (eventArgs.Action) {
|
|
|
|
case DataRowAction.Delete:
|
|
OnDataTableRowDeleted (sender, eventArgs);
|
|
break;
|
|
|
|
case DataRowAction.Add:
|
|
OnDataTableRowAdded (eventArgs);
|
|
break;
|
|
|
|
case DataRowAction.Rollback:
|
|
OnDataTableRowRollback (eventArgs);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} finally {
|
|
raiseDocumentEvents = escapedRaiseDocumentEvents;
|
|
}
|
|
}
|
|
|
|
// Added - see FillNodeChildrenFromRow comment
|
|
private void OnDataTableRowAdded (DataRowChangeEventArgs args)
|
|
{
|
|
if (!raiseDataSetEvents)
|
|
return;
|
|
|
|
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 (XmlHelper.Encode (DataSet.DataSetName)));
|
|
|
|
DataTable table= args.Row.Table;
|
|
XmlElement element = GetElementFromRow (row);
|
|
if (element == null)
|
|
element = CreateElement (table.Prefix, XmlHelper.Encode (table.TableName), table.Namespace);
|
|
|
|
if (element.ChildNodes.Count == 0)
|
|
FillNodeChildrenFromRow (row, element);
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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, XmlHelper.Encode (col.ColumnName), col.Namespace);
|
|
el.InnerText = value;
|
|
element.AppendChild (el);
|
|
break;
|
|
case MappingType.Attribute:
|
|
XmlAttribute attr = CreateAttribute (col.Prefix, XmlHelper.Encode (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 ("It does not look complete.")]
|
|
private void OnDataTableRowRollback (DataRowChangeEventArgs args)
|
|
{
|
|
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 [XmlHelper.Decode (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 [XmlHelper.Decode (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);
|
|
}
|
|
|
|
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 (value == null || value == String.Empty)
|
|
return DBNull.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);
|
|
#if NET_2_0
|
|
case TypeCode.DateTime: return XmlConvert.ToDateTime (value, XmlDateTimeSerializationMode.Unspecified);
|
|
#else
|
|
case TypeCode.DateTime: return XmlConvert.ToDateTime (value);
|
|
#endif
|
|
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
|
|
}
|
|
}
|
|
|