Business Logic Toolkit for .NET
www.bltoolkit.net
|  Home   |  Download   |  Documentation   |  Discussions   |  License   |

  Source.EditableObjects.EditableXmlDocument.cs

 
using System;
using System.Collections;
using System.Reflection;
using System.Xml;

using BLToolkit.TypeBuilder;

namespace BLToolkit.EditableObjects
{
    [Serializable]
    public class EditableXmlDocument: IEditable, ISetParent, IMemberwiseEditable, IPrintDebugState
    {
        private          Stack                      _changedNodes;
        private          XmlDocument                _original;
        private          XmlDocument                _current;
        private          IPropertyChanged           _parent;
        private          PropertyInfo               _propertyInfo;

        public EditableXmlDocument()
            : this(new XmlDocument())
        {
        }

        public EditableXmlDocument(XmlDocument value)
        {
            _changedNodes = null;
            _current      = value;
            _original     = value;

            StartXmlDocTracking();
        }

        [GetValue, SetValue]
        public XmlDocument Value
        {
            get { return _current; }
            set
            {
                if (_current == value)
                    return;

                if (_current == _original)
                    StopXmlDocTracking();

                // Drop changes, if any.
                //
                if (_changedNodes != null)
                    _changedNodes.Clear();

                _current = value;

                if (_current == _original)
                    StartXmlDocTracking();
            }
        }

        private void StartXmlDocTracking()
        {
            if (_current == null)
                return;

            _current.NodeInserted += HandleNodeChanged;
            _current.NodeRemoved  += HandleNodeChanged;
            _current.NodeChanged  += HandleNodeChanged;
        }

        private void StopXmlDocTracking()
        {
            if (_current == null)
                return;

            _current.NodeInserted -= HandleNodeChanged;
            _current.NodeRemoved  -= HandleNodeChanged;
            _current.NodeChanged  -= HandleNodeChanged;
        }

        private void HandleNodeChanged(object sender, XmlNodeChangedEventArgs ea)
        {
            if (ea.Action == XmlNodeChangedAction.Change && ea.NewValue == ea.OldValue)
            {
                // A void change can be ignored.
                //
                return;
            }

            if (_changedNodes == null)
                _changedNodes = new Stack();

            _changedNodes.Push(new XmlNodeTrackBack(ea));

            // Propagate changes to parent object, if set.
            //
            if (_parent != null)
                _parent.OnPropertyChanged(_propertyInfo);
        }

        #region IEditable Members

        public void AcceptChanges()
        {
            if (_original != _current)
            {
                _original = _current;
                StartXmlDocTracking();
            }
            else
            {
                // Let them go away.
                //
                if (_changedNodes != null)
                    _changedNodes.Clear();
            }
        }

        public void RejectChanges()
        {
            if (_original != _current)
            {
                _current = _original;
                StartXmlDocTracking();
            }
            else if (_changedNodes != null && _changedNodes.Count > 0)
            {
                // Don't fall into an infinite loop.
                //
                StopXmlDocTracking();

                // A Stack enumerates from back to front.
                //
                foreach (XmlNodeTrackBack nodeTrackBack in _changedNodes)
                {
                    switch (nodeTrackBack.Action)
                    {
                        case XmlNodeChangedAction.Insert:
                            if (nodeTrackBack.Node.NodeType == XmlNodeType.Attribute)
                                ((XmlElement)nodeTrackBack.Value).Attributes.Remove((XmlAttribute)nodeTrackBack.Node);
                            else
                                ((XmlNode)nodeTrackBack.Value).RemoveChild(nodeTrackBack.Node);
                            break;
                        case XmlNodeChangedAction.Remove:
                            // NB: The order of children nodes may change.
                            //
                            if (nodeTrackBack.Node.NodeType == XmlNodeType.Attribute)
                                ((XmlElement)nodeTrackBack.Value).Attributes.Append((XmlAttribute)nodeTrackBack.Node);
                            else
                                ((XmlNode)nodeTrackBack.Value).AppendChild(nodeTrackBack.Node);
                            break;
                        case XmlNodeChangedAction.Change:
                            nodeTrackBack.Node.Value = (string)nodeTrackBack.Value;
                            break;
                    }
                }

                _changedNodes.Clear();
                StartXmlDocTracking();
            }
        }

        public bool IsDirty
        {
            get
            {
                if (_current == _original)
                    return _changedNodes != null && _changedNodes.Count > 0;

                if (_current == null || _original == null)
                    return true;

                return _current.InnerXml.TrimEnd() != _original.InnerXml.TrimEnd();
            }
        }

        #endregion

        #region IMemberwiseEditable Members

        public bool AcceptMemberChanges(PropertyInfo propertyInfo, string memberName)
        {
            if (memberName != propertyInfo.Name)
                return false;

            AcceptChanges();

            return true;
        }

        public bool RejectMemberChanges(PropertyInfo propertyInfo, string memberName)
        {
            if (memberName != propertyInfo.Name)
                return false;

            RejectChanges();

            return true;
        }

        public bool IsDirtyMember(PropertyInfo propertyInfo, string memberName, ref bool isDirty)
        {
            if (memberName != propertyInfo.Name)
                return false;

            isDirty = IsDirty;

            return true;
        }

        public void GetDirtyMembers(PropertyInfo propertyInfo, ArrayList list)
        {
            if (IsDirty)
                list.Add(propertyInfo);
        }

        #endregion

        #region IPrintDebugState Members

        public void PrintDebugState(PropertyInfo propertyInfo, ref string str)
        {
            str += string.Format("{0,-20} {1} {2,-80}\r\n",
                propertyInfo.Name, IsDirty? "*": " ", _current != null? _current.OuterXml: "(null)");
        }

        #endregion

        #region ISetParent Members

        public void SetParent(object parent, PropertyInfo propertyInfo)
        {
            _parent       = parent as IPropertyChanged;
            _propertyInfo = propertyInfo;
        }

        #endregion

        #region Inner types

        private struct XmlNodeTrackBack
        {
            public readonly XmlNode              Node;
            public readonly XmlNodeChangedAction Action;
            public readonly object               Value;

            public XmlNodeTrackBack(XmlNodeChangedEventArgs ea)
            {
                Node   = ea.Node;
                Action = ea.Action;
                switch(ea.Action)
                {
                    case XmlNodeChangedAction.Insert:
                        Value = ea.NewParent;
                        break;
                    case XmlNodeChangedAction.Remove:
                        Value = ea.OldParent;
                        break;
                    case XmlNodeChangedAction.Change:
                        Value = ea.OldValue;
                        break;
                    default:
                        throw new ArgumentOutOfRangeException("ea", ea.Action, string.Format("Unknown XmlNodeChangedAction"));
                }
            }
        }

        #endregion

    }
}
 
© 2010 www.bltoolkit.net
support@bltoolkit.net