Mercurial > pub > bltoolkit
diff Source/EditableObjects/EditableXmlDocument.cs @ 0:f990fcb411a9
Копия текущей версии из github
author | cin |
---|---|
date | Thu, 27 Mar 2014 21:46:09 +0400 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Source/EditableObjects/EditableXmlDocument.cs Thu Mar 27 21:46:09 2014 +0400 @@ -0,0 +1,267 @@ +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 + + } +}