0
|
1 using System;
|
|
2 using System.Collections;
|
|
3 using System.Reflection;
|
|
4 using System.Xml;
|
|
5
|
|
6 using BLToolkit.TypeBuilder;
|
|
7
|
|
8 namespace BLToolkit.EditableObjects
|
|
9 {
|
|
10 [Serializable]
|
|
11 public class EditableXmlDocument: IEditable, ISetParent, IMemberwiseEditable, IPrintDebugState
|
|
12 {
|
|
13 private Stack _changedNodes;
|
|
14 private XmlDocument _original;
|
|
15 private XmlDocument _current;
|
|
16 private IPropertyChanged _parent;
|
|
17 private PropertyInfo _propertyInfo;
|
|
18
|
|
19 public EditableXmlDocument()
|
|
20 : this(new XmlDocument())
|
|
21 {
|
|
22 }
|
|
23
|
|
24 public EditableXmlDocument(XmlDocument value)
|
|
25 {
|
|
26 _changedNodes = null;
|
|
27 _current = value;
|
|
28 _original = value;
|
|
29
|
|
30 StartXmlDocTracking();
|
|
31 }
|
|
32
|
|
33 [GetValue, SetValue]
|
|
34 public XmlDocument Value
|
|
35 {
|
|
36 get { return _current; }
|
|
37 set
|
|
38 {
|
|
39 if (_current == value)
|
|
40 return;
|
|
41
|
|
42 if (_current == _original)
|
|
43 StopXmlDocTracking();
|
|
44
|
|
45 // Drop changes, if any.
|
|
46 //
|
|
47 if (_changedNodes != null)
|
|
48 _changedNodes.Clear();
|
|
49
|
|
50 _current = value;
|
|
51
|
|
52 if (_current == _original)
|
|
53 StartXmlDocTracking();
|
|
54 }
|
|
55 }
|
|
56
|
|
57 private void StartXmlDocTracking()
|
|
58 {
|
|
59 if (_current == null)
|
|
60 return;
|
|
61
|
|
62 _current.NodeInserted += HandleNodeChanged;
|
|
63 _current.NodeRemoved += HandleNodeChanged;
|
|
64 _current.NodeChanged += HandleNodeChanged;
|
|
65 }
|
|
66
|
|
67 private void StopXmlDocTracking()
|
|
68 {
|
|
69 if (_current == null)
|
|
70 return;
|
|
71
|
|
72 _current.NodeInserted -= HandleNodeChanged;
|
|
73 _current.NodeRemoved -= HandleNodeChanged;
|
|
74 _current.NodeChanged -= HandleNodeChanged;
|
|
75 }
|
|
76
|
|
77 private void HandleNodeChanged(object sender, XmlNodeChangedEventArgs ea)
|
|
78 {
|
|
79 if (ea.Action == XmlNodeChangedAction.Change && ea.NewValue == ea.OldValue)
|
|
80 {
|
|
81 // A void change can be ignored.
|
|
82 //
|
|
83 return;
|
|
84 }
|
|
85
|
|
86 if (_changedNodes == null)
|
|
87 _changedNodes = new Stack();
|
|
88
|
|
89 _changedNodes.Push(new XmlNodeTrackBack(ea));
|
|
90
|
|
91 // Propagate changes to parent object, if set.
|
|
92 //
|
|
93 if (_parent != null)
|
|
94 _parent.OnPropertyChanged(_propertyInfo);
|
|
95 }
|
|
96
|
|
97 #region IEditable Members
|
|
98
|
|
99 public void AcceptChanges()
|
|
100 {
|
|
101 if (_original != _current)
|
|
102 {
|
|
103 _original = _current;
|
|
104 StartXmlDocTracking();
|
|
105 }
|
|
106 else
|
|
107 {
|
|
108 // Let them go away.
|
|
109 //
|
|
110 if (_changedNodes != null)
|
|
111 _changedNodes.Clear();
|
|
112 }
|
|
113 }
|
|
114
|
|
115 public void RejectChanges()
|
|
116 {
|
|
117 if (_original != _current)
|
|
118 {
|
|
119 _current = _original;
|
|
120 StartXmlDocTracking();
|
|
121 }
|
|
122 else if (_changedNodes != null && _changedNodes.Count > 0)
|
|
123 {
|
|
124 // Don't fall into an infinite loop.
|
|
125 //
|
|
126 StopXmlDocTracking();
|
|
127
|
|
128 // A Stack enumerates from back to front.
|
|
129 //
|
|
130 foreach (XmlNodeTrackBack nodeTrackBack in _changedNodes)
|
|
131 {
|
|
132 switch (nodeTrackBack.Action)
|
|
133 {
|
|
134 case XmlNodeChangedAction.Insert:
|
|
135 if (nodeTrackBack.Node.NodeType == XmlNodeType.Attribute)
|
|
136 ((XmlElement)nodeTrackBack.Value).Attributes.Remove((XmlAttribute)nodeTrackBack.Node);
|
|
137 else
|
|
138 ((XmlNode)nodeTrackBack.Value).RemoveChild(nodeTrackBack.Node);
|
|
139 break;
|
|
140 case XmlNodeChangedAction.Remove:
|
|
141 // NB: The order of children nodes may change.
|
|
142 //
|
|
143 if (nodeTrackBack.Node.NodeType == XmlNodeType.Attribute)
|
|
144 ((XmlElement)nodeTrackBack.Value).Attributes.Append((XmlAttribute)nodeTrackBack.Node);
|
|
145 else
|
|
146 ((XmlNode)nodeTrackBack.Value).AppendChild(nodeTrackBack.Node);
|
|
147 break;
|
|
148 case XmlNodeChangedAction.Change:
|
|
149 nodeTrackBack.Node.Value = (string)nodeTrackBack.Value;
|
|
150 break;
|
|
151 }
|
|
152 }
|
|
153
|
|
154 _changedNodes.Clear();
|
|
155 StartXmlDocTracking();
|
|
156 }
|
|
157 }
|
|
158
|
|
159 public bool IsDirty
|
|
160 {
|
|
161 get
|
|
162 {
|
|
163 if (_current == _original)
|
|
164 return _changedNodes != null && _changedNodes.Count > 0;
|
|
165
|
|
166 if (_current == null || _original == null)
|
|
167 return true;
|
|
168
|
|
169 return _current.InnerXml.TrimEnd() != _original.InnerXml.TrimEnd();
|
|
170 }
|
|
171 }
|
|
172
|
|
173 #endregion
|
|
174
|
|
175 #region IMemberwiseEditable Members
|
|
176
|
|
177 public bool AcceptMemberChanges(PropertyInfo propertyInfo, string memberName)
|
|
178 {
|
|
179 if (memberName != propertyInfo.Name)
|
|
180 return false;
|
|
181
|
|
182 AcceptChanges();
|
|
183
|
|
184 return true;
|
|
185 }
|
|
186
|
|
187 public bool RejectMemberChanges(PropertyInfo propertyInfo, string memberName)
|
|
188 {
|
|
189 if (memberName != propertyInfo.Name)
|
|
190 return false;
|
|
191
|
|
192 RejectChanges();
|
|
193
|
|
194 return true;
|
|
195 }
|
|
196
|
|
197 public bool IsDirtyMember(PropertyInfo propertyInfo, string memberName, ref bool isDirty)
|
|
198 {
|
|
199 if (memberName != propertyInfo.Name)
|
|
200 return false;
|
|
201
|
|
202 isDirty = IsDirty;
|
|
203
|
|
204 return true;
|
|
205 }
|
|
206
|
|
207 public void GetDirtyMembers(PropertyInfo propertyInfo, ArrayList list)
|
|
208 {
|
|
209 if (IsDirty)
|
|
210 list.Add(propertyInfo);
|
|
211 }
|
|
212
|
|
213 #endregion
|
|
214
|
|
215 #region IPrintDebugState Members
|
|
216
|
|
217 public void PrintDebugState(PropertyInfo propertyInfo, ref string str)
|
|
218 {
|
|
219 str += string.Format("{0,-20} {1} {2,-80}\r\n",
|
|
220 propertyInfo.Name, IsDirty? "*": " ", _current != null? _current.OuterXml: "(null)");
|
|
221 }
|
|
222
|
|
223 #endregion
|
|
224
|
|
225 #region ISetParent Members
|
|
226
|
|
227 public void SetParent(object parent, PropertyInfo propertyInfo)
|
|
228 {
|
|
229 _parent = parent as IPropertyChanged;
|
|
230 _propertyInfo = propertyInfo;
|
|
231 }
|
|
232
|
|
233 #endregion
|
|
234
|
|
235 #region Inner types
|
|
236
|
|
237 private struct XmlNodeTrackBack
|
|
238 {
|
|
239 public readonly XmlNode Node;
|
|
240 public readonly XmlNodeChangedAction Action;
|
|
241 public readonly object Value;
|
|
242
|
|
243 public XmlNodeTrackBack(XmlNodeChangedEventArgs ea)
|
|
244 {
|
|
245 Node = ea.Node;
|
|
246 Action = ea.Action;
|
|
247 switch(ea.Action)
|
|
248 {
|
|
249 case XmlNodeChangedAction.Insert:
|
|
250 Value = ea.NewParent;
|
|
251 break;
|
|
252 case XmlNodeChangedAction.Remove:
|
|
253 Value = ea.OldParent;
|
|
254 break;
|
|
255 case XmlNodeChangedAction.Change:
|
|
256 Value = ea.OldValue;
|
|
257 break;
|
|
258 default:
|
|
259 throw new ArgumentOutOfRangeException("ea", ea.Action, string.Format("Unknown XmlNodeChangedAction"));
|
|
260 }
|
|
261 }
|
|
262 }
|
|
263
|
|
264 #endregion
|
|
265
|
|
266 }
|
|
267 }
|