AXmlObject ReadSingleObject(IEnumerator <AXmlObject> objStream) { AXmlObject obj = objStream.Current; objStream.MoveNext(); return(obj); }
void RemoveChildrenNotIn(IList <AXmlObject> srcList) { Dictionary <int, AXmlObject> srcChildren = srcList.ToDictionary(i => i.StartOffset); for (int i = 0; i < this.Children.Count;) { AXmlObject child = this.Children[i]; AXmlObject srcChild; if (srcChildren.TryGetValue(child.StartOffset, out srcChild) && child.CanUpdateDataFrom(srcChild)) { // Keep only one item with given offset (we might have several due to deletion) srcChildren.Remove(child.StartOffset); // If contaner that needs updating AXmlContainer childAsContainer = child as AXmlContainer; if (childAsContainer != null && child.LastUpdatedFrom != srcChild) { childAsContainer.RemoveChildrenNotIn(((AXmlContainer)srcChild).Children); } i++; } else { RemoveChild(i); } } }
AXmlObject ReadTextOrElement(IEnumerator <AXmlObject> objStream) { AXmlObject curr = objStream.Current; if (curr is AXmlText || curr is AXmlElement) { return(ReadSingleObject(objStream)); } else { AXmlTag currTag = (AXmlTag)curr; if (currTag == StartTagPlaceholder) { return(ReadElement(objStream)); } else if (currTag.IsStartOrEmptyTag) { return(ReadElement(objStream)); } else { return(ReadSingleObject(objStream)); } } }
void InsertAndUpdateChildrenFrom(IList <AXmlObject> srcList) { for (int i = 0; i < srcList.Count; i++) { // End of our list? if (i == this.Children.Count) { InsertChild(i, srcList[i]); continue; } AXmlObject child = this.Children[i]; AXmlObject srcChild = srcList[i]; if (child.CanUpdateDataFrom(srcChild)) // includes offset test // Does it need updating? { if (child.LastUpdatedFrom != srcChild) { child.UpdateDataFrom(srcChild); AXmlContainer childAsContainer = child as AXmlContainer; if (childAsContainer != null) { childAsContainer.InsertAndUpdateChildrenFrom(((AXmlContainer)srcChild).Children); } } } else { InsertChild(i, srcChild); } } Assert(this.Children.Count == srcList.Count, "List lengths differ after update"); }
/// <summary> Is call to UpdateDataFrom is allowed? </summary> internal bool CanUpdateDataFrom(AXmlObject source) { return (this.GetType() == source.GetType() && this.StartOffset == source.StartOffset && (this.LastUpdatedFrom == source || !this.IsCached)); }
/// <inheritdoc/> internal override bool UpdateDataFrom(AXmlObject source) { if (!base.UpdateDataFrom(source)) { return(false); } AXmlElement src = (AXmlElement)source; // Clear the cache for this - quite expensive attributesAndElements = null; if (this.IsProperlyNested != src.IsProperlyNested || this.HasStartOrEmptyTag != src.HasStartOrEmptyTag || this.HasEndTag != src.HasEndTag) { OnChanging(); this.IsProperlyNested = src.IsProperlyNested; this.HasStartOrEmptyTag = src.HasStartOrEmptyTag; this.HasEndTag = src.HasEndTag; OnChanged(); return(true); } else { return(false); } }
/// <summary> Add object to cache, optionally adding extra memory tracking </summary> public void AddParsedObject(AXmlObject obj, int?maxTouchedLocation) { if (!(obj.Length > 0 || obj is AXmlDocument)) { AXmlParser.Assert(false, string.Format(CultureInfo.InvariantCulture, "Invalid object {0}. It has zero length.", obj)); } // // Expensive check // if (obj is AXmlContainer) { // int objStartOffset = obj.StartOffset; // int objEndOffset = obj.EndOffset; // foreach(AXmlObject child in ((AXmlContainer)obj).Children) { // AXmlParser.Assert(objStartOffset <= child.StartOffset && child.EndOffset <= objEndOffset, "Wrong nesting"); // } // } segments.Add(obj); AddSyntaxErrorsOf(obj); obj.IsCached = true; if (maxTouchedLocation != null) { // location is assumed to be read so the range ends at (location + 1) // For example eg for "a_" it is (0-2) TouchedRange range = new TouchedRange() { StartOffset = obj.StartOffset, EndOffset = maxTouchedLocation.Value + 1, TouchedByObject = obj }; segments.Add(range); AXmlParser.Log("{0} touched range ({1}-{2})", obj, range.StartOffset, range.EndOffset); } }
public void AddSyntaxErrorsOf(AXmlObject obj) { foreach (SyntaxError syntaxError in obj.MySyntaxErrors) { segments.Add(syntaxError); } }
public void RemoveSyntaxErrorsOf(AXmlObject obj) { foreach (SyntaxError syntaxError in obj.MySyntaxErrors) { segments.Remove(syntaxError); } }
internal void OnObjectRemoved(int index, AXmlObject obj) { if (ObjectRemoved != null) { ObjectRemoved(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new AXmlObject[] { obj }.ToList(), index)); } }
/// <summary> Verify that the subtree is consistent. Only in debug build. </summary> /// <remarks> Parent pointers might be null or pointing somewhere else in parse tree </remarks> internal override void DebugCheckConsistency(bool checkParentPointers) { base.DebugCheckConsistency(checkParentPointers); AXmlObject prevChild = null; int myStartOffset = this.StartOffset; int myEndOffset = this.EndOffset; foreach (AXmlObject child in this.Children) { Assert(child.Length != 0, "Empty child"); if (checkParentPointers) { Assert(child.Parent != null, "Null parent reference"); Assert(child.Parent == this, "Inccorect parent reference"); } if (this.Document != null) { Assert(child.Document != null, "Child has null document"); Assert(child.Document == this.Document, "Child is in different document"); } if (this.IsCached) { Assert(child.IsCached, "Child not in cache"); } Assert(myStartOffset <= child.StartOffset && child.EndOffset <= myEndOffset, "Child not within parent text range"); if (prevChild != null) { Assert(prevChild.EndOffset <= child.StartOffset, "Overlaping childs"); } child.DebugCheckConsistency(checkParentPointers); prevChild = child; } }
/// <inheritdoc/> internal override bool UpdateDataFrom(AXmlObject source) { if (!base.UpdateDataFrom(source)) { return(false); } AXmlAttribute src = (AXmlAttribute)source; if (this.Name != src.Name || this.EqualsSign != src.EqualsSign || this.QuotedValue != src.QuotedValue || this.Value != src.Value) { OnChanging(); this.Name = src.Name; this.EqualsSign = src.EqualsSign; this.QuotedValue = src.QuotedValue; this.Value = src.Value; OnChanged(); return(true); } else { return(false); } }
int GetIndentLevel(AXmlObject obj) { int offset = obj.StartOffset - 1; int level = 0; while (true) { if (offset < 0) { break; } char c = input[offset]; if (c == ' ') { level++; } else if (c == '\t') { level += 4; } else if (c == '\r' || c == '\n') { break; } else { return(-1); } offset--; } return(level); }
IEnumerable <AXmlObject> Split(AXmlElement elem) { int myIndention = GetIndentLevel(elem); // Has start tag and no end tag ? (other then empty-element tag) if (elem.HasStartOrEmptyTag && elem.StartTag.IsStartTag && !elem.HasEndTag && myIndention != -1) { int lastAccepted = 0; // Accept start tag while (lastAccepted + 1 < elem.Children.Count) { AXmlObject nextItem = elem.Children[lastAccepted + 1]; if (nextItem is AXmlText) { lastAccepted++; continue; // Accept } else { // Include all more indented items if (GetIndentLevel(nextItem) > myIndention) { lastAccepted++; continue; // Accept } else { break; // Reject } } } // Accepted everything? if (lastAccepted + 1 == elem.Children.Count) { yield return(elem); yield break; } AXmlParser.Log("Splitting {0} - take {1} of {2} nested", elem, lastAccepted, elem.Children.Count - 1); AXmlElement topHalf = new AXmlElement(); topHalf.HasStartOrEmptyTag = elem.HasStartOrEmptyTag; topHalf.HasEndTag = elem.HasEndTag; topHalf.AddChildren(elem.Children.Take(1 + lastAccepted)); // Start tag + nested topHalf.StartOffset = topHalf.FirstChild.StartOffset; topHalf.EndOffset = topHalf.LastChild.EndOffset; TagReader.OnSyntaxError(topHalf, topHalf.LastChild.EndOffset, topHalf.LastChild.EndOffset, "Expected '</{0}>'", topHalf.StartTag.Name); AXmlParser.Log("Constructed {0}", topHalf); trackedSegments.AddParsedObject(topHalf, null); yield return(topHalf); for (int i = lastAccepted + 1; i < elem.Children.Count; i++) { yield return(elem.Children[i]); } } else { yield return(elem); } }
// Only these four methods should be used to modify the collection /// <summary> To be used exlucively by the parser </summary> internal void AddChild(AXmlObject item) { // Childs can be only added to newly parsed items Assert(this.Parent == null, "I have to be new"); Assert(item.IsCached, "Added item must be in cache"); // Do not set parent pointer this.Children.InsertItemAt(this.Children.Count, item); }
internal SyntaxError Clone(AXmlObject newOwner) { return new SyntaxError { Object = newOwner, Message = Message, Tag = Tag, StartOffset = StartOffset, EndOffset = EndOffset, }; }
internal void OnObjectChanged(AXmlObject obj) { if (ObjectChanged != null) { ObjectChanged(this, new AXmlObjectEventArgs() { Object = obj }); } }
internal SyntaxError Clone(AXmlObject newOwner) { return(new SyntaxError { Object = newOwner, Message = Message, Tag = Tag, StartOffset = StartOffset, EndOffset = EndOffset, }); }
public IEnumerable <AXmlObject> GetAncestors() { AXmlObject curr = this.Parent; while (curr != null) { yield return(curr); curr = curr.Parent; } }
/// <summary> Invalidates items, but keeps tracking them </summary> /// <remarks> Can be called redundantly (from range tacking) </remarks> void InvalidateCache(AXmlObject obj, bool includeParents) { if (includeParents) { foreach (AXmlObject parent in FindParents(obj)) { parent.IsCached = false; AXmlParser.Log("Invalidating cached item {0} (it is parent)", parent); } } obj.IsCached = false; AXmlParser.Log("Invalidating cached item {0}", obj); }
/// <summary> /// To be used exclusively by the children update algorithm. /// Insert child and keep links consistent. /// </summary> void InsertChild(int index, AXmlObject item) { AXmlParser.Log("Inserting {0} at index {1}", item, index); Assert(this.Document != null, "Can not insert to dangling object"); Assert(item.Parent != this, "Can not own item twice"); SetParentPointersInTree(item); this.Children.InsertItemAt(index, item); this.Document.OnObjectInserted(index, item); }
IEnumerable <AXmlObject> FindParents(AXmlObject child) { int childStartOffset = child.StartOffset; int childEndOffset = child.EndOffset; foreach (AXmlObject parent in segments.FindSegmentsContaining(child.StartOffset).OfType <AXmlObject>()) { // Parent is anyone wholy containg the child if (parent.StartOffset <= childStartOffset && childEndOffset <= parent.EndOffset && parent != child) { yield return(parent); } } }
public static void OnSyntaxError(AXmlObject obj, int start, int end, string message, params object[] args) { if (end <= start) { end = start + 1; } AXmlParser.Log("Syntax error ({0}-{1}): {2}", start, end, string.Format(message, args)); obj.AddSyntaxError(new SyntaxError() { Object = obj, StartOffset = start, EndOffset = end, Message = string.Format(message, args), }); }
/// <inheritdoc/> internal override bool UpdateDataFrom(AXmlObject source) { if (!base.UpdateDataFrom(source)) return false; AXmlText src = (AXmlText)source; if (this.EscapedValue != src.EscapedValue || this.Value != src.Value) { OnChanging(); this.EscapedValue = src.EscapedValue; this.Value = src.Value; OnChanged(); return true; } else { return false; } }
public static void OnSyntaxError(AXmlObject obj, int start, int end, string message, params object[] args) { if (end <= start) { end = start + 1; } string formattedMessage = string.Format(CultureInfo.InvariantCulture, message, args); AXmlParser.Log("Syntax error ({0}-{1}): {2}", start, end, formattedMessage); obj.AddSyntaxError(new SyntaxError() { Object = obj, StartOffset = start, EndOffset = end, Message = formattedMessage, }); }
/// <summary> Copy all data from the 'source' to this object </summary> /// <remarks> Returns true if any updates were done </remarks> internal virtual bool UpdateDataFrom(AXmlObject source) { Assert(this.GetType() == source.GetType(), "Source has different type"); DebugAssert(this.StartOffset == source.StartOffset, "Source has different StartOffset"); if (this.LastUpdatedFrom == source) { DebugAssert(this.EndOffset == source.EndOffset, "Source has different EndOffset"); return(false); } Assert(!this.IsCached, "Can not update cached item"); Assert(source.IsCached, "Must update from cache"); this.LastUpdatedFrom = source; this.StartOffset = source.StartOffset; // In some cases we are just updating objects of that same // type and position and hoping to be luckily right this.EndOffset = source.EndOffset; // Do not bother comparing - assume changed if non-null if (this.syntaxErrors != null || source.syntaxErrors != null) { // May be called again in derived class - oh, well, does not matter OnChanging(); this.Document.Parser.TrackedSegments.RemoveSyntaxErrorsOf(this); if (source.syntaxErrors == null) { this.syntaxErrors = null; } else { this.syntaxErrors = new List <SyntaxError>(); foreach (var error in source.MySyntaxErrors) { // The object differs, so create our own copy // The source still might need it in the future and we do not want to break it this.AddSyntaxError(error.Clone(this)); } } this.Document.Parser.TrackedSegments.AddSyntaxErrorsOf(this); OnChanged(); } return(true); }
/// <inheritdoc/> internal override bool UpdateDataFrom(AXmlObject source) { if (!base.UpdateDataFrom(source)) return false; AXmlTag src = (AXmlTag)source; if (this.OpeningBracket != src.OpeningBracket || this.Name != src.Name || this.ClosingBracket != src.ClosingBracket) { OnChanging(); this.OpeningBracket = src.OpeningBracket; this.Name = src.Name; this.ClosingBracket = src.ClosingBracket; OnChanged(); return true; } else { return false; } }
/// <summary> Removes object with all of its non-cached children </summary> public void RemoveParsedObject(AXmlObject obj) { // Cached objects may be used in the future - do not remove them if (obj.IsCached) { return; } segments.Remove(obj); RemoveSyntaxErrorsOf(obj); AXmlParser.Log("Stopped tracking {0}", obj); if (obj is AXmlContainer) { foreach (AXmlObject child in ((AXmlContainer)obj).Children) { RemoveParsedObject(child); } } }
/// <summary> /// To be used exclusively by the children update algorithm. /// Remove child, set parent to null and notify the document /// </summary> void RemoveChild(int index) { AXmlObject removed = this.Children[index]; AXmlParser.Log("Removing {0} at index {1}", removed, index); // Stop tracking if the object can not be used again if (!removed.IsCached) { this.Document.Parser.TrackedSegments.RemoveParsedObject(removed); } // Null parent pointer Assert(removed.Parent == this, "Inconsistent child"); removed.Parent = null; this.Children.RemoveItemAt(index); this.Document.OnObjectRemoved(index, removed); }
/// <inheritdoc/> internal override bool UpdateDataFrom(AXmlObject source) { if (!base.UpdateDataFrom(source)) return false; AXmlElement src = (AXmlElement)source; // Clear the cache for this - quite expensive attributesAndElements = null; if (this.IsProperlyNested != src.IsProperlyNested || this.HasStartOrEmptyTag != src.HasStartOrEmptyTag || this.HasEndTag != src.HasEndTag) { OnChanging(); this.IsProperlyNested = src.IsProperlyNested; this.HasStartOrEmptyTag = src.HasStartOrEmptyTag; this.HasEndTag = src.HasEndTag; OnChanged(); return true; } else { return false; } }
/// <summary> Recursively fix all parent pointer in a tree </summary> /// <remarks> /// Cache constraint: /// If cached item has parent set, then the whole subtree must be consistent and document set /// </remarks> void SetParentPointersInTree(AXmlObject item) { // All items come from the parser cache if (item.Parent == null) { // Dangling object - either a new parser object or removed tree (still cached) item.Parent = this; item.Document = this.Document; AXmlContainer container = item as AXmlContainer; if (container != null) { foreach (AXmlObject child in container.Children) { container.SetParentPointersInTree(child); } } } else if (item.Parent == this) { // If node is attached and then deattached, it will have null parent pointer // but valid subtree - so its children will alredy have correct parent pointer // like in this case // item.DebugCheckConsistency(false); // Rest of the tree is consistent - do not recurse } else { // From cache & parent set => consitent subtree // item.DebugCheckConsistency(false); // The parent (or any futher parents) can not be part of parsed document // becuase otherwise this item would be included twice => safe to change parents // Maintain cache constraint by setting parents to null foreach (AXmlObject ancest in item.GetAncestors().ToList()) { ancest.Parent = null; } item.Parent = this; // Rest of the tree is consistent - do not recurse } }
/// <inheritdoc/> internal override bool UpdateDataFrom(AXmlObject source) { if (!base.UpdateDataFrom(source)) { return(false); } AXmlText src = (AXmlText)source; if (this.EscapedValue != src.EscapedValue || this.Value != src.Value) { OnChanging(); this.EscapedValue = src.EscapedValue; this.Value = src.Value; OnChanged(); return(true); } else { return(false); } }
/// <inheritdoc/> internal override bool UpdateDataFrom(AXmlObject source) { if (!base.UpdateDataFrom(source)) { return(false); } AXmlTag src = (AXmlTag)source; if (this.OpeningBracket != src.OpeningBracket || this.Name != src.Name || this.ClosingBracket != src.ClosingBracket) { OnChanging(); this.OpeningBracket = src.OpeningBracket; this.Name = src.Name; this.ClosingBracket = src.ClosingBracket; OnChanged(); return(true); } else { return(false); } }
string Dereference(AXmlObject owner, string text, int textLocation) { StringBuilder sb = null; // The dereferenced text so far (all up to 'curr') int curr = 0; while (true) { // Reached end of input if (curr == text.Length) { if (sb != null) { return(sb.ToString()); } else { return(text); } } // Try to find reference int start = text.IndexOf('&', curr); // No more references found if (start == -1) { if (sb != null) { sb.Append(text, curr, text.Length - curr); // Add rest return(sb.ToString()); } else { return(text); } } // Append text before the enitiy reference if (sb == null) { sb = new StringBuilder(text.Length); } sb.Append(text, curr, start - curr); curr = start; // Process the entity int errorLoc = textLocation + sb.Length; // Find entity name int end = text.IndexOfAny(new char[] { '&', ';' }, start + 1, Math.Min(maxEntityLength, text.Length - (start + 1))); if (end == -1 || text[end] == '&') { // Not found OnSyntaxError(owner, errorLoc, errorLoc + 1, "Entity reference must be terminated with ';'"); // Keep '&' sb.Append('&'); curr++; continue; // Restart and next character location } string name = text.Substring(start + 1, end - (start + 1)); // Resolve the name string replacement; if (name.Length == 0) { replacement = null; OnSyntaxError(owner, errorLoc + 1, errorLoc + 1, "Entity name expected"); } else if (name == "amp") { replacement = "&"; } else if (name == "lt") { replacement = "<"; } else if (name == "gt") { replacement = ">"; } else if (name == "apos") { replacement = "'"; } else if (name == "quot") { replacement = "\""; } else if (name.Length > 0 && name[0] == '#') { int num; if (name.Length > 1 && name[1] == 'x') { if (!int.TryParse(name.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture.NumberFormat, out num)) { num = -1; OnSyntaxError(owner, errorLoc + 3, errorLoc + 1 + name.Length, "Hexadecimal code of unicode character expected"); } } else { if (!int.TryParse(name.Substring(1), NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out num)) { num = -1; OnSyntaxError(owner, errorLoc + 2, errorLoc + 1 + name.Length, "Numeric code of unicode character expected"); } } if (num != -1) { try { replacement = char.ConvertFromUtf32(num); } catch (ArgumentOutOfRangeException) { replacement = null; OnSyntaxError(owner, errorLoc + 2, errorLoc + 1 + name.Length, "Invalid unicode character U+{0:X} ({0})", num); } } else { replacement = null; } } else if (!IsValidName(name)) { replacement = null; OnSyntaxError(owner, errorLoc + 1, errorLoc + 1, "Invalid entity name"); } else { replacement = null; if (parser.UnknownEntityReferenceIsError) { OnSyntaxError(owner, errorLoc, errorLoc + 1 + name.Length + 1, "Unknown entity reference '{0}'", name); } } // Append the replacement to output if (replacement != null) { sb.Append(replacement); } else { sb.Append('&'); sb.Append(name); sb.Append(';'); } curr = end + 1; continue; } }
/// <summary> Is call to UpdateDataFrom is allowed? </summary> internal bool CanUpdateDataFrom(AXmlObject source) { return this.GetType() == source.GetType() && this.StartOffset == source.StartOffset && (this.LastUpdatedFrom == source || !this.IsCached); }
/// <summary> Recursively fix all parent pointer in a tree </summary> /// <remarks> /// Cache constraint: /// If cached item has parent set, then the whole subtree must be consistent and document set /// </remarks> void SetParentPointersInTree(AXmlObject item) { // All items come from the parser cache if (item.Parent == null) { // Dangling object - either a new parser object or removed tree (still cached) item.Parent = this; item.Document = this.Document; if (item is AXmlContainer) { foreach(AXmlObject child in ((AXmlContainer)item).Children) { ((AXmlContainer)item).SetParentPointersInTree(child); } } } else if (item.Parent == this) { // If node is attached and then deattached, it will have null parent pointer // but valid subtree - so its children will alredy have correct parent pointer // like in this case // item.DebugCheckConsistency(false); // Rest of the tree is consistent - do not recurse } else { // From cache & parent set => consitent subtree // item.DebugCheckConsistency(false); // The parent (or any futher parents) can not be part of parsed document // becuase otherwise this item would be included twice => safe to change parents // Maintain cache constraint by setting parents to null foreach(AXmlObject ancest in item.GetAncestors().ToList()) { ancest.Parent = null; } item.Parent = this; // Rest of the tree is consistent - do not recurse } }
/// <summary> Copy all data from the 'source' to this object </summary> /// <remarks> Returns true if any updates were done </remarks> internal virtual bool UpdateDataFrom(AXmlObject source) { Assert(this.GetType() == source.GetType(), "Source has different type"); DebugAssert(this.StartOffset == source.StartOffset, "Source has different StartOffset"); if (this.LastUpdatedFrom == source) { DebugAssert(this.EndOffset == source.EndOffset, "Source has different EndOffset"); return false; } Assert(!this.IsCached, "Can not update cached item"); Assert(source.IsCached, "Must update from cache"); this.LastUpdatedFrom = source; this.StartOffset = source.StartOffset; // In some cases we are just updating objects of that same // type and position and hoping to be luckily right this.EndOffset = source.EndOffset; // Do not bother comparing - assume changed if non-null if (this.syntaxErrors != null || source.syntaxErrors != null) { // May be called again in derived class - oh, well, does not matter OnChanging(); this.Document.Parser.TrackedSegments.RemoveSyntaxErrorsOf(this); if (source.syntaxErrors == null) { this.syntaxErrors = null; } else { this.syntaxErrors = new List<SyntaxError>(); foreach(var error in source.MySyntaxErrors) { // The object differs, so create our own copy // The source still might need it in the future and we do not want to break it this.AddSyntaxError(error.Clone(this)); } } this.Document.Parser.TrackedSegments.AddSyntaxErrorsOf(this); OnChanged(); } return true; }
internal void OnObjectRemoved(int index, AXmlObject obj) { if (ObjectRemoved != null) ObjectRemoved(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new AXmlObject[] { obj }.ToList(), index)); }
/// <inheritdoc/> internal override bool UpdateDataFrom(AXmlObject source) { if (!base.UpdateDataFrom(source)) return false; AXmlAttribute src = (AXmlAttribute)source; if (this.Name != src.Name || this.EqualsSign != src.EqualsSign || this.QuotedValue != src.QuotedValue || this.Value != src.Value) { OnChanging(); this.Name = src.Name; this.EqualsSign = src.EqualsSign; this.QuotedValue = src.QuotedValue; this.Value = src.Value; OnChanged(); return true; } else { return false; } }
internal void OnObjectChanged(AXmlObject obj) { if (ObjectChanged != null) ObjectChanged(this, new AXmlObjectEventArgs() { Object = obj } ); }