aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml
diff options
context:
space:
mode:
authorRoy Ben Shabat <Roy.mail.net@gmail.com>2019-04-09 01:47:48 +0300
committerRoy Ben Shabat <Roy.mail.net@gmail.com>2019-04-09 01:47:48 +0300
commit080f1697e97e13461ec6df4d31c8924d01257a1b (patch)
treeb1fe0285de7bc9bc52e9e2195e66fe022bf8f5b3 /Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml
parent1608e69a417bc5e40a607c3958c4a60f19f66f1a (diff)
downloadTango-080f1697e97e13461ec6df4d31c8924d01257a1b.tar.gz
Tango-080f1697e97e13461ec6df4d31c8924d01257a1b.zip
MERGE
Diffstat (limited to 'Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml')
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlAttribute.cs129
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlAttributeCollection.cs119
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlContainer.cs282
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlDocument.cs69
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlElement.cs226
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObject.cs266
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObjectCollection.cs90
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObjectEventArgs.cs21
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlParser.cs201
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlTag.cs108
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlText.cs62
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AbstractAXmlVisitor.cs44
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/CanonicalPrintAXmlVisitor.cs119
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/ExtensionMethods.cs47
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/FilteredCollection.cs99
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/IAXmlVisitor.cs29
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/InternalException.cs45
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/MergedCollection.cs70
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/PrettyPrintAXmlVisitor.cs69
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/SyntaxError.cs36
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TagMatchingHeuristics.cs439
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TagReader.cs740
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TextType.cs39
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TokenReader.cs309
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TrackedSegmentCollection.cs165
25 files changed, 3823 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlAttribute.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlAttribute.cs
new file mode 100644
index 000000000..b8b726ef9
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlAttribute.cs
@@ -0,0 +1,129 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ /// <summary>
+ /// Name-value pair in a tag
+ /// </summary>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
+ public class AXmlAttribute: AXmlObject
+ {
+ /// <summary> Name with namespace prefix - exactly as in source file </summary>
+ public string Name { get; internal set; }
+ /// <summary> Equals sign and surrounding whitespace </summary>
+ public string EqualsSign { get; internal set; }
+ /// <summary> The raw value - exactly as in source file (*probably* quoted and escaped) </summary>
+ public string QuotedValue { get; internal set; }
+ /// <summary> Unquoted and dereferenced value of the attribute </summary>
+ public string Value { get; internal set; }
+
+ internal override void DebugCheckConsistency(bool checkParentPointers)
+ {
+ DebugAssert(Name != null, "Null Name");
+ DebugAssert(EqualsSign != null, "Null EqualsSign");
+ DebugAssert(QuotedValue != null, "Null QuotedValue");
+ DebugAssert(Value != null, "Null Value");
+ base.DebugCheckConsistency(checkParentPointers);
+ }
+
+ #region Helpper methods
+
+ /// <summary> The element containing this attribute </summary>
+ /// <returns> Null if orphaned </returns>
+ public AXmlElement ParentElement {
+ get {
+ AXmlTag tag = this.Parent as AXmlTag;
+ if (tag != null) {
+ return tag.Parent as AXmlElement;
+ }
+ return null;
+ }
+ }
+
+ /// <summary> The part of name before ":"</summary>
+ /// <returns> Empty string if not found </returns>
+ public string Prefix {
+ get {
+ return GetNamespacePrefix(this.Name);
+ }
+ }
+
+ /// <summary> The part of name after ":" </summary>
+ /// <returns> Whole name if ":" not found </returns>
+ public string LocalName {
+ get {
+ return GetLocalName(this.Name);
+ }
+ }
+
+ /// <summary>
+ /// Resolved namespace of the name. Empty string if not found
+ /// From the specification: "The namespace name for an unprefixed attribute name always has no value."
+ /// </summary>
+ public string Namespace {
+ get {
+ if (string.IsNullOrEmpty(this.Prefix)) return NoNamespace;
+
+ AXmlElement elem = this.ParentElement;
+ if (elem != null) {
+ return elem.ResolvePrefix(this.Prefix);
+ }
+ return NoNamespace; // Orphaned attribute
+ }
+ }
+
+ /// <summary> Attribute is declaring namespace ("xmlns" or "xmlns:*") </summary>
+ public bool IsNamespaceDeclaration {
+ get {
+ return this.Name == "xmlns" || this.Prefix == "xmlns";
+ }
+ }
+
+ #endregion
+
+ /// <inheritdoc/>
+ public override void AcceptVisitor(IAXmlVisitor visitor)
+ {
+ visitor.VisitAttribute(this);
+ }
+
+ /// <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;
+ }
+ }
+
+ /// <inheritdoc/>
+ public override string ToString()
+ {
+ return string.Format(CultureInfo.InvariantCulture, "[{0} '{1}{2}{3}']", base.ToString(), this.Name, this.EqualsSign, this.Value);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlAttributeCollection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlAttributeCollection.cs
new file mode 100644
index 000000000..95fa83cd0
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlAttributeCollection.cs
@@ -0,0 +1,119 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ /// <summary>
+ /// Specailized attribute collection with attribute name caching
+ /// </summary>
+ public class AXmlAttributeCollection: FilteredCollection<AXmlAttribute, AXmlObjectCollection<AXmlObject>>
+ {
+ /// <summary> Empty unbound collection </summary>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes",
+ Justification = "InsertItem prevents modifying the Empty collection")]
+ public static readonly AXmlAttributeCollection Empty = new AXmlAttributeCollection();
+
+ /// <summary> Create unbound collection </summary>
+ protected AXmlAttributeCollection() {}
+
+ /// <summary> Wrap the given collection. Non-attributes are filtered </summary>
+ public AXmlAttributeCollection(AXmlObjectCollection<AXmlObject> source): base(source) {}
+
+ /// <summary> Wrap the given collection. Non-attributes are filtered. Items not matching the condition are filtered. </summary>
+ public AXmlAttributeCollection(AXmlObjectCollection<AXmlObject> source, Predicate<object> condition): base(source, condition) {}
+
+ Dictionary<string, List<AXmlAttribute>> hashtable = new Dictionary<string, List<AXmlAttribute>>();
+
+ void AddToHashtable(AXmlAttribute attr)
+ {
+ string localName = attr.LocalName;
+ if (!hashtable.ContainsKey(localName)) {
+ hashtable[localName] = new List<AXmlAttribute>(1);
+ }
+ hashtable[localName].Add(attr);
+ }
+
+ void RemoveFromHashtable(AXmlAttribute attr)
+ {
+ string localName = attr.LocalName;
+ hashtable[localName].Remove(attr);
+ }
+
+ static List<AXmlAttribute> NoAttributes = new List<AXmlAttribute>();
+
+ /// <summary>
+ /// Get all attributes with given local name.
+ /// Hash table is used for lookup so this is cheap.
+ /// </summary>
+ public IEnumerable<AXmlAttribute> GetByLocalName(string localName)
+ {
+ if (hashtable.ContainsKey(localName)) {
+ return hashtable[localName];
+ } else {
+ return NoAttributes;
+ }
+ }
+
+ /// <inheritdoc/>
+ protected override void ClearItems()
+ {
+ foreach(AXmlAttribute item in this) {
+ RemoveFromHashtable(item);
+ item.Changing -= item_Changing;
+ item.Changed -= item_Changed;
+ }
+ base.ClearItems();
+ }
+
+ /// <inheritdoc/>
+ protected override void InsertItem(int index, AXmlAttribute item)
+ {
+ // prevent insertions into the static 'Empty' instance
+ if (this == Empty)
+ throw new NotSupportedException("Cannot insert into AXmlAttributeCollection.Empty");
+
+ AddToHashtable(item);
+ item.Changing += item_Changing;
+ item.Changed += item_Changed;
+ base.InsertItem(index, item);
+ }
+
+ /// <inheritdoc/>
+ protected override void RemoveItem(int index)
+ {
+ RemoveFromHashtable(this[index]);
+ this[index].Changing -= item_Changing;
+ this[index].Changed -= item_Changed;
+ base.RemoveItem(index);
+ }
+
+ /// <inheritdoc/>
+ protected override void SetItem(int index, AXmlAttribute item)
+ {
+ RemoveFromHashtable(this[index]);
+ this[index].Changing -= item_Changing;
+ this[index].Changed -= item_Changed;
+
+ AddToHashtable(item);
+ item.Changing += item_Changing;
+ item.Changed += item_Changed;
+ base.SetItem(index, item);
+ }
+
+ // Every item in the collection should be registered to these handlers
+ // so that we can handle renames
+
+ void item_Changing(object sender, AXmlObjectEventArgs e)
+ {
+ RemoveFromHashtable((AXmlAttribute)e.Object);
+ }
+
+ void item_Changed(object sender, AXmlObjectEventArgs e)
+ {
+ AddToHashtable((AXmlAttribute)e.Object);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlContainer.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlContainer.cs
new file mode 100644
index 000000000..3cc716de5
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlContainer.cs
@@ -0,0 +1,282 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Diagnostics;
+using System.Linq;
+
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ /// <summary>
+ /// Abstact base class for all types that can contain child nodes
+ /// </summary>
+ public abstract class AXmlContainer: AXmlObject
+ {
+ /// <summary>
+ /// Children of the node. It is read-only.
+ /// Note that is has CollectionChanged event.
+ /// </summary>
+ public AXmlObjectCollection<AXmlObject> Children { get; private set; }
+
+ /// <summary> Create new container </summary>
+ protected AXmlContainer()
+ {
+ this.Children = new AXmlObjectCollection<AXmlObject>();
+ }
+
+ #region Helpper methods
+
+ ObservableCollection<AXmlElement> elements;
+
+ /// <summary> Gets direcly nested elements (non-recursive) </summary>
+ public ObservableCollection<AXmlElement> Elements {
+ get {
+ if (elements == null) {
+ elements = new FilteredCollection<AXmlElement, AXmlObjectCollection<AXmlObject>>(this.Children);
+ }
+ return elements;
+ }
+ }
+
+ internal AXmlObject FirstChild {
+ get {
+ return this.Children[0];
+ }
+ }
+
+ internal AXmlObject LastChild {
+ get {
+ return this.Children[this.Children.Count - 1];
+ }
+ }
+
+ #endregion
+
+ /// <inheritdoc/>
+ public override IEnumerable<AXmlObject> GetSelfAndAllChildren()
+ {
+ return (new AXmlObject[] { this }).Flatten(
+ delegate(AXmlObject i) {
+ AXmlContainer container = i as AXmlContainer;
+ if (container != null)
+ return container.Children;
+ else
+ return null;
+ }
+ );
+ }
+
+ /// <summary>
+ /// Gets a child fully containg the given offset.
+ /// Goes recursively down the tree.
+ /// Specail case if at the end of attribute or text
+ /// </summary>
+ public AXmlObject GetChildAtOffset(int offset)
+ {
+ foreach(AXmlObject child in this.Children) {
+ if ((child is AXmlAttribute || child is AXmlText) && offset == child.EndOffset) return child;
+ if (child.StartOffset < offset && offset < child.EndOffset) {
+ AXmlContainer container = child as AXmlContainer;
+ if (container != null) {
+ return container.GetChildAtOffset(offset);
+ } else {
+ return child;
+ }
+ }
+ }
+ return this; // No childs at offset
+ }
+
+ // 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);
+ }
+
+ /// <summary> To be used exlucively by the parser </summary>
+ internal void AddChildren(IEnumerable<AXmlObject> items)
+ {
+ // Childs can be only added to newly parsed items
+ Assert(this.Parent == null, "I have to be new");
+ // Do not set parent pointer
+ this.Children.InsertItemsAt(this.Children.Count, items.ToList());
+ }
+
+ /// <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);
+ }
+
+ /// <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
+ }
+ }
+
+ /// <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);
+ }
+
+ /// <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;
+ }
+ }
+
+ /// <remarks>
+ /// Note the the method is not called recuively.
+ /// Only the helper methods are recursive.
+ /// </remarks>
+ internal void UpdateTreeFrom(AXmlContainer srcContainer)
+ {
+ this.StartOffset = srcContainer.StartOffset; // Force the update
+ this.UpdateDataFrom(srcContainer);
+ RemoveChildrenNotIn(srcContainer.Children);
+ InsertAndUpdateChildrenFrom(srcContainer.Children);
+ }
+
+ 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);
+ }
+ }
+ }
+
+ 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");
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlDocument.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlDocument.cs
new file mode 100644
index 000000000..f96c50ca4
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlDocument.cs
@@ -0,0 +1,69 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ /// <summary>
+ /// The root object of the XML document
+ /// </summary>
+ public class AXmlDocument: AXmlContainer
+ {
+ /// <summary> Parser that produced this document </summary>
+ internal AXmlParser Parser { get; set; }
+
+ /// <summary> Occurs when object is added to any part of the document </summary>
+ public event EventHandler<NotifyCollectionChangedEventArgs> ObjectInserted;
+ /// <summary> Occurs when object is removed from any part of the document </summary>
+ public event EventHandler<NotifyCollectionChangedEventArgs> ObjectRemoved;
+ /// <summary> Occurs before local data of any object in the document changes </summary>
+ public event EventHandler<AXmlObjectEventArgs> ObjectChanging;
+ /// <summary> Occurs after local data of any object in the document changed </summary>
+ public event EventHandler<AXmlObjectEventArgs> ObjectChanged;
+
+ internal void OnObjectInserted(int index, AXmlObject obj)
+ {
+ if (ObjectInserted != null)
+ ObjectInserted(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new AXmlObject[] { obj }.ToList(), index));
+ }
+
+ internal void OnObjectRemoved(int index, AXmlObject obj)
+ {
+ if (ObjectRemoved != null)
+ ObjectRemoved(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new AXmlObject[] { obj }.ToList(), index));
+ }
+
+ internal void OnObjectChanging(AXmlObject obj)
+ {
+ if (ObjectChanging != null)
+ ObjectChanging(this, new AXmlObjectEventArgs() { Object = obj } );
+ }
+
+ internal void OnObjectChanged(AXmlObject obj)
+ {
+ if (ObjectChanged != null)
+ ObjectChanged(this, new AXmlObjectEventArgs() { Object = obj } );
+ }
+
+ /// <inheritdoc/>
+ public override void AcceptVisitor(IAXmlVisitor visitor)
+ {
+ visitor.VisitDocument(this);
+ }
+
+ /// <inheritdoc/>
+ public override string ToString()
+ {
+ return string.Format(CultureInfo.InvariantCulture, "[{0} Chld:{1}]", base.ToString(), this.Children.Count);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlElement.cs
new file mode 100644
index 000000000..080d538e8
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlElement.cs
@@ -0,0 +1,226 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ /// <summary>
+ /// Logical grouping of other nodes together.
+ /// </summary>
+ public class AXmlElement: AXmlContainer
+ {
+ /// <summary> No tags are missing anywhere within this element (recursive) </summary>
+ public bool IsProperlyNested { get; set; }
+ /// <returns> True in wellformed XML </returns>
+ public bool HasStartOrEmptyTag { get; set; }
+ /// <returns> True in wellformed XML </returns>
+ public bool HasEndTag { get; set; }
+
+ /// <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> The start or empty-element tag if there is any </summary>
+ internal AXmlTag StartTag {
+ get {
+ Assert(HasStartOrEmptyTag, "Does not have a start tag");
+ return (AXmlTag)this.Children[0];
+ }
+ }
+
+ /// <summary> The end tag if there is any </summary>
+ internal AXmlTag EndTag {
+ get {
+ Assert(HasEndTag, "Does not have an end tag");
+ return (AXmlTag)this.Children[this.Children.Count - 1];
+ }
+ }
+
+ internal override void DebugCheckConsistency(bool checkParentPointers)
+ {
+ DebugAssert(Children.Count > 0, "No children");
+ base.DebugCheckConsistency(checkParentPointers);
+ }
+
+ #region Helpper methods
+
+ /// <summary> Gets attributes of the element </summary>
+ /// <remarks>
+ /// Warning: this is a cenvenience method to access the attributes of the start tag.
+ /// However, since the start tag might be moved/replaced, this property might return
+ /// different values over time.
+ /// </remarks>
+ public AXmlAttributeCollection Attributes {
+ get {
+ if (this.HasStartOrEmptyTag) {
+ return this.StartTag.Attributes;
+ } else {
+ return AXmlAttributeCollection.Empty;
+ }
+ }
+ }
+
+ ObservableCollection<AXmlObject> attributesAndElements;
+
+ /// <summary> Gets both attributes and elements. Expensive, avoid use. </summary>
+ /// <remarks> Warning: the collection will regenerate after each update </remarks>
+ public ObservableCollection<AXmlObject> AttributesAndElements {
+ get {
+ if (attributesAndElements == null) {
+ if (this.HasStartOrEmptyTag) {
+ attributesAndElements = new MergedCollection<AXmlObject, ObservableCollection<AXmlObject>> (
+ // New wrapper with RawObject types
+ new FilteredCollection<AXmlObject, AXmlObjectCollection<AXmlObject>>(this.StartTag.Children, x => x is AXmlAttribute),
+ new FilteredCollection<AXmlObject, AXmlObjectCollection<AXmlObject>>(this.Children, x => x is AXmlElement)
+ );
+ } else {
+ attributesAndElements = new FilteredCollection<AXmlObject, AXmlObjectCollection<AXmlObject>>(this.Children, x => x is AXmlElement);
+ }
+ }
+ return attributesAndElements;
+ }
+ }
+
+ /// <summary> Name with namespace prefix - exactly as in source </summary>
+ public string Name {
+ get {
+ if (this.HasStartOrEmptyTag) {
+ return this.StartTag.Name;
+ } else {
+ return this.EndTag.Name;
+ }
+ }
+ }
+
+ /// <summary> The part of name before ":" </summary>
+ /// <returns> Empty string if not found </returns>
+ public string Prefix {
+ get {
+ return GetNamespacePrefix(this.Name);
+ }
+ }
+
+ /// <summary> The part of name after ":" </summary>
+ /// <returns> Empty string if not found </returns>
+ public string LocalName {
+ get {
+ return GetLocalName(this.Name);
+ }
+ }
+
+ /// <summary> Resolved namespace of the name </summary>
+ /// <returns> Empty string if prefix is not found </returns>
+ public string Namespace {
+ get {
+ string prefix = this.Prefix;
+ if (string.IsNullOrEmpty(prefix)) {
+ return FindDefaultNamespace();
+ } else {
+ return ResolvePrefix(prefix);
+ }
+ }
+ }
+
+ /// <summary> Find the defualt namespace for this context </summary>
+ public string FindDefaultNamespace()
+ {
+ AXmlElement current = this;
+ while(current != null) {
+ string namesapce = current.GetAttributeValue(NoNamespace, "xmlns");
+ if (namesapce != null) return namesapce;
+ current = current.Parent as AXmlElement;
+ }
+ return string.Empty; // No namesapce
+ }
+
+ /// <summary>
+ /// Recursively resolve given prefix in this context. Prefix must have some value.
+ /// </summary>
+ /// <returns> Empty string if prefix is not found </returns>
+ public string ResolvePrefix(string prefix)
+ {
+ if (string.IsNullOrEmpty(prefix)) throw new ArgumentException("No prefix given", "prefix");
+
+ // Implicit namesapces
+ if (prefix == "xml") return XmlNamespace;
+ if (prefix == "xmlns") return XmlnsNamespace;
+
+ AXmlElement current = this;
+ while(current != null) {
+ string namesapce = current.GetAttributeValue(XmlnsNamespace, prefix);
+ if (namesapce != null) return namesapce;
+ current = current.Parent as AXmlElement;
+ }
+ return NoNamespace; // Can not find prefix
+ }
+
+ /// <summary>
+ /// Get unquoted value of attribute.
+ /// It looks in the no namespace (empty string).
+ /// </summary>
+ /// <returns>Null if not found</returns>
+ public string GetAttributeValue(string localName)
+ {
+ return GetAttributeValue(NoNamespace, localName);
+ }
+
+ /// <summary>
+ /// Get unquoted value of attribute
+ /// </summary>
+ /// <param name="namespace">Namespace. Can be no namepace (empty string), which is the default for attributes.</param>
+ /// <param name="localName">Local name - text after ":"</param>
+ /// <returns>Null if not found</returns>
+ public string GetAttributeValue(string @namespace, string localName)
+ {
+ @namespace = @namespace ?? string.Empty;
+ foreach(AXmlAttribute attr in this.Attributes.GetByLocalName(localName)) {
+ DebugAssert(attr.LocalName == localName, "Bad hashtable");
+ if (attr.Namespace == @namespace) {
+ return attr.Value;
+ }
+ }
+ return null;
+ }
+
+ #endregion
+
+ /// <inheritdoc/>
+ public override void AcceptVisitor(IAXmlVisitor visitor)
+ {
+ visitor.VisitElement(this);
+ }
+
+ /// <inheritdoc/>
+ public override string ToString()
+ {
+ return string.Format(CultureInfo.InvariantCulture, "[{0} '{1}' Attr:{2} Chld:{3} Nest:{4}]", base.ToString(), this.Name, this.HasStartOrEmptyTag ? this.StartTag.Children.Count : 0, this.Children.Count, this.IsProperlyNested ? "Ok" : "Bad");
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObject.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObject.cs
new file mode 100644
index 000000000..4951a7c0b
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObject.cs
@@ -0,0 +1,266 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ /// <summary>
+ /// Abstact base class for all types
+ /// </summary>
+ public abstract class AXmlObject: TextSegment
+ {
+ /// <summary> Empty string. The namespace used if there is no "xmlns" specified </summary>
+ public static readonly string NoNamespace = string.Empty;
+
+ /// <summary> Namespace for "xml:" prefix: "http://www.w3.org/XML/1998/namespace" </summary>
+ public static readonly string XmlNamespace = "http://www.w3.org/XML/1998/namespace";
+
+ /// <summary> Namesapce for "xmlns:" prefix: "http://www.w3.org/2000/xmlns/" </summary>
+ public static readonly string XmlnsNamespace = "http://www.w3.org/2000/xmlns/";
+
+ /// <summary> Parent node. </summary>
+ /// <remarks>
+ /// New cached items start with null parent.
+ /// Cache constraint:
+ /// If cached item has parent set, then the whole subtree must be consistent
+ /// </remarks>
+ public AXmlObject Parent { get; set; }
+
+ /// <summary>
+ /// Gets the document that has owns this object.
+ /// Once set, it is not changed. Not even set to null.
+ /// </summary>
+ internal AXmlDocument Document { get; set; }
+
+ /// <summary> Creates new object </summary>
+ protected AXmlObject()
+ {
+ this.LastUpdatedFrom = this;
+ }
+
+ /// <summary> Occurs before the value of any local properties changes. Nested changes do not cause the event to occur </summary>
+ public event EventHandler<AXmlObjectEventArgs> Changing;
+
+ /// <summary> Occurs after the value of any local properties changed. Nested changes do not cause the event to occur </summary>
+ public event EventHandler<AXmlObjectEventArgs> Changed;
+
+ /// <summary> Raises Changing event </summary>
+ protected void OnChanging()
+ {
+ AXmlParser.Log("Changing {0}", this);
+ if (Changing != null) {
+ Changing(this, new AXmlObjectEventArgs() { Object = this } );
+ }
+ AXmlDocument doc = this.Document;
+ if (doc != null) {
+ doc.OnObjectChanging(this);
+ }
+ // As a convenience, also rasie an event for the parent element
+ AXmlTag me = this as AXmlTag;
+ if (me != null && (me.IsStartOrEmptyTag || me.IsEndTag) && me.Parent is AXmlElement) {
+ me.Parent.OnChanging();
+ }
+ }
+
+ /// <summary> Raises Changed event </summary>
+ protected void OnChanged()
+ {
+ AXmlParser.Log("Changed {0}", this);
+ if (Changed != null) {
+ Changed(this, new AXmlObjectEventArgs() { Object = this } );
+ }
+ AXmlDocument doc = this.Document;
+ if (doc != null) {
+ doc.OnObjectChanged(this);
+ }
+ // As a convenience, also rasie an event for the parent element
+ AXmlTag me = this as AXmlTag;
+ if (me != null && (me.IsStartOrEmptyTag || me.IsEndTag) && me.Parent is AXmlElement) {
+ me.Parent.OnChanged();
+ }
+ }
+
+ List<SyntaxError> syntaxErrors;
+
+ /// <summary>
+ /// The error that occured in the context of this node (excluding nested nodes)
+ /// </summary>
+ public IEnumerable<SyntaxError> MySyntaxErrors {
+ get {
+ if (syntaxErrors == null) {
+ return new SyntaxError[] {};
+ } else {
+ return syntaxErrors;
+ }
+ }
+ }
+
+ /// <summary>
+ /// The error that occured in the context of this node and all nested nodes.
+ /// It has O(n) cost.
+ /// </summary>
+ public IEnumerable<SyntaxError> SyntaxErrors {
+ get {
+ return GetSelfAndAllChildren().SelectMany(obj => obj.MySyntaxErrors);
+ }
+ }
+
+ internal void AddSyntaxError(SyntaxError error)
+ {
+ DebugAssert(error.Object == this, "Must own the error");
+ if (this.syntaxErrors == null) this.syntaxErrors = new List<SyntaxError>();
+ syntaxErrors.Add(error);
+ }
+
+ /// <summary> Throws exception if condition is false </summary>
+ /// <remarks> Present in release mode - use only for very cheap aserts </remarks>
+ protected static void Assert(bool condition, string message)
+ {
+ if (!condition) {
+ throw new InternalException("Assertion failed: " + message);
+ }
+ }
+
+ /// <summary> Throws exception if condition is false </summary>
+ [Conditional("DEBUG")]
+ protected static void DebugAssert(bool condition, string message)
+ {
+ if (!condition) {
+ throw new InternalException("Assertion failed: " + message);
+ }
+ }
+
+ /// <summary> Recursively gets self and all nested nodes. </summary>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate",
+ Justification = "Using a method makes the API look more LINQ-like and indicates that the returned collection is computed every time.")]
+ public virtual IEnumerable<AXmlObject> GetSelfAndAllChildren()
+ {
+ return new AXmlObject[] { this };
+ }
+
+ /// <summary> Get all ancestors of this node </summary>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate",
+ Justification = "Using a method makes the API look more LINQ-like and indicates that the returned collection is computed every time.")]
+ public IEnumerable<AXmlObject> GetAncestors()
+ {
+ AXmlObject curr = this.Parent;
+ while(curr != null) {
+ yield return curr;
+ curr = curr.Parent;
+ }
+ }
+
+ /// <summary> Call appropriate visit method on the given visitor </summary>
+ public abstract void AcceptVisitor(IAXmlVisitor visitor);
+
+ /// <summary> The parser tree object this object was updated from </summary>
+ /// <remarks> Initialized to 'this' </remarks>
+ internal AXmlObject LastUpdatedFrom { get; private set; }
+
+ internal bool IsCached { get; set; }
+
+ /// <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> 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;
+ }
+
+ /// <summary> Verify that the item is consistent. Only in debug build. </summary>
+ [Conditional("DEBUG")]
+ internal virtual void DebugCheckConsistency(bool allowNullParent)
+ {
+
+ }
+
+ /// <inheritdoc/>
+ public override string ToString()
+ {
+ return string.Format(CultureInfo.InvariantCulture, "{0}({1}-{2})", this.GetType().Name.Remove(0, 4), this.StartOffset, this.EndOffset);
+ }
+
+ #region Helpper methods
+
+ /// <summary> The part of name before ":" </summary>
+ /// <returns> Empty string if not found </returns>
+ protected static string GetNamespacePrefix(string name)
+ {
+ if (string.IsNullOrEmpty(name)) return string.Empty;
+ int colonIndex = name.IndexOf(':');
+ if (colonIndex != -1) {
+ return name.Substring(0, colonIndex);
+ } else {
+ return string.Empty;
+ }
+ }
+
+ /// <summary> The part of name after ":" </summary>
+ /// <returns> Whole name if ":" not found </returns>
+ protected static string GetLocalName(string name)
+ {
+ if (string.IsNullOrEmpty(name)) return string.Empty;
+ int colonIndex = name.IndexOf(':');
+ if (colonIndex != -1) {
+ return name.Remove(0, colonIndex + 1);
+ } else {
+ return name ?? string.Empty;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObjectCollection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObjectCollection.cs
new file mode 100644
index 000000000..3a2c7cc21
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObjectCollection.cs
@@ -0,0 +1,90 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ /// <summary>
+ /// Collection that is publicly read-only and has support
+ /// for adding/removing multiple items at a time.
+ /// </summary>
+ public class AXmlObjectCollection<T>: Collection<T>, INotifyCollectionChanged
+ {
+ /// <summary> Occurs when the collection is changed </summary>
+ public event NotifyCollectionChangedEventHandler CollectionChanged;
+
+ /// <summary> Raises <see cref="CollectionChanged"/> event </summary>
+ // Do not inherit - it is not called if event is null
+ void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
+ {
+ if (CollectionChanged != null) {
+ CollectionChanged(this, e);
+ }
+ }
+
+ /// <inheritdoc/>
+ protected override void ClearItems()
+ {
+ throw new NotSupportedException();
+ }
+
+ /// <inheritdoc/>
+ protected override void InsertItem(int index, T item)
+ {
+ throw new NotSupportedException();
+ }
+
+ /// <inheritdoc/>
+ protected override void RemoveItem(int index)
+ {
+ throw new NotSupportedException();
+ }
+
+ /// <inheritdoc/>
+ protected override void SetItem(int index, T item)
+ {
+ throw new NotSupportedException();
+ }
+
+ internal void InsertItemAt(int index, T item)
+ {
+ base.InsertItem(index, item);
+ if (CollectionChanged != null)
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new T[] { item }.ToList(), index));
+ }
+
+ internal void RemoveItemAt(int index)
+ {
+ T removed = this[index];
+ base.RemoveItem(index);
+ if (CollectionChanged != null)
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new T[] { removed }.ToList(), index));
+ }
+
+ internal void InsertItemsAt(int index, IList<T> items)
+ {
+ for(int i = 0; i < items.Count; i++) {
+ base.InsertItem(index + i, items[i]);
+ }
+ if (CollectionChanged != null)
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, (IList)items, index));
+ }
+
+ internal void RemoveItemsAt(int index, int count)
+ {
+ List<T> removed = new List<T>();
+ for(int i = 0; i < count; i++) {
+ removed.Add(this[index]);
+ base.RemoveItem(index);
+ }
+ if (CollectionChanged != null)
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, (IList)removed, index));
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObjectEventArgs.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObjectEventArgs.cs
new file mode 100644
index 000000000..27ea041e6
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlObjectEventArgs.cs
@@ -0,0 +1,21 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Diagnostics;
+using System.Linq;
+
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ /// <summary> Holds event args for event caused by <see cref="AXmlObject"/> </summary>
+ public class AXmlObjectEventArgs: EventArgs
+ {
+ /// <summary> The object that caused the event </summary>
+ public AXmlObject Object { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlParser.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlParser.cs
new file mode 100644
index 000000000..625ff59d9
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlParser.cs
@@ -0,0 +1,201 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using System.Threading;
+
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ /// <summary>
+ /// Creates object tree from XML document.
+ /// </summary>
+ /// <remarks>
+ /// The created tree fully describes the document and thus the orginal XML file can be
+ /// exactly reproduced.
+ ///
+ /// Any further parses will reparse only the changed parts and the existing tree will
+ /// be updated with the changes. The user can add event handlers to be notified of
+ /// the changes. The parser tries to minimize the number of changes to the tree.
+ /// (for example, it will add a single child at the start of collection rather than
+ /// clearing the collection and adding new children)
+ ///
+ /// The object tree consists of following types:
+ /// RawObject - Abstact base class for all types
+ /// RawContainer - Abstact base class for all types that can contain child nodes
+ /// RawDocument - The root object of the XML document
+ /// RawElement - Logical grouping of other nodes together. The first child is always the start tag.
+ /// RawTag - Represents any markup starting with "&lt;" and (hopefully) ending with ">"
+ /// RawAttribute - Name-value pair in a tag
+ /// RawText - Whitespace or character data
+ ///
+ /// For example, see the following XML and the produced object tree:
+ /// <![CDATA[
+ /// <!-- My favourite quote -->
+ /// <quote author="Albert Einstein">
+ /// Make everything as simple as possible, but not simpler.
+ /// </quote>
+ ///
+ /// RawDocument
+ /// RawTag "<!--" "-->"
+ /// RawText " My favourite quote "
+ /// RawElement
+ /// RawTag "<" "quote" ">"
+ /// RawText " "
+ /// RawAttribute 'author="Albert Einstein"'
+ /// RawText "\n Make everything as simple as possible, but not simpler.\n"
+ /// RawTag "</" "quote" ">"
+ /// ]]>
+ ///
+ /// The precise content of RawTag depends on what it represents:
+ /// <![CDATA[
+ /// Start tag: "<" Name? (RawText+ RawAttribute)* RawText* (">" | "/>")
+ /// End tag: "</" Name? (RawText+ RawAttribute)* RawText* ">"
+ /// P.instr.: "<?" Name? (RawText)* "?>"
+ /// Comment: "<!--" (RawText)* "-->"
+ /// CData: "<![CDATA[" (RawText)* "]]" ">"
+ /// DTD: "<!DOCTYPE" (RawText+ RawTag)* RawText* ">" (DOCTYPE or other DTD names)
+ /// UknownBang: "<!" (RawText)* ">"
+ /// ]]>
+ ///
+ /// The type of tag can be identified by the opening backet.
+ /// There are helpper properties in the RawTag class to identify the type, exactly
+ /// one of the properties will be true.
+ ///
+ /// The closing bracket may be missing or may be different for mallformed XML.
+ ///
+ /// Note that there can always be multiple consequtive RawText nodes.
+ /// This is to ensure that idividual texts are not too long.
+ ///
+ /// XML Spec: http://www.w3.org/TR/xml/
+ /// XML EBNF: http://www.jelks.nu/XML/xmlebnf.html
+ ///
+ /// Internals:
+ ///
+ /// "Try" methods can silently fail by returning false.
+ /// MoveTo methods do not move if they are already at the given target
+ /// If methods return some object, it must be no-empty. It is up to the caller to ensure
+ /// the context is appropriate for reading.
+ ///
+ /// </remarks>
+ public class AXmlParser
+ {
+ AXmlDocument userDocument;
+
+ internal TrackedSegmentCollection TrackedSegments { get; private set; }
+
+ /// <summary>
+ /// Generate syntax error when seeing enity reference other then the build-in ones
+ /// </summary>
+ public bool UnknownEntityReferenceIsError { get; set; }
+
+ /// <summary> Create new parser </summary>
+ public AXmlParser()
+ {
+ this.Lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
+ ClearInternal();
+ }
+
+ /// <summary> Throws exception if condition is false </summary>
+ internal static void Assert(bool condition, string message)
+ {
+ if (!condition) {
+ throw new InternalException("Assertion failed: " + message);
+ }
+ }
+
+ /// <summary> Throws exception if condition is false </summary>
+ [Conditional("DEBUG")]
+ internal static void DebugAssert(bool condition, string message)
+ {
+ if (!condition) {
+ throw new InternalException("Assertion failed: " + message);
+ }
+ }
+
+ [Conditional("DEBUG")]
+ internal static void Log(string text, params object[] pars)
+ {
+ //System.Diagnostics.Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "XML: " + text, pars));
+ }
+
+ /// <summary>
+ /// Incrementaly parse the given text.
+ /// You have to hold the write lock.
+ /// </summary>
+ /// <param name="input">
+ /// The full XML text of the new document.
+ /// </param>
+ /// <param name="changesSinceLastParse">
+ /// Changes since last parse. Null will cause full reparse.
+ /// </param>
+ public AXmlDocument Parse(string input, IEnumerable<DocumentChangeEventArgs> changesSinceLastParse)
+ {
+ if (!Lock.IsWriteLockHeld)
+ throw new InvalidOperationException("Lock needed!");
+
+ // Use changes to invalidate cache
+ if (changesSinceLastParse != null) {
+ this.TrackedSegments.UpdateOffsetsAndInvalidate(changesSinceLastParse);
+ } else {
+ this.TrackedSegments.InvalidateAll();
+ }
+
+ TagReader tagReader = new TagReader(this, input);
+ List<AXmlObject> tags = tagReader.ReadAllTags();
+ AXmlDocument parsedDocument = new TagMatchingHeuristics(this, input, tags).ReadDocument();
+ tagReader.PrintStringCacheStats();
+ AXmlParser.Log("Updating main DOM tree...");
+ userDocument.UpdateTreeFrom(parsedDocument);
+ userDocument.DebugCheckConsistency(true);
+ Assert(userDocument.GetSelfAndAllChildren().Count() == parsedDocument.GetSelfAndAllChildren().Count(), "Parsed document and updated document have different number of children");
+ return userDocument;
+ }
+
+ /// <summary>
+ /// Makes calls to Parse() thread-safe. Use Lock everywhere Parse() is called.
+ /// </summary>
+ public ReaderWriterLockSlim Lock { get; private set; }
+
+ /// <summary>
+ /// Returns the last cached version of the document.
+ /// </summary>
+ /// <exception cref="InvalidOperationException">No read lock is held by the current thread.</exception>
+ public AXmlDocument LastDocument {
+ get {
+ if (!Lock.IsReadLockHeld)
+ throw new InvalidOperationException("Read lock needed!");
+
+ return userDocument;
+ }
+ }
+
+ /// <summary>
+ /// Clears the parser data.
+ /// </summary>
+ /// <exception cref="InvalidOperationException">No write lock is held by the current thread.</exception>
+ public void Clear()
+ {
+ if (!Lock.IsWriteLockHeld)
+ throw new InvalidOperationException("Write lock needed!");
+
+ ClearInternal();
+ }
+
+ void ClearInternal()
+ {
+ this.UnknownEntityReferenceIsError = true;
+ this.TrackedSegments = new TrackedSegmentCollection();
+ this.userDocument = new AXmlDocument() { Parser = this };
+ this.userDocument.Document = this.userDocument;
+ // Track the document
+ this.TrackedSegments.AddParsedObject(this.userDocument, null);
+ this.userDocument.IsCached = false;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlTag.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlTag.cs
new file mode 100644
index 000000000..8283cea22
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlTag.cs
@@ -0,0 +1,108 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ /// <summary>
+ /// Represents any markup starting with "&lt;" and (hopefully) ending with ">"
+ /// </summary>
+ public class AXmlTag: AXmlContainer
+ {
+ /// <summary> These identify the start of DTD elements </summary>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification="ReadOnlyCollection is immutable")]
+ public static readonly ReadOnlyCollection<string> DtdNames = new ReadOnlyCollection<string>(
+ new string[] {"<!DOCTYPE", "<!NOTATION", "<!ELEMENT", "<!ATTLIST", "<!ENTITY" } );
+
+ /// <summary> Opening bracket - usually "&lt;" </summary>
+ public string OpeningBracket { get; internal set; }
+ /// <summary> Name following the opening bracket </summary>
+ public string Name { get; internal set; }
+ /// <summary> Opening bracket - usually "&gt;" </summary>
+ public string ClosingBracket { get; internal set; }
+
+ /// <summary> True if tag starts with "&lt;" </summary>
+ public bool IsStartOrEmptyTag { get { return OpeningBracket == "<"; } }
+ /// <summary> True if tag starts with "&lt;" and ends with "&gt;" </summary>
+ public bool IsStartTag { get { return OpeningBracket == "<" && ClosingBracket == ">"; } }
+ /// <summary> True if tag starts with "&lt;" and does not end with "&gt;" </summary>
+ public bool IsEmptyTag { get { return OpeningBracket == "<" && ClosingBracket != ">" ; } }
+ /// <summary> True if tag starts with "&lt;/" </summary>
+ public bool IsEndTag { get { return OpeningBracket == "</"; } }
+ /// <summary> True if tag starts with "&lt;?" </summary>
+ public bool IsProcessingInstruction { get { return OpeningBracket == "<?"; } }
+ /// <summary> True if tag starts with "&lt;!--" </summary>
+ public bool IsComment { get { return OpeningBracket == "<!--"; } }
+ /// <summary> True if tag starts with "&lt;![CDATA[" </summary>
+ public bool IsCData { get { return OpeningBracket == "<![CDATA["; } }
+ /// <summary> True if tag starts with one of the DTD starts </summary>
+ public bool IsDocumentType { get { return DtdNames.Contains(OpeningBracket); } }
+ /// <summary> True if tag starts with "&lt;!" </summary>
+ public bool IsUnknownBang { get { return OpeningBracket == "<!"; } }
+
+ #region Helpper methods
+
+ AXmlAttributeCollection attributes;
+
+ /// <summary> Gets attributes of the tag (if applicable) </summary>
+ public AXmlAttributeCollection Attributes {
+ get {
+ if (attributes == null) {
+ attributes = new AXmlAttributeCollection(this.Children);
+ }
+ return attributes;
+ }
+ }
+
+ #endregion
+
+ internal override void DebugCheckConsistency(bool checkParentPointers)
+ {
+ Assert(OpeningBracket != null, "Null OpeningBracket");
+ Assert(Name != null, "Null Name");
+ Assert(ClosingBracket != null, "Null ClosingBracket");
+ base.DebugCheckConsistency(checkParentPointers);
+ }
+
+ /// <inheritdoc/>
+ public override void AcceptVisitor(IAXmlVisitor visitor)
+ {
+ visitor.VisitTag(this);
+ }
+
+ /// <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;
+ }
+ }
+
+ /// <inheritdoc/>
+ public override string ToString()
+ {
+ return string.Format(CultureInfo.InvariantCulture, "[{0} '{1}{2}{3}' Attr:{4}]", base.ToString(), this.OpeningBracket, this.Name, this.ClosingBracket, this.Children.Count);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlText.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlText.cs
new file mode 100644
index 000000000..8470979ce
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlText.cs
@@ -0,0 +1,62 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ /// <summary>
+ /// Whitespace or character data
+ /// </summary>
+ public class AXmlText: AXmlObject
+ {
+ /// <summary> The context in which the text occured </summary>
+ internal TextType Type { get; set; }
+ /// <summary> The text exactly as in source </summary>
+ public string EscapedValue { get; set; }
+ /// <summary> The text with all entity references resloved </summary>
+ public string Value { get; set; }
+ /// <summary> True if the text contains only whitespace characters </summary>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
+ Justification = "System.Xml also uses 'Whitespace'")]
+ public bool ContainsOnlyWhitespace { get; set; }
+
+ /// <inheritdoc/>
+ public override void AcceptVisitor(IAXmlVisitor visitor)
+ {
+ visitor.VisitText(this);
+ }
+
+ /// <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/>
+ public override string ToString()
+ {
+ return string.Format(CultureInfo.InvariantCulture, "[{0} Text.Length={1}]", base.ToString(), this.EscapedValue.Length);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AbstractAXmlVisitor.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AbstractAXmlVisitor.cs
new file mode 100644
index 000000000..1c8e72bbb
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AbstractAXmlVisitor.cs
@@ -0,0 +1,44 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Text;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ /// <summary>
+ /// Derive from this class to create visitor for the XML tree
+ /// </summary>
+ public abstract class AbstractAXmlVisitor : IAXmlVisitor
+ {
+ /// <summary> Visit RawDocument </summary>
+ public virtual void VisitDocument(AXmlDocument document)
+ {
+ foreach(AXmlObject child in document.Children) child.AcceptVisitor(this);
+ }
+
+ /// <summary> Visit RawElement </summary>
+ public virtual void VisitElement(AXmlElement element)
+ {
+ foreach(AXmlObject child in element.Children) child.AcceptVisitor(this);
+ }
+
+ /// <summary> Visit RawTag </summary>
+ public virtual void VisitTag(AXmlTag tag)
+ {
+ foreach(AXmlObject child in tag.Children) child.AcceptVisitor(this);
+ }
+
+ /// <summary> Visit RawAttribute </summary>
+ public virtual void VisitAttribute(AXmlAttribute attribute)
+ {
+
+ }
+
+ /// <summary> Visit RawText </summary>
+ public virtual void VisitText(AXmlText text)
+ {
+
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/CanonicalPrintAXmlVisitor.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/CanonicalPrintAXmlVisitor.cs
new file mode 100644
index 000000000..df2ac5e81
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/CanonicalPrintAXmlVisitor.cs
@@ -0,0 +1,119 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Linq;
+using System.Text;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ /// <summary>
+ /// Converts the XML tree back to text in canonical form.
+ /// See http://www.w3.org/TR/xml-c14n
+ /// </summary>
+ public class CanonicalPrintAXmlVisitor: AbstractAXmlVisitor
+ {
+ StringBuilder sb = new StringBuilder();
+
+ /// <summary>
+ /// Gets the pretty printed text
+ /// </summary>
+ public string Output {
+ get {
+ return sb.ToString();
+ }
+ }
+
+ /// <summary> Create canonical text from a document </summary>
+ public static string Print(AXmlDocument doc)
+ {
+ CanonicalPrintAXmlVisitor visitor = new CanonicalPrintAXmlVisitor();
+ visitor.VisitDocument(doc);
+ return visitor.Output;
+ }
+
+ /// <summary> Visit RawDocument </summary>
+ public override void VisitDocument(AXmlDocument document)
+ {
+ foreach(AXmlObject child in document.Children) {
+ AXmlTag childAsTag = child as AXmlTag;
+ // Only procssing instructions or elements
+ if (childAsTag != null && childAsTag.IsProcessingInstruction && childAsTag.Name != "xml") {
+ VisitTag(childAsTag);
+ } else {
+ AXmlElement childAsElement = child as AXmlElement;
+ if (childAsElement != null) {
+ VisitElement(childAsElement);
+ }
+ }
+ }
+ }
+
+ /// <summary> Visit RawElement </summary>
+ public override void VisitElement(AXmlElement element)
+ {
+ base.VisitElement(element);
+ }
+
+ /// <summary> Visit RawTag </summary>
+ public override void VisitTag(AXmlTag tag)
+ {
+ if (tag.IsStartOrEmptyTag) {
+ sb.Append('<');
+ sb.Append(tag.Name);
+ foreach(AXmlAttribute attr in tag.Children.OfType<AXmlAttribute>().OrderBy(a => a.Name)) {
+ VisitAttribute(attr);
+ }
+ sb.Append('>');
+ if (tag.IsEmptyTag) {
+ // Use explicit start-end pair
+ sb.AppendFormat("</{0}>", tag.Name);
+ }
+ } else if (tag.IsEndTag) {
+ sb.AppendFormat("</{0}>", tag.Name);
+ } else if (tag.IsProcessingInstruction) {
+ sb.Append("<?");
+ sb.Append(tag.Name);
+ foreach(AXmlText text in tag.Children.OfType<AXmlText>()) {
+ sb.Append(text.Value);
+ }
+ if (tag.Children.Count == 0)
+ sb.Append(' ');
+ sb.Append("?>");
+ } else if (tag.IsCData) {
+ foreach(AXmlText text in tag.Children.OfType<AXmlText>()) {
+ sb.Append(Escape(text.Value));
+ }
+ }
+ }
+
+ /// <summary> Visit RawAttribute </summary>
+ public override void VisitAttribute(AXmlAttribute attribute)
+ {
+ sb.Append(' ');
+ sb.Append(attribute.Name);
+ sb.Append("=");
+ sb.Append('"');
+ sb.Append(Escape(attribute.Value));
+ sb.Append('"');
+ }
+
+ /// <summary> Visit RawText </summary>
+ public override void VisitText(AXmlText text)
+ {
+ sb.Append(Escape(text.Value));
+ }
+
+ static string Escape(string text)
+ {
+ return text
+ .Replace("&", "&amp;")
+ .Replace("<", "&lt;")
+ .Replace(">", "&gt;")
+ .Replace("\"", "&quot;")
+ .Replace("\u0009", "&#9;")
+ .Replace("\u000A", "&#10;")
+ .Replace("\u000D", "&#13;");
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/ExtensionMethods.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/ExtensionMethods.cs
new file mode 100644
index 000000000..34664a23a
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/ExtensionMethods.cs
@@ -0,0 +1,47 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Diagnostics;
+using System.Linq;
+
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ static class ExtensionMethods
+ {
+ // Copied from ICSharpCode.SharpDevelop.Dom.ExtensionMethods
+ /// <summary>
+ /// Converts a recursive data structure into a flat list.
+ /// </summary>
+ /// <param name="input">The root elements of the recursive data structure.</param>
+ /// <param name="recursion">The function that gets the children of an element.</param>
+ /// <returns>Iterator that enumerates the tree structure in preorder.</returns>
+ public static IEnumerable<T> Flatten<T>(this IEnumerable<T> input, Func<T, IEnumerable<T>> recursion)
+ {
+ Stack<IEnumerator<T>> stack = new Stack<IEnumerator<T>>();
+ try {
+ stack.Push(input.GetEnumerator());
+ while (stack.Count > 0) {
+ while (stack.Peek().MoveNext()) {
+ T element = stack.Peek().Current;
+ yield return element;
+ IEnumerable<T> children = recursion(element);
+ if (children != null) {
+ stack.Push(children.GetEnumerator());
+ }
+ }
+ stack.Pop().Dispose();
+ }
+ } finally {
+ while (stack.Count > 0) {
+ stack.Pop().Dispose();
+ }
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/FilteredCollection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/FilteredCollection.cs
new file mode 100644
index 000000000..fd936d1b0
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/FilteredCollection.cs
@@ -0,0 +1,99 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ /// <summary>
+ /// Collection that presents only some items from the wrapped collection.
+ /// It implicitely filters object that are not of type T (or derived).
+ /// </summary>
+ public class FilteredCollection<T, TCollection>: ObservableCollection<T> where TCollection : INotifyCollectionChanged, IList
+ {
+ TCollection source;
+ Predicate<object> condition;
+ List<int> srcPtrs = new List<int>(); // Index to the original collection
+
+ /// <summary> Create unbound collection </summary>
+ protected FilteredCollection() {}
+
+ /// <summary> Wrap the given collection. Items of type other then T are filtered </summary>
+ public FilteredCollection(TCollection source) : this (source, x => true) { }
+
+ /// <summary> Wrap the given collection. Items of type other then T are filtered. Items not matching the condition are filtered. </summary>
+ public FilteredCollection(TCollection source, Predicate<object> condition)
+ {
+ this.source = source;
+ this.condition = condition;
+
+ this.source.CollectionChanged += SourceCollectionChanged;
+
+ Reset();
+ }
+
+ void Reset()
+ {
+ this.Clear();
+ srcPtrs.Clear();
+ for(int i = 0; i < source.Count; i++) {
+ if (source[i] is T && condition(source[i])) {
+ this.Add((T)source[i]);
+ srcPtrs.Add(i);
+ }
+ }
+ }
+
+ void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ switch(e.Action) {
+ case NotifyCollectionChangedAction.Add:
+ // Update pointers
+ for(int i = 0; i < srcPtrs.Count; i++) {
+ if (srcPtrs[i] >= e.NewStartingIndex) {
+ srcPtrs[i] += e.NewItems.Count;
+ }
+ }
+ // Find where to add items
+ int addIndex = srcPtrs.FindIndex(srcPtr => srcPtr >= e.NewStartingIndex);
+ if (addIndex == -1) addIndex = this.Count;
+ // Add items to collection
+ for(int i = 0; i < e.NewItems.Count; i++) {
+ if (e.NewItems[i] is T && condition(e.NewItems[i])) {
+ this.InsertItem(addIndex, (T)e.NewItems[i]);
+ srcPtrs.Insert(addIndex, e.NewStartingIndex + i);
+ addIndex++;
+ }
+ }
+ break;
+ case NotifyCollectionChangedAction.Remove:
+ // Remove the item from our collection
+ for(int i = 0; i < e.OldItems.Count; i++) {
+ // Anyone points to the removed item?
+ int removeIndex = srcPtrs.IndexOf(e.OldStartingIndex + i);
+ // Remove
+ if (removeIndex != -1) {
+ this.RemoveAt(removeIndex);
+ srcPtrs.RemoveAt(removeIndex);
+ }
+ }
+ // Update pointers
+ for(int i = 0; i < srcPtrs.Count; i++) {
+ if (srcPtrs[i] >= e.OldStartingIndex) {
+ srcPtrs[i] -= e.OldItems.Count;
+ }
+ }
+ break;
+ case NotifyCollectionChangedAction.Reset:
+ Reset();
+ break;
+ default:
+ throw new NotSupportedException(e.Action.ToString());
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/IAXmlVisitor.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/IAXmlVisitor.cs
new file mode 100644
index 000000000..cbd4b2549
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/IAXmlVisitor.cs
@@ -0,0 +1,29 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Text;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ /// <summary>
+ /// Visitor for the XML tree
+ /// </summary>
+ public interface IAXmlVisitor
+ {
+ /// <summary> Visit RawDocument </summary>
+ void VisitDocument(AXmlDocument document);
+
+ /// <summary> Visit RawElement </summary>
+ void VisitElement(AXmlElement element);
+
+ /// <summary> Visit RawTag </summary>
+ void VisitTag(AXmlTag tag);
+
+ /// <summary> Visit RawAttribute </summary>
+ void VisitAttribute(AXmlAttribute attribute);
+
+ /// <summary> Visit RawText </summary>
+ void VisitText(AXmlText text);
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/InternalException.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/InternalException.cs
new file mode 100644
index 000000000..a52ac4b4f
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/InternalException.cs
@@ -0,0 +1,45 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Runtime.Serialization;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ /// <summary>
+ /// Exception used for internal errors in XML parser.
+ /// This exception indicates a bug in AvalonEdit.
+ /// </summary>
+ [Serializable()]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1064:ExceptionsShouldBePublic", Justification = "This exception is not public because it is not supposed to be caught by user code - it indicates a bug in AvalonEdit.")]
+ class InternalException : Exception
+ {
+ /// <summary>
+ /// Creates a new InternalException instance.
+ /// </summary>
+ public InternalException() : base()
+ {
+ }
+
+ /// <summary>
+ /// Creates a new InternalException instance.
+ /// </summary>
+ public InternalException(string message) : base(message)
+ {
+ }
+
+ /// <summary>
+ /// Creates a new InternalException instance.
+ /// </summary>
+ public InternalException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ /// <summary>
+ /// Creates a new InternalException instance.
+ /// </summary>
+ protected InternalException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/MergedCollection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/MergedCollection.cs
new file mode 100644
index 000000000..79574053b
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/MergedCollection.cs
@@ -0,0 +1,70 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ /// <summary>
+ /// Two collections in sequence
+ /// </summary>
+ public class MergedCollection<T, TCollection> : ObservableCollection<T> where TCollection : INotifyCollectionChanged, IList<T>
+ {
+ TCollection a;
+ TCollection b;
+
+ /// <summary> Create a wrapper containing elements of 'a' and then 'b' </summary>
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")]
+ public MergedCollection(TCollection a, TCollection b)
+ {
+ this.a = a;
+ this.b = b;
+
+ this.a.CollectionChanged += SourceCollectionAChanged;
+ this.b.CollectionChanged += SourceCollectionBChanged;
+
+ Reset();
+ }
+
+ void Reset()
+ {
+ this.Clear();
+ foreach(T item in a) this.Add(item);
+ foreach(T item in b) this.Add(item);
+ }
+
+ void SourceCollectionAChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ SourceCollectionChanged(0, e);
+ }
+
+ void SourceCollectionBChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ SourceCollectionChanged(a.Count, e);
+ }
+
+ void SourceCollectionChanged(int collectionStart, NotifyCollectionChangedEventArgs e)
+ {
+ switch(e.Action) {
+ case NotifyCollectionChangedAction.Add:
+ for (int i = 0; i < e.NewItems.Count; i++) {
+ this.InsertItem(collectionStart + e.NewStartingIndex + i, (T)e.NewItems[i]);
+ }
+ break;
+ case NotifyCollectionChangedAction.Remove:
+ for (int i = 0; i < e.OldItems.Count; i++) {
+ this.RemoveAt(collectionStart + e.OldStartingIndex);
+ }
+ break;
+ case NotifyCollectionChangedAction.Reset:
+ Reset();
+ break;
+ default:
+ throw new NotSupportedException(e.Action.ToString());
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/PrettyPrintAXmlVisitor.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/PrettyPrintAXmlVisitor.cs
new file mode 100644
index 000000000..cb393c552
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/PrettyPrintAXmlVisitor.cs
@@ -0,0 +1,69 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Text;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ /// <summary>
+ /// Converts the XML tree back to text.
+ /// The text should exactly match the original.
+ /// </summary>
+ public class PrettyPrintAXmlVisitor: AbstractAXmlVisitor
+ {
+ StringBuilder sb = new StringBuilder();
+
+ /// <summary>
+ /// Gets the pretty printed text
+ /// </summary>
+ public string Output {
+ get {
+ return sb.ToString();
+ }
+ }
+
+ /// <summary> Create XML text from a document </summary>
+ public static string PrettyPrint(AXmlDocument doc)
+ {
+ PrettyPrintAXmlVisitor visitor = new PrettyPrintAXmlVisitor();
+ visitor.VisitDocument(doc);
+ return visitor.Output;
+ }
+
+ /// <summary> Visit RawDocument </summary>
+ public override void VisitDocument(AXmlDocument document)
+ {
+ base.VisitDocument(document);
+ }
+
+ /// <summary> Visit RawElement </summary>
+ public override void VisitElement(AXmlElement element)
+ {
+ base.VisitElement(element);
+ }
+
+ /// <summary> Visit RawTag </summary>
+ public override void VisitTag(AXmlTag tag)
+ {
+ sb.Append(tag.OpeningBracket);
+ sb.Append(tag.Name);
+ base.VisitTag(tag);
+ sb.Append(tag.ClosingBracket);
+ }
+
+ /// <summary> Visit RawAttribute </summary>
+ public override void VisitAttribute(AXmlAttribute attribute)
+ {
+ sb.Append(attribute.Name);
+ sb.Append(attribute.EqualsSign);
+ sb.Append(attribute.QuotedValue);
+ }
+
+ /// <summary> Visit RawText </summary>
+ public override void VisitText(AXmlText text)
+ {
+ sb.Append(text.EscapedValue);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/SyntaxError.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/SyntaxError.cs
new file mode 100644
index 000000000..4c2e5d636
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/SyntaxError.cs
@@ -0,0 +1,36 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Diagnostics;
+using System.Linq;
+
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ /// <summary> Information about syntax error that occured during parsing </summary>
+ public class SyntaxError: TextSegment
+ {
+ /// <summary> Object for which the error occured </summary>
+ public AXmlObject Object { get; internal set; }
+ /// <summary> Textual description of the error </summary>
+ public string Message { get; internal set; }
+ /// <summary> Any user data </summary>
+ public object Tag { get; set; }
+
+ internal SyntaxError Clone(AXmlObject newOwner)
+ {
+ return new SyntaxError {
+ Object = newOwner,
+ Message = Message,
+ Tag = Tag,
+ StartOffset = StartOffset,
+ EndOffset = EndOffset,
+ };
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TagMatchingHeuristics.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TagMatchingHeuristics.cs
new file mode 100644
index 000000000..52c692cd8
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TagMatchingHeuristics.cs
@@ -0,0 +1,439 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using Tango.Scripting.Editors.Utils;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ class TagMatchingHeuristics
+ {
+ const int maxConfigurationCount = 10;
+
+ AXmlParser parser;
+ TrackedSegmentCollection trackedSegments;
+ string input;
+ List<AXmlObject> tags;
+
+ public TagMatchingHeuristics(AXmlParser parser, string input, List<AXmlObject> tags)
+ {
+ this.parser = parser;
+ this.trackedSegments = parser.TrackedSegments;
+ this.input = input;
+ this.tags = tags;
+ }
+
+ public AXmlDocument ReadDocument()
+ {
+ AXmlDocument doc = new AXmlDocument() { Parser = parser };
+
+ // AXmlParser.Log("Flat stream: {0}", PrintObjects(tags));
+ List<AXmlObject> valid = MatchTags(tags);
+ // AXmlParser.Log("Fixed stream: {0}", PrintObjects(valid));
+ IEnumerator<AXmlObject> validStream = valid.GetEnumerator();
+ validStream.MoveNext(); // Move to first
+ while(true) {
+ // End of stream?
+ try {
+ if (validStream.Current == null) break;
+ } catch (InvalidCastException) {
+ break;
+ }
+ doc.AddChild(ReadTextOrElement(validStream));
+ }
+
+ if (doc.Children.Count > 0) {
+ doc.StartOffset = doc.FirstChild.StartOffset;
+ doc.EndOffset = doc.LastChild.EndOffset;
+ }
+
+ // Check well formed
+ foreach(AXmlTag xmlDeclaration in doc.Children.OfType<AXmlTag>().Where(t => t.IsProcessingInstruction && string.Equals(t.Name, "xml", StringComparison.OrdinalIgnoreCase))) {
+ if (xmlDeclaration.StartOffset != 0)
+ TagReader.OnSyntaxError(doc, xmlDeclaration.StartOffset, xmlDeclaration.StartOffset + 5,
+ "XML declaration must be at the start of document");
+ }
+ int elemCount = doc.Children.OfType<AXmlElement>().Count();
+ if (elemCount == 0)
+ TagReader.OnSyntaxError(doc, doc.EndOffset, doc.EndOffset,
+ "Root element is missing");
+ if (elemCount > 1) {
+ AXmlElement next = doc.Children.OfType<AXmlElement>().Skip(1).First();
+ TagReader.OnSyntaxError(doc, next.StartOffset, next.StartOffset,
+ "Only one root element is allowed");
+ }
+ foreach(AXmlTag tag in doc.Children.OfType<AXmlTag>()) {
+ if (tag.IsCData)
+ TagReader.OnSyntaxError(doc, tag.StartOffset, tag.EndOffset,
+ "CDATA not allowed in document root");
+ }
+ foreach(AXmlText text in doc.Children.OfType<AXmlText>()) {
+ if (!text.ContainsOnlyWhitespace)
+ TagReader.OnSyntaxError(doc, text.StartOffset, text.EndOffset,
+ "Only whitespace is allowed in document root");
+ }
+
+
+ AXmlParser.Log("Constructed {0}", doc);
+ trackedSegments.AddParsedObject(doc, null);
+ return doc;
+ }
+
+ static AXmlObject ReadSingleObject(IEnumerator<AXmlObject> objStream)
+ {
+ AXmlObject obj = objStream.Current;
+ objStream.MoveNext();
+ return obj;
+ }
+
+ 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);
+ }
+ }
+ }
+
+ AXmlElement ReadElement(IEnumerator<AXmlObject> objStream)
+ {
+ AXmlElement element = new AXmlElement();
+ element.IsProperlyNested = true;
+
+ // Read start tag
+ AXmlTag startTag = ReadSingleObject(objStream) as AXmlTag;
+ AXmlParser.DebugAssert(startTag != null, "Start tag expected");
+ AXmlParser.DebugAssert(startTag.IsStartOrEmptyTag || startTag == StartTagPlaceholder, "Start tag expected");
+ if (startTag == StartTagPlaceholder) {
+ element.HasStartOrEmptyTag = false;
+ element.IsProperlyNested = false;
+ TagReader.OnSyntaxError(element, objStream.Current.StartOffset, objStream.Current.EndOffset,
+ "Matching openning tag was not found");
+ } else {
+ element.HasStartOrEmptyTag = true;
+ element.AddChild(startTag);
+ }
+
+ // Read content and end tag
+ if (startTag == StartTagPlaceholder || // Check first in case the start tag is null
+ element.StartTag.IsStartTag)
+ {
+ while(true) {
+ AXmlTag currTag = objStream.Current as AXmlTag; // Peek
+ if (currTag == EndTagPlaceholder) {
+ TagReader.OnSyntaxError(element, element.LastChild.EndOffset, element.LastChild.EndOffset,
+ "Expected '</{0}>'", element.StartTag.Name);
+ ReadSingleObject(objStream);
+ element.HasEndTag = false;
+ element.IsProperlyNested = false;
+ break;
+ } else if (currTag != null && currTag.IsEndTag) {
+ if (element.HasStartOrEmptyTag && currTag.Name != element.StartTag.Name) {
+ TagReader.OnSyntaxError(element, currTag.StartOffset + 2, currTag.StartOffset + 2 + currTag.Name.Length,
+ "Expected '{0}'. End tag must have same name as start tag.", element.StartTag.Name);
+ }
+ element.AddChild(ReadSingleObject(objStream));
+ element.HasEndTag = true;
+ break;
+ }
+ AXmlObject nested = ReadTextOrElement(objStream);
+
+ AXmlElement nestedAsElement = nested as AXmlElement;
+ if (nestedAsElement != null) {
+ if (!nestedAsElement.IsProperlyNested)
+ element.IsProperlyNested = false;
+ element.AddChildren(Split(nestedAsElement).ToList());
+ } else {
+ element.AddChild(nested);
+ }
+ }
+ } else {
+ element.HasEndTag = false;
+ }
+
+ element.StartOffset = element.FirstChild.StartOffset;
+ element.EndOffset = element.LastChild.EndOffset;
+
+ AXmlParser.Assert(element.HasStartOrEmptyTag || element.HasEndTag, "Must have at least start or end tag");
+
+ AXmlParser.Log("Constructed {0}", element);
+ trackedSegments.AddParsedObject(element, null); // Need all elements in cache for offset tracking
+ return element;
+ }
+
+ 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;
+ }
+ }
+
+ 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;
+ }
+
+ /// <summary>
+ /// Stack of still unmatched start tags.
+ /// It includes the cost and backtack information.
+ /// </summary>
+ class Configuration
+ {
+ /// <summary> Unmatched start tags </summary>
+ public ImmutableStack<AXmlTag> StartTags { get; set; }
+ /// <summary> Properly nested tags </summary>
+ public ImmutableStack<AXmlObject> Document { get; set; }
+ /// <summary> Number of needed modificaitons to the document </summary>
+ public int Cost { get; set; }
+ }
+
+ /// <summary>
+ /// Dictionary which stores the cheapest configuration
+ /// </summary>
+ class Configurations: Dictionary<ImmutableStack<AXmlTag>, Configuration>
+ {
+ public Configurations()
+ {
+ }
+
+ public Configurations(IEnumerable<Configuration> configs)
+ {
+ foreach(Configuration config in configs) {
+ this.Add(config);
+ }
+ }
+
+ /// <summary> Overwrite only if cheaper </summary>
+ public void Add(Configuration newConfig)
+ {
+ Configuration oldConfig;
+ if (this.TryGetValue(newConfig.StartTags, out oldConfig)) {
+ if (newConfig.Cost < oldConfig.Cost) {
+ this[newConfig.StartTags] = newConfig;
+ }
+ } else {
+ base.Add(newConfig.StartTags, newConfig);
+ }
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ foreach(var kvp in this) {
+ sb.Append("\n - '");
+ foreach(AXmlTag startTag in kvp.Value.StartTags.Reverse()) {
+ sb.Append('<');
+ sb.Append(startTag.Name);
+ sb.Append('>');
+ }
+ sb.AppendFormat("' = {0}", kvp.Value.Cost);
+ }
+ return sb.ToString();
+ }
+ }
+
+ // Tags used to guide the element creation
+ readonly AXmlTag StartTagPlaceholder = new AXmlTag();
+ readonly AXmlTag EndTagPlaceholder = new AXmlTag();
+
+ /// <summary>
+ /// Add start or end tag placeholders so that the documment is properly nested
+ /// </summary>
+ List<AXmlObject> MatchTags(IEnumerable<AXmlObject> objs)
+ {
+ Configurations configurations = new Configurations();
+ configurations.Add(new Configuration {
+ StartTags = ImmutableStack<AXmlTag>.Empty,
+ Document = ImmutableStack<AXmlObject>.Empty,
+ Cost = 0,
+ });
+ foreach(AXmlObject obj in objs) {
+ configurations = ProcessObject(configurations, obj);
+ }
+ // Close any remaining start tags
+ foreach(Configuration conifg in configurations.Values) {
+ while(!conifg.StartTags.IsEmpty) {
+ conifg.StartTags = conifg.StartTags.Pop();
+ conifg.Document = conifg.Document.Push(EndTagPlaceholder);
+ conifg.Cost += 1;
+ }
+ }
+ // AXmlParser.Log("Configurations after closing all remaining tags:" + configurations.ToString());
+ Configuration bestConfig = configurations.Values.OrderBy(v => v.Cost).First();
+ AXmlParser.Log("Best configuration has cost {0}", bestConfig.Cost);
+
+ return bestConfig.Document.Reverse().ToList();
+ }
+
+ /// <summary> Get posible configurations after considering given object </summary>
+ Configurations ProcessObject(Configurations oldConfigs, AXmlObject obj)
+ {
+ AXmlParser.Log("Processing {0}", obj);
+
+ AXmlTag objAsTag = obj as AXmlTag;
+ AXmlElement objAsElement = obj as AXmlElement;
+ AXmlParser.DebugAssert(objAsTag != null || objAsElement != null || obj is AXmlText, obj.GetType().Name + " not expected");
+ if (objAsElement != null)
+ AXmlParser.Assert(objAsElement.IsProperlyNested, "Element not properly nested");
+
+ Configurations newConfigs = new Configurations();
+
+ foreach(var kvp in oldConfigs) {
+ Configuration oldConfig = kvp.Value;
+ var oldStartTags = oldConfig.StartTags;
+ var oldDocument = oldConfig.Document;
+ int oldCost = oldConfig.Cost;
+
+ if (objAsTag != null && objAsTag.IsStartTag) {
+ newConfigs.Add(new Configuration { // Push start-tag (cost 0)
+ StartTags = oldStartTags.Push(objAsTag),
+ Document = oldDocument.Push(objAsTag),
+ Cost = oldCost,
+ });
+ } else if (objAsTag != null && objAsTag.IsEndTag) {
+ newConfigs.Add(new Configuration { // Ignore (cost 1)
+ StartTags = oldStartTags,
+ Document = oldDocument.Push(StartTagPlaceholder).Push(objAsTag),
+ Cost = oldCost + 1,
+ });
+ if (!oldStartTags.IsEmpty && oldStartTags.Peek().Name != objAsTag.Name) {
+ newConfigs.Add(new Configuration { // Pop 1 item (cost 1) - not mathcing
+ StartTags = oldStartTags.Pop(),
+ Document = oldDocument.Push(objAsTag),
+ Cost = oldCost + 1,
+ });
+ }
+ int popedCount = 0;
+ var startTags = oldStartTags;
+ var doc = oldDocument;
+ foreach(AXmlTag poped in oldStartTags) {
+ popedCount++;
+ if (poped.Name == objAsTag.Name) {
+ newConfigs.Add(new Configuration { // Pop 'x' items (cost x-1) - last one is matching
+ StartTags = startTags.Pop(),
+ Document = doc.Push(objAsTag),
+ Cost = oldCost + popedCount - 1,
+ });
+ }
+ startTags = startTags.Pop();
+ doc = doc.Push(EndTagPlaceholder);
+ }
+ } else {
+ // Empty tag or other tag type or text or properly nested element
+ newConfigs.Add(new Configuration { // Ignore (cost 0)
+ StartTags = oldStartTags,
+ Document = oldDocument.Push(obj),
+ Cost = oldCost,
+ });
+ }
+ }
+
+ // Log("New configurations:" + newConfigs.ToString());
+
+ Configurations bestNewConfigurations = new Configurations(
+ newConfigs.Values.OrderBy(v => v.Cost).Take(maxConfigurationCount)
+ );
+
+ // AXmlParser.Log("Best new configurations:" + bestNewConfigurations.ToString());
+
+ return bestNewConfigurations;
+ }
+
+ #region Helper methods
+ /*
+ string PrintObjects(IEnumerable<AXmlObject> objs)
+ {
+ StringBuilder sb = new StringBuilder();
+ foreach(AXmlObject obj in objs) {
+ if (obj is AXmlTag) {
+ if (obj == StartTagPlaceholder) {
+ sb.Append("#StartTag#");
+ } else if (obj == EndTagPlaceholder) {
+ sb.Append("#EndTag#");
+ } else {
+ sb.Append(((AXmlTag)obj).OpeningBracket);
+ sb.Append(((AXmlTag)obj).Name);
+ sb.Append(((AXmlTag)obj).ClosingBracket);
+ }
+ } else if (obj is AXmlElement) {
+ sb.Append('[');
+ sb.Append(PrintObjects(((AXmlElement)obj).Children));
+ sb.Append(']');
+ } else if (obj is AXmlText) {
+ sb.Append('~');
+ } else {
+ throw new InternalException("Should not be here: " + obj);
+ }
+ }
+ return sb.ToString();
+ }
+ */
+ #endregion
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TagReader.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TagReader.cs
new file mode 100644
index 000000000..1a1a34f6b
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TagReader.cs
@@ -0,0 +1,740 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ class TagReader: TokenReader
+ {
+ AXmlParser parser;
+ TrackedSegmentCollection trackedSegments;
+ string input;
+
+ public TagReader(AXmlParser parser, string input): base(input)
+ {
+ this.parser = parser;
+ this.trackedSegments = parser.TrackedSegments;
+ this.input = input;
+ }
+
+ bool TryReadFromCacheOrNew<T>(out T res) where T: AXmlObject, new()
+ {
+ return TryReadFromCacheOrNew(out res, t => true);
+ }
+
+ bool TryReadFromCacheOrNew<T>(out T res, Predicate<T> condition) where T: AXmlObject, new()
+ {
+ T cached = trackedSegments.GetCachedObject<T>(this.CurrentLocation, 0, condition);
+ if (cached != null) {
+ Skip(cached.Length);
+ AXmlParser.Assert(cached.Length > 0, "cached elements must not have zero length");
+ res = cached;
+ return true;
+ } else {
+ res = new T();
+ return false;
+ }
+ }
+
+ void OnParsed(AXmlObject obj)
+ {
+ AXmlParser.Log("Parsed {0}", obj);
+ trackedSegments.AddParsedObject(obj, this.MaxTouchedLocation > this.CurrentLocation ? (int?)this.MaxTouchedLocation : null);
+ }
+
+ /// <summary>
+ /// Read all tags in the document in a flat sequence.
+ /// It also includes the text between tags and possibly some properly nested Elements from cache.
+ /// </summary>
+ public List<AXmlObject> ReadAllTags()
+ {
+ List<AXmlObject> stream = new List<AXmlObject>();
+
+ while(true) {
+ if (IsEndOfFile()) {
+ break;
+ } else if (TryPeek('<')) {
+ AXmlElement elem;
+ if (TryReadFromCacheOrNew(out elem, e => e.IsProperlyNested)) {
+ stream.Add(elem);
+ } else {
+ stream.Add(ReadTag());
+ }
+ } else {
+ stream.AddRange(ReadText(TextType.CharacterData));
+ }
+ }
+
+ return stream;
+ }
+
+ /// <summary>
+ /// Context: "&lt;"
+ /// </summary>
+ AXmlTag ReadTag()
+ {
+ AssertHasMoreData();
+
+ AXmlTag tag;
+ if (TryReadFromCacheOrNew(out tag)) return tag;
+
+ tag.StartOffset = this.CurrentLocation;
+
+ // Read the opening bracket
+ // It identifies the type of tag and parsing behavior for the rest of it
+ tag.OpeningBracket = ReadOpeningBracket();
+
+ if (tag.IsUnknownBang && !TryPeekWhiteSpace())
+ OnSyntaxError(tag, tag.StartOffset, this.CurrentLocation, "Unknown tag");
+
+ if (tag.IsStartOrEmptyTag || tag.IsEndTag || tag.IsProcessingInstruction) {
+ // Read the name
+ string name;
+ if (TryReadName(out name)) {
+ if (!IsValidName(name)) {
+ OnSyntaxError(tag, this.CurrentLocation - name.Length, this.CurrentLocation, "The name '{0}' is invalid", name);
+ }
+ } else {
+ OnSyntaxError(tag, "Element name expected");
+ }
+ tag.Name = name;
+ } else {
+ tag.Name = string.Empty;
+ }
+
+ bool isXmlDeclr = tag.StartOffset == 0 && tag.Name == "xml";
+
+ if (tag.IsStartOrEmptyTag || tag.IsEndTag || isXmlDeclr) {
+ // Read attributes for the tag
+ while(true) {
+ // Chech for all forbiden 'name' charcters first - see ReadName
+ if (IsEndOfFile()) break;
+ if (TryPeekWhiteSpace()) {
+ tag.AddChildren(ReadText(TextType.WhiteSpace));
+ continue; // End of file might be next
+ }
+ if (TryPeek('<')) break;
+ string endBr;
+ int endBrStart = this.CurrentLocation; // Just peek
+ if (TryReadClosingBracket(out endBr)) { // End tag
+ GoBack(endBrStart);
+ break;
+ }
+
+ // We have "=\'\"" or name - read attribute
+ AXmlAttribute attr = ReadAttribulte();
+ tag.AddChild(attr);
+ if (tag.IsEndTag)
+ OnSyntaxError(tag, attr.StartOffset, attr.EndOffset, "Attribute not allowed in end tag.");
+ }
+ } else if (tag.IsDocumentType) {
+ tag.AddChildren(ReadContentOfDTD());
+ } else {
+ int start = this.CurrentLocation;
+ IEnumerable<AXmlObject> text;
+ if (tag.IsComment) {
+ text = ReadText(TextType.Comment);
+ } else if (tag.IsCData) {
+ text = ReadText(TextType.CData);
+ } else if (tag.IsProcessingInstruction) {
+ text = ReadText(TextType.ProcessingInstruction);
+ } else if (tag.IsUnknownBang) {
+ text = ReadText(TextType.UnknownBang);
+ } else {
+ throw new InternalException(string.Format(CultureInfo.InvariantCulture, "Unknown opening bracket '{0}'", tag.OpeningBracket));
+ }
+ // Enumerate
+ text = text.ToList();
+ // Backtrack at complete start
+ if (IsEndOfFile() || (tag.IsUnknownBang && TryPeek('<'))) {
+ GoBack(start);
+ } else {
+ tag.AddChildren(text);
+ }
+ }
+
+ // Read closing bracket
+ string bracket;
+ TryReadClosingBracket(out bracket);
+ tag.ClosingBracket = bracket;
+
+ // Error check
+ int brStart = this.CurrentLocation - (tag.ClosingBracket ?? string.Empty).Length;
+ int brEnd = this.CurrentLocation;
+ if (tag.Name == null) {
+ // One error was reported already
+ } else if (tag.IsStartOrEmptyTag) {
+ if (tag.ClosingBracket != ">" && tag.ClosingBracket != "/>") OnSyntaxError(tag, brStart, brEnd, "'>' or '/>' expected");
+ } else if (tag.IsEndTag) {
+ if (tag.ClosingBracket != ">") OnSyntaxError(tag, brStart, brEnd, "'>' expected");
+ } else if (tag.IsComment) {
+ if (tag.ClosingBracket != "-->") OnSyntaxError(tag, brStart, brEnd, "'-->' expected");
+ } else if (tag.IsCData) {
+ if (tag.ClosingBracket != "]]>") OnSyntaxError(tag, brStart, brEnd, "']]>' expected");
+ } else if (tag.IsProcessingInstruction) {
+ if (tag.ClosingBracket != "?>") OnSyntaxError(tag, brStart, brEnd, "'?>' expected");
+ } else if (tag.IsUnknownBang) {
+ if (tag.ClosingBracket != ">") OnSyntaxError(tag, brStart, brEnd, "'>' expected");
+ } else if (tag.IsDocumentType) {
+ if (tag.ClosingBracket != ">") OnSyntaxError(tag, brStart, brEnd, "'>' expected");
+ } else {
+ throw new InternalException(string.Format(CultureInfo.InvariantCulture, "Unknown opening bracket '{0}'", tag.OpeningBracket));
+ }
+
+ // Attribute name may not apper multiple times
+ var duplicates = tag.Children.OfType<AXmlAttribute>().GroupBy(attr => attr.Name).SelectMany(g => g.Skip(1));
+ foreach(AXmlAttribute attr in duplicates) {
+ OnSyntaxError(tag, attr.StartOffset, attr.EndOffset, "Attribute with name '{0}' already exists", attr.Name);
+ }
+
+ tag.EndOffset = this.CurrentLocation;
+
+ OnParsed(tag);
+ return tag;
+ }
+
+ /// <summary>
+ /// Reads any of the know opening brackets. (only full bracket)
+ /// Context: "&lt;"
+ /// </summary>
+ string ReadOpeningBracket()
+ {
+ // We are using a lot of string literals so that the memory instances are shared
+ //int start = this.CurrentLocation;
+ if (TryRead('<')) {
+ if (TryRead('/')) {
+ return "</";
+ } else if (TryRead('?')) {
+ return "<?";
+ } else if (TryRead('!')) {
+ if (TryRead("--")) {
+ return "<!--";
+ } else if (TryRead("[CDATA[")) {
+ return "<![CDATA[";
+ } else {
+ foreach(string dtdName in AXmlTag.DtdNames) {
+ // the dtdName includes "<!"
+ if (TryRead(dtdName.Remove(0, 2))) return dtdName;
+ }
+ return "<!";
+ }
+ } else {
+ return "<";
+ }
+ } else {
+ throw new InternalException("'<' expected");
+ }
+ }
+
+ /// <summary>
+ /// Reads any of the know closing brackets. (only full bracket)
+ /// Context: any
+ /// </summary>
+ bool TryReadClosingBracket(out string bracket)
+ {
+ // We are using a lot of string literals so that the memory instances are shared
+ if (TryRead('>')) {
+ bracket = ">";
+ } else if (TryRead("/>")) {
+ bracket = "/>";
+ } else if (TryRead("?>")) {
+ bracket = "?>";
+ } else if (TryRead("-->")) {
+ bracket = "-->";
+ } else if (TryRead("]]>")) {
+ bracket = "]]>";
+ } else {
+ bracket = string.Empty;
+ return false;
+ }
+ return true;
+ }
+
+ IEnumerable<AXmlObject> ReadContentOfDTD()
+ {
+ int start = this.CurrentLocation;
+ while(true) {
+ if (IsEndOfFile()) break; // End of file
+ TryMoveToNonWhiteSpace(); // Skip whitespace
+ if (TryRead('\'')) TryMoveTo('\''); // Skip single quoted string TODO: Bug
+ if (TryRead('\"')) TryMoveTo('\"'); // Skip single quoted string
+ if (TryRead('[')) { // Start of nested infoset
+ // Reading infoset
+ while(true) {
+ if (IsEndOfFile()) break;
+ TryMoveToAnyOf('<', ']');
+ if (TryPeek('<')) {
+ if (start != this.CurrentLocation) { // Two following tags
+ yield return MakeText(start, this.CurrentLocation);
+ }
+ yield return ReadTag();
+ start = this.CurrentLocation;
+ }
+ if (TryPeek(']')) break;
+ }
+ }
+ TryRead(']'); // End of nested infoset
+ if (TryPeek('>')) break; // Proper closing
+ if (TryPeek('<')) break; // Malformed XML
+ TryMoveNext(); // Skip anything else
+ }
+ if (start != this.CurrentLocation) {
+ yield return MakeText(start, this.CurrentLocation);
+ }
+ }
+
+ /// <summary>
+ /// Context: name or "=\'\""
+ /// </summary>
+ AXmlAttribute ReadAttribulte()
+ {
+ AssertHasMoreData();
+
+ AXmlAttribute attr;
+ if (TryReadFromCacheOrNew(out attr)) return attr;
+
+ attr.StartOffset = this.CurrentLocation;
+
+ // Read name
+ string name;
+ if (TryReadName(out name)) {
+ if (!IsValidName(name)) {
+ OnSyntaxError(attr, this.CurrentLocation - name.Length, this.CurrentLocation, "The name '{0}' is invalid", name);
+ }
+ } else {
+ OnSyntaxError(attr, "Attribute name expected");
+ }
+ attr.Name = name;
+
+ // Read equals sign and surrounding whitespace
+ int checkpoint = this.CurrentLocation;
+ TryMoveToNonWhiteSpace();
+ if (TryRead('=')) {
+ int chk2 = this.CurrentLocation;
+ TryMoveToNonWhiteSpace();
+ if (!TryPeek('"') && !TryPeek('\'')) {
+ // Do not read whitespace if quote does not follow
+ GoBack(chk2);
+ }
+ attr.EqualsSign = GetText(checkpoint, this.CurrentLocation);
+ } else {
+ GoBack(checkpoint);
+ OnSyntaxError(attr, "'=' expected");
+ attr.EqualsSign = string.Empty;
+ }
+
+ // Read attribute value
+ int start = this.CurrentLocation;
+ char quoteChar = TryPeek('"') ? '"' : '\'';
+ bool startsWithQuote;
+ if (TryRead(quoteChar)) {
+ startsWithQuote = true;
+ int valueStart = this.CurrentLocation;
+ TryMoveToAnyOf(quoteChar, '<');
+ if (TryRead(quoteChar)) {
+ if (!TryPeekAnyOf(' ', '\t', '\n', '\r', '/', '>', '?')) {
+ if (TryPeekPrevious('=', 2) || (TryPeekPrevious('=', 3) && TryPeekPrevious(' ', 2))) {
+ // This actually most likely means that we are in the next attribute value
+ GoBack(valueStart);
+ ReadAttributeValue(quoteChar);
+ if (TryRead(quoteChar)) {
+ OnSyntaxError(attr, "White space or end of tag expected");
+ } else {
+ OnSyntaxError(attr, "Quote {0} expected (or add whitespace after the following one)", quoteChar);
+ }
+ } else {
+ OnSyntaxError(attr, "White space or end of tag expected");
+ }
+ }
+ } else {
+ // '<' or end of file
+ GoBack(valueStart);
+ ReadAttributeValue(quoteChar);
+ OnSyntaxError(attr, "Quote {0} expected", quoteChar);
+ }
+ } else {
+ startsWithQuote = false;
+ int valueStart = this.CurrentLocation;
+ ReadAttributeValue(null);
+ TryRead('\"');
+ TryRead('\'');
+ if (valueStart == this.CurrentLocation) {
+ OnSyntaxError(attr, "Attribute value expected");
+ } else {
+ OnSyntaxError(attr, valueStart, this.CurrentLocation, "Attribute value must be quoted");
+ }
+ }
+ attr.QuotedValue = GetText(start, this.CurrentLocation);
+ attr.Value = Unquote(attr.QuotedValue);
+ attr.Value = Dereference(attr, attr.Value, startsWithQuote ? start + 1 : start);
+
+ attr.EndOffset = this.CurrentLocation;
+
+ OnParsed(attr);
+ return attr;
+ }
+
+ /// <summary>
+ /// Read everything up to quote (excluding), opening/closing tag or attribute signature
+ /// </summary>
+ void ReadAttributeValue(char? quote)
+ {
+ while(true) {
+ if (IsEndOfFile()) return;
+ // What is next?
+ int start = this.CurrentLocation;
+ TryMoveToNonWhiteSpace(); // Read white space (if any)
+ if (quote.HasValue) {
+ if (TryPeek(quote.Value)) return;
+ } else {
+ if (TryPeek('"') || TryPeek('\'')) return;
+ }
+ // Opening/closing tag
+ string endBr;
+ if (TryPeek('<') || TryReadClosingBracket(out endBr)) {
+ GoBack(start);
+ return;
+ }
+ // Try reading attribute signature
+ string name;
+ if (TryReadName(out name)) {
+ int nameEnd = this.CurrentLocation;
+ if (TryMoveToNonWhiteSpace() && TryRead("=") &&
+ TryMoveToNonWhiteSpace() && TryPeekAnyOf('"', '\''))
+ {
+ // Start of attribute. Great
+ GoBack(start);
+ return; // Done
+ } else {
+ // Just some gargabe - make it part of the value
+ GoBack(nameEnd);
+ continue; // Read more
+ }
+ }
+ TryMoveNext(); // Accept everyting else
+ }
+ }
+
+ AXmlText MakeText(int start, int end)
+ {
+ AXmlParser.DebugAssert(end > start, "Empty text");
+
+ AXmlText text = new AXmlText() {
+ StartOffset = start,
+ EndOffset = end,
+ EscapedValue = GetText(start, end),
+ Type = TextType.Other
+ };
+
+ OnParsed(text);
+ return text;
+ }
+
+ const int maxEntityLength = 16; // The longest build-in one is 10 ("&#1114111;")
+ const int maxTextFragmentSize = 64;
+ const int lookAheadLength = (3 * maxTextFragmentSize) / 2; // More so that we do not get small "what was inserted" fragments
+
+ /// <summary>
+ /// Reads text and optionaly separates it into fragments.
+ /// It can also return empty set for no appropriate text input.
+ /// Make sure you enumerate it only once
+ /// </summary>
+ IEnumerable<AXmlObject> ReadText(TextType type)
+ {
+ bool lookahead = false;
+ while(true) {
+ AXmlText text;
+ if (TryReadFromCacheOrNew(out text, t => t.Type == type)) {
+ // Cached text found
+ yield return text;
+ continue; // Read next fragment; the method can handle "no text left"
+ }
+ text.Type = type;
+
+ // Limit the reading to just a few characters
+ // (the first character not to be read)
+ int fragmentEnd = Math.Min(this.CurrentLocation + maxTextFragmentSize, this.InputLength);
+
+ // Look if some futher text has been already processed and align so that
+ // we hit that chache point. It is expensive so it is off for the first run
+ if (lookahead) {
+ // Note: Must fit entity
+ AXmlObject nextFragment = trackedSegments.GetCachedObject<AXmlText>(this.CurrentLocation + maxEntityLength, lookAheadLength - maxEntityLength, t => t.Type == type);
+ if (nextFragment != null) {
+ fragmentEnd = Math.Min(nextFragment.StartOffset, this.InputLength);
+ AXmlParser.Log("Parsing only text ({0}-{1}) because later text was already processed", this.CurrentLocation, fragmentEnd);
+ }
+ }
+ lookahead = true;
+
+ text.StartOffset = this.CurrentLocation;
+ int start = this.CurrentLocation;
+
+ // Whitespace would be skipped anyway by any operation
+ TryMoveToNonWhiteSpace(fragmentEnd);
+ int wsEnd = this.CurrentLocation;
+
+ // Try move to the terminator given by the context
+ if (type == TextType.WhiteSpace) {
+ TryMoveToNonWhiteSpace(fragmentEnd);
+ } else if (type == TextType.CharacterData) {
+ while(true) {
+ if (!TryMoveToAnyOf(new char[] {'<', ']'}, fragmentEnd)) break; // End of fragment
+ if (TryPeek('<')) break;
+ if (TryPeek(']')) {
+ if (TryPeek("]]>")) {
+ OnSyntaxError(text, this.CurrentLocation, this.CurrentLocation + 3, "']]>' is not allowed in text");
+ }
+ TryMoveNext();
+ continue;
+ }
+ throw new Exception("Infinite loop");
+ }
+ } else if (type == TextType.Comment) {
+ // Do not report too many errors
+ bool errorReported = false;
+ while(true) {
+ if (!TryMoveTo('-', fragmentEnd)) break; // End of fragment
+ if (TryPeek("-->")) break;
+ if (TryPeek("--") && !errorReported) {
+ OnSyntaxError(text, this.CurrentLocation, this.CurrentLocation + 2, "'--' is not allowed in comment");
+ errorReported = true;
+ }
+ TryMoveNext();
+ }
+ } else if (type == TextType.CData) {
+ while(true) {
+ // We can not use use TryMoveTo("]]>", fragmentEnd) because it may incorectly accept "]" at the end of fragment
+ if (!TryMoveTo(']', fragmentEnd)) break; // End of fragment
+ if (TryPeek("]]>")) break;
+ TryMoveNext();
+ }
+ } else if (type == TextType.ProcessingInstruction) {
+ while(true) {
+ if (!TryMoveTo('?', fragmentEnd)) break; // End of fragment
+ if (TryPeek("?>")) break;
+ TryMoveNext();
+ }
+ } else if (type == TextType.UnknownBang) {
+ TryMoveToAnyOf(new char[] {'<', '>'}, fragmentEnd);
+ } else {
+ throw new Exception("Uknown type " + type);
+ }
+
+ text.ContainsOnlyWhitespace = (wsEnd == this.CurrentLocation);
+
+ // Terminal found or real end was reached;
+ bool finished = this.CurrentLocation < fragmentEnd || IsEndOfFile();
+
+ if (!finished) {
+ // We have to continue reading more text fragments
+
+ // If there is entity reference, make sure the next segment starts with it to prevent framentation
+ int entitySearchStart = Math.Max(start + 1 /* data for us */, this.CurrentLocation - maxEntityLength);
+ int entitySearchLength = this.CurrentLocation - entitySearchStart;
+ if (entitySearchLength > 0) {
+ // Note that LastIndexOf works backward
+ int entityIndex = input.LastIndexOf('&', this.CurrentLocation - 1, entitySearchLength);
+ if (entityIndex != -1) {
+ GoBack(entityIndex);
+ }
+ }
+ }
+
+ text.EscapedValue = GetText(start, this.CurrentLocation);
+ if (type == TextType.CharacterData) {
+ // Normalize end of line first
+ text.Value = Dereference(text, NormalizeEndOfLine(text.EscapedValue), start);
+ } else {
+ text.Value = text.EscapedValue;
+ }
+ text.EndOffset = this.CurrentLocation;
+
+ if (text.EscapedValue.Length > 0) {
+ OnParsed(text);
+ yield return text;
+ }
+
+ if (finished) {
+ yield break;
+ }
+ }
+ }
+
+ #region Helper methods
+
+ void OnSyntaxError(AXmlObject obj, string message, params object[] args)
+ {
+ OnSyntaxError(obj, this.CurrentLocation, this.CurrentLocation + 1, message, args);
+ }
+
+ 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,
+ });
+ }
+
+ static bool IsValidName(string name)
+ {
+ try {
+ System.Xml.XmlConvert.VerifyName(name);
+ return true;
+ } catch (System.Xml.XmlException) {
+ return false;
+ }
+ }
+
+ /// <summary> Remove quoting from the given string </summary>
+ static string Unquote(string quoted)
+ {
+ if (string.IsNullOrEmpty(quoted)) return string.Empty;
+ char first = quoted[0];
+ if (quoted.Length == 1) return (first == '"' || first == '\'') ? string.Empty : quoted;
+ char last = quoted[quoted.Length - 1];
+ if (first == '"' || first == '\'') {
+ if (first == last) {
+ // Remove both quotes
+ return quoted.Substring(1, quoted.Length - 2);
+ } else {
+ // Remove first quote
+ return quoted.Remove(0, 1);
+ }
+ } else {
+ if (last == '"' || last == '\'') {
+ // Remove last quote
+ return quoted.Substring(0, quoted.Length - 1);
+ } else {
+ // Keep whole string
+ return quoted;
+ }
+ }
+ }
+
+ static string NormalizeEndOfLine(string text)
+ {
+ return text.Replace("\r\n", "\n").Replace("\r", "\n");
+ }
+
+ 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;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TextType.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TextType.cs
new file mode 100644
index 000000000..965185925
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TextType.cs
@@ -0,0 +1,39 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Diagnostics;
+using System.Linq;
+
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ /// <summary> Identifies the context in which the text occured </summary>
+ enum TextType
+ {
+ /// <summary> Ends with non-whitespace </summary>
+ WhiteSpace,
+
+ /// <summary> Ends with "&lt;"; "]]&gt;" is error </summary>
+ CharacterData,
+
+ /// <summary> Ends with "-->"; "--" is error </summary>
+ Comment,
+
+ /// <summary> Ends with "]]&gt;" </summary>
+ CData,
+
+ /// <summary> Ends with "?>" </summary>
+ ProcessingInstruction,
+
+ /// <summary> Ends with "&lt;" or ">" </summary>
+ UnknownBang,
+
+ /// <summary> Unknown </summary>
+ Other
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TokenReader.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TokenReader.cs
new file mode 100644
index 000000000..83d3315ed
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TokenReader.cs
@@ -0,0 +1,309 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ class TokenReader
+ {
+ string input;
+ int inputLength;
+ int currentLocation;
+
+ // CurrentLocation is assumed to be touched and the fact does not
+ // have to be recorded in this variable.
+ // This stores any value bigger than that if applicable.
+ // Acutal value is max(currentLocation, maxTouchedLocation).
+ int maxTouchedLocation;
+
+ public int InputLength {
+ get { return inputLength; }
+ }
+
+ public int CurrentLocation {
+ get { return currentLocation; }
+ }
+
+ public int MaxTouchedLocation {
+ get { return Math.Max(currentLocation, maxTouchedLocation); }
+ }
+
+ public TokenReader(string input)
+ {
+ this.input = input;
+ this.inputLength = input.Length;
+ }
+
+ protected bool IsEndOfFile()
+ {
+ return currentLocation == inputLength;
+ }
+
+ protected bool HasMoreData()
+ {
+ return currentLocation < inputLength;
+ }
+
+ protected void AssertHasMoreData()
+ {
+ AXmlParser.Assert(HasMoreData(), "Unexpected end of file");
+ }
+
+ protected bool TryMoveNext()
+ {
+ if (currentLocation == inputLength) return false;
+
+ currentLocation++;
+ return true;
+ }
+
+ protected void Skip(int count)
+ {
+ AXmlParser.Assert(currentLocation + count <= inputLength, "Skipping after the end of file");
+ currentLocation += count;
+ }
+
+ protected void GoBack(int oldLocation)
+ {
+ AXmlParser.Assert(oldLocation <= currentLocation, "Trying to move forward");
+ maxTouchedLocation = Math.Max(maxTouchedLocation, currentLocation);
+ currentLocation = oldLocation;
+ }
+
+ protected bool TryRead(char c)
+ {
+ if (currentLocation == inputLength) return false;
+
+ if (input[currentLocation] == c) {
+ currentLocation++;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ protected bool TryReadAnyOf(params char[] c)
+ {
+ if (currentLocation == inputLength) return false;
+
+ if (c.Contains(input[currentLocation])) {
+ currentLocation++;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ protected bool TryRead(string text)
+ {
+ if (TryPeek(text)) {
+ currentLocation += text.Length;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ protected bool TryPeekPrevious(char c, int back)
+ {
+ if (currentLocation - back == inputLength) return false;
+ if (currentLocation - back < 0 ) return false;
+
+ return input[currentLocation - back] == c;
+ }
+
+ protected bool TryPeek(char c)
+ {
+ if (currentLocation == inputLength) return false;
+
+ return input[currentLocation] == c;
+ }
+
+ protected bool TryPeekAnyOf(params char[] chars)
+ {
+ if (currentLocation == inputLength) return false;
+
+ return chars.Contains(input[currentLocation]);
+ }
+
+ protected bool TryPeek(string text)
+ {
+ if (!TryPeek(text[0])) return false; // Early exit
+
+ maxTouchedLocation = Math.Max(maxTouchedLocation, currentLocation + (text.Length - 1));
+ // The following comparison 'touches' the end of file - it does depend on the end being there
+ if (currentLocation + text.Length > inputLength) return false;
+
+ return input.Substring(currentLocation, text.Length) == text;
+ }
+
+ protected bool TryPeekWhiteSpace()
+ {
+ if (currentLocation == inputLength) return false;
+
+ char c = input[currentLocation];
+ return ((int)c <= 0x20) && (c == ' ' || c == '\t' || c == '\n' || c == '\r');
+ }
+
+ // The move functions do not have to move if already at target
+ // The move functions allow 'overriding' of the document length
+
+ protected bool TryMoveTo(char c)
+ {
+ return TryMoveTo(c, inputLength);
+ }
+
+ protected bool TryMoveTo(char c, int inputLength)
+ {
+ if (currentLocation == inputLength) return false;
+ int index = input.IndexOf(c, currentLocation, inputLength - currentLocation);
+ if (index != -1) {
+ currentLocation = index;
+ return true;
+ } else {
+ currentLocation = inputLength;
+ return false;
+ }
+ }
+
+ protected bool TryMoveToAnyOf(params char[] c)
+ {
+ return TryMoveToAnyOf(c, inputLength);
+ }
+
+ protected bool TryMoveToAnyOf(char[] c, int inputLength)
+ {
+ if (currentLocation == inputLength) return false;
+ int index = input.IndexOfAny(c, currentLocation, inputLength - currentLocation);
+ if (index != -1) {
+ currentLocation = index;
+ return true;
+ } else {
+ currentLocation = inputLength;
+ return false;
+ }
+ }
+
+ protected bool TryMoveTo(string text)
+ {
+ return TryMoveTo(text, inputLength);
+ }
+
+ protected bool TryMoveTo(string text, int inputLength)
+ {
+ if (currentLocation == inputLength) return false;
+ int index = input.IndexOf(text, currentLocation, inputLength - currentLocation, StringComparison.Ordinal);
+ if (index != -1) {
+ maxTouchedLocation = index + text.Length - 1;
+ currentLocation = index;
+ return true;
+ } else {
+ currentLocation = inputLength;
+ return false;
+ }
+ }
+
+ protected bool TryMoveToNonWhiteSpace()
+ {
+ return TryMoveToNonWhiteSpace(inputLength);
+ }
+
+ protected bool TryMoveToNonWhiteSpace(int inputLength)
+ {
+ while(true) {
+ if (currentLocation == inputLength) return false; // Reject end of file
+ char c = input[currentLocation];
+ if (((int)c <= 0x20) && (c == ' ' || c == '\t' || c == '\n' || c == '\r')) {
+ currentLocation++; // Accept white-space
+ continue;
+ } else {
+ return true; // Found non-white-space
+ }
+ }
+ }
+
+ /// <summary>
+ /// Read a name token.
+ /// The following characters are not allowed:
+ /// "" End of file
+ /// " \n\r\t" Whitesapce
+ /// "=\'\"" Attribute value
+ /// "&lt;>/?" Tags
+ /// </summary>
+ /// <returns> True if read at least one character </returns>
+ protected bool TryReadName(out string res)
+ {
+ int start = currentLocation;
+ // Keep reading up to invalid character
+ while(true) {
+ if (currentLocation == inputLength) break; // Reject end of file
+ char c = input[currentLocation];
+ if (0x41 <= (int)c) { // Accpet from 'A' onwards
+ currentLocation++;
+ continue;
+ }
+ if (c == ' ' || c == '\n' || c == '\r' || c == '\t' || // Reject whitesapce
+ c == '=' || c == '\'' || c == '"' || // Reject attributes
+ c == '<' || c == '>' || c == '/' || c == '?') { // Reject tags
+ break;
+ } else {
+ currentLocation++;
+ continue; // Accept other character
+ }
+ }
+ if (start == currentLocation) {
+ res = string.Empty;
+ return false;
+ } else {
+ res = GetText(start, currentLocation);
+ return true;
+ }
+ }
+
+ protected string GetText(int start, int end)
+ {
+ AXmlParser.Assert(end <= currentLocation, "Reading ahead of current location");
+ if (start == inputLength && end == inputLength) {
+ return string.Empty;
+ } else {
+ return GetCachedString(input.Substring(start, end - start));
+ }
+ }
+
+ Dictionary<string, string> stringCache = new Dictionary<string, string>();
+ int stringCacheRequestedCount;
+ int stringCacheRequestedSize;
+ int stringCacheStoredCount;
+ int stringCacheStoredSize;
+
+ string GetCachedString(string cached)
+ {
+ stringCacheRequestedCount += 1;
+ stringCacheRequestedSize += 8 + 2 * cached.Length;
+ // Do not bother with long strings
+ if (cached.Length > 32) {
+ stringCacheStoredCount += 1;
+ stringCacheStoredSize += 8 + 2 * cached.Length;
+ return cached;
+ }
+ if (stringCache.ContainsKey(cached)) {
+ // Get the instance from the cache instead
+ return stringCache[cached];
+ } else {
+ // Add to cache
+ stringCacheStoredCount += 1;
+ stringCacheStoredSize += 8 + 2 * cached.Length;
+ stringCache.Add(cached, cached);
+ return cached;
+ }
+ }
+
+ public void PrintStringCacheStats()
+ {
+ AXmlParser.Log("String cache: Requested {0} ({1} bytes); Actaully stored {2} ({3} bytes); {4}% stored", stringCacheRequestedCount, stringCacheRequestedSize, stringCacheStoredCount, stringCacheStoredSize, stringCacheRequestedSize == 0 ? 0 : stringCacheStoredSize * 100 / stringCacheRequestedSize);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TrackedSegmentCollection.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TrackedSegmentCollection.cs
new file mode 100644
index 000000000..f8e516591
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/TrackedSegmentCollection.cs
@@ -0,0 +1,165 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Xml
+{
+ /// <summary>
+ /// Holds all objects that need to keep offsets up to date.
+ /// </summary>
+ class TrackedSegmentCollection
+ {
+ /// <summary>
+ /// Holds all types of objects in one collection.
+ /// </summary>
+ TextSegmentCollection<TextSegment> segments = new TextSegmentCollection<TextSegment>();
+
+ /// <summary>
+ /// Is used to identify what memory range was touched by object
+ /// The default is (StartOffset, EndOffset + 1) which is not stored
+ /// </summary>
+ class TouchedRange: TextSegment
+ {
+ public AXmlObject TouchedByObject { get; set; }
+ }
+
+ public void UpdateOffsetsAndInvalidate(IEnumerable<DocumentChangeEventArgs> changes)
+ {
+ foreach(DocumentChangeEventArgs change in changes) {
+ // Update offsets of all items
+ segments.UpdateOffsets(change);
+
+ // Remove any items affected by the change
+ AXmlParser.Log("Changed {0}-{1}", change.Offset, change.Offset + change.InsertionLength);
+ // Removing will cause one of the ends to be set to change.Offset
+ // FindSegmentsContaining includes any segments touching
+ // so that conviniently takes care of the +1 byte
+ var segmentsContainingOffset = segments.FindOverlappingSegments(change.Offset, change.InsertionLength);
+ foreach(AXmlObject obj in segmentsContainingOffset.OfType<AXmlObject>().Where(o => o.IsCached)) {
+ InvalidateCache(obj, false);
+ }
+ foreach(TouchedRange range in segmentsContainingOffset.OfType<TouchedRange>()) {
+ AXmlParser.Log("Found that {0} dependeds on ({1}-{2})", range.TouchedByObject, range.StartOffset, range.EndOffset);
+ InvalidateCache(range.TouchedByObject, true);
+ segments.Remove(range);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Invlidates all objects. That is, the whole document has changed.
+ /// </summary>
+ /// <remarks> We still have to keep the items becuase they might be in the document </remarks>
+ public void InvalidateAll()
+ {
+ AXmlParser.Log("Invalidating all objects");
+ foreach(AXmlObject obj in segments.OfType<AXmlObject>()) {
+ obj.IsCached = 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);
+ }
+ }
+
+ /// <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);
+
+ AXmlContainer container = obj as AXmlContainer;
+ if (container != null) {
+ foreach (AXmlObject child in container.Children) {
+ RemoveParsedObject(child);
+ }
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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;
+ }
+ }
+ }
+
+ /// <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);
+ }
+
+ public T GetCachedObject<T>(int offset, int lookaheadCount, Predicate<T> conditon) where T: AXmlObject, new()
+ {
+ TextSegment obj = segments.FindFirstSegmentWithStartAfter(offset);
+ while(obj != null && offset <= obj.StartOffset && obj.StartOffset <= offset + lookaheadCount) {
+ if (obj is T && ((AXmlObject)obj).IsCached && conditon((T)obj)) {
+ return (T)obj;
+ }
+ obj = segments.GetNextSegment(obj);
+ }
+ return null;
+ }
+ }
+}