diff --git a/ICSharpCode.NRefactory.Xml/.gitignore b/ICSharpCode.NRefactory.Xml/.gitignore
new file mode 100644
index 0000000000..9ce745d95d
--- /dev/null
+++ b/ICSharpCode.NRefactory.Xml/.gitignore
@@ -0,0 +1,3 @@
+
+bin/
+obj/
\ No newline at end of file
diff --git a/ICSharpCode.NRefactory.Xml/AXmlAttribute.cs b/ICSharpCode.NRefactory.Xml/AXmlAttribute.cs
new file mode 100644
index 0000000000..a7adbeae73
--- /dev/null
+++ b/ICSharpCode.NRefactory.Xml/AXmlAttribute.cs
@@ -0,0 +1,61 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using ICSharpCode.NRefactory.Editor;
+
+namespace ICSharpCode.NRefactory.Xml
+{
+ ///
+ /// Name-value pair in a tag
+ ///
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
+ public class AXmlAttribute : AXmlObject
+ {
+ internal AXmlAttribute(AXmlObject parent, int startOffset, InternalAttribute internalObject)
+ : base(parent, startOffset, internalObject)
+ {
+ }
+
+ internal InternalAttribute InternalAttribute {
+ get { return (InternalAttribute)internalObject; }
+ }
+
+ /// Name with namespace prefix - exactly as in source file
+ public string Name { get { return InternalAttribute.Name; } }
+
+ /// Unquoted and dereferenced value of the attribute
+ public string Value { get { return InternalAttribute.Value; } }
+
+ /// Gets the segment for the attribute name
+ public ISegment NameSegment {
+ get { return new XmlSegment(startOffset, startOffset + Name.Length); }
+ }
+
+ /// Gets the segment for the attribute value, including the quotes
+ public ISegment ValueSegment {
+ get { return new XmlSegment(startOffset + Name.Length + InternalAttribute.EqualsSignLength, this.EndOffset); }
+ }
+
+ ///
+ public override void AcceptVisitor(IAXmlVisitor visitor)
+ {
+ visitor.VisitAttribute(this);
+ }
+ }
+}
diff --git a/ICSharpCode.NRefactory.Xml/AXmlObject.cs b/ICSharpCode.NRefactory.Xml/AXmlObject.cs
new file mode 100644
index 0000000000..826d4ebe04
--- /dev/null
+++ b/ICSharpCode.NRefactory.Xml/AXmlObject.cs
@@ -0,0 +1,163 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using ICSharpCode.NRefactory.Editor;
+using ICSharpCode.NRefactory.Utils;
+
+namespace ICSharpCode.NRefactory.Xml
+{
+ ///
+ /// XML object.
+ ///
+ public abstract class AXmlObject : ISegment
+ {
+ readonly AXmlObject parent;
+ internal readonly int startOffset;
+ internal readonly InternalObject internalObject;
+ IList children;
+
+ internal AXmlObject(AXmlObject parent, int startOffset, InternalObject internalObject)
+ {
+ this.parent = parent;
+ this.startOffset = startOffset;
+ this.internalObject = internalObject;
+ }
+
+ ///
+ /// Gets the parent node.
+ ///
+ public AXmlObject Parent {
+ get { return parent; }
+ }
+
+ ///
+ /// Gets the list of child objects.
+ ///
+ public IList Children {
+ get {
+ var result = this.children;
+ if (result != null) {
+ LazyInit.ReadBarrier();
+ return result;
+ } else {
+ if (internalObject.NestedObjects != null) {
+ var array = new AXmlObject[internalObject.NestedObjects.Length];
+ for (int i = 0; i < array.Length; i++) {
+ array[i] = internalObject.NestedObjects[i].CreatePublicObject(this, startOffset);
+ }
+ result = Array.AsReadOnly(array);
+ } else {
+ result = EmptyList.Instance;
+ }
+ return LazyInit.GetOrSet(ref this.children, result);
+ }
+ }
+ }
+
+ ///
+ /// The error that occured in the context of this node (excluding nested nodes)
+ ///
+ public IEnumerable MySyntaxErrors {
+ get {
+ if (internalObject.SyntaxErrors != null) {
+ return internalObject.SyntaxErrors.Select(e => new SyntaxError(startOffset + e.RelativeStart, startOffset + e.RelativeEnd, e.Description));
+ } else {
+ return EmptyList.Instance;
+ }
+ }
+ }
+
+ ///
+ /// The error that occured in the context of this node and all nested nodes.
+ /// It has O(n) cost.
+ ///
+ public IEnumerable SyntaxErrors {
+ get {
+ return TreeTraversal.PreOrder(this, n => n.Children).SelectMany(obj => obj.MySyntaxErrors);
+ }
+ }
+
+ /// Get all ancestors of this node
+ public IEnumerable Ancestors {
+ get {
+ AXmlObject curr = this.Parent;
+ while(curr != null) {
+ yield return curr;
+ curr = curr.Parent;
+ }
+ }
+ }
+
+ #region Helper methods
+
+ /// The part of name before ":"
+ /// Empty string if not found
+ 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;
+ }
+ }
+
+ /// The part of name after ":"
+ /// Whole name if ":" not found
+ 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
+
+ /// Call appropriate visit method on the given visitor
+ public abstract void AcceptVisitor(IAXmlVisitor visitor);
+
+ ///
+ /// Gets the start offset of the segment.
+ ///
+ public int StartOffset {
+ get { return startOffset; }
+ }
+
+ int ISegment.Offset {
+ get { return startOffset; }
+ }
+
+ ///
+ public int Length {
+ get { return internalObject.Length; }
+ }
+
+ ///
+ public int EndOffset {
+ get { return startOffset + internalObject.Length; }
+ }
+ }
+}
diff --git a/ICSharpCode.NRefactory.Xml/AXmlTag.cs b/ICSharpCode.NRefactory.Xml/AXmlTag.cs
new file mode 100644
index 0000000000..3584053d1b
--- /dev/null
+++ b/ICSharpCode.NRefactory.Xml/AXmlTag.cs
@@ -0,0 +1,91 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.ObjectModel;
+using ICSharpCode.NRefactory.Editor;
+
+namespace ICSharpCode.NRefactory.Xml
+{
+ ///
+ /// Represents any markup starting with "<" and (hopefully) ending with ">"
+ ///
+ public class AXmlTag : AXmlObject
+ {
+ /// These identify the start of DTD elements
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification="ReadOnlyCollection is immutable")]
+ public static readonly ReadOnlyCollection DtdNames = new ReadOnlyCollection(
+ new string[] {" Opening bracket - usually "<"
+ public string OpeningBracket {
+ get { return internalObject.OpeningBracket; }
+ }
+
+ /// Name following the opening bracket
+ public string Name {
+ get { return internalObject.Name; }
+ }
+
+ /// Gets the segment containing the tag name
+ public ISegment NameSegment {
+ get {
+ int start = startOffset + internalObject.RelativeNameStart;
+ return new XmlSegment(start, start + internalObject.Name.Length);
+ }
+ }
+
+ /// Closing bracket - usually ">"
+ public string ClosingBracket {
+ get { return internalObject.ClosingBracket; }
+ }
+
+ /// True if tag starts with "<"
+ public bool IsStartOrEmptyTag { get { return internalObject.IsStartOrEmptyTag; } }
+ /// True if tag starts with "<" and ends with ">"
+ public bool IsStartTag { get { return internalObject.IsStartOrEmptyTag && ClosingBracket == ">"; } }
+ /// True if tag starts with "<" and does not end with ">"
+ public bool IsEmptyTag { get { return internalObject.IsStartOrEmptyTag && ClosingBracket != ">" ; } }
+ /// True if tag starts with "</"
+ public bool IsEndTag { get { return internalObject.IsEndTag; } }
+ /// True if tag starts with "<?"
+ public bool IsProcessingInstruction { get { return internalObject.IsProcessingInstruction; } }
+ /// True if tag starts with "<!--"
+ public bool IsComment { get { return internalObject.IsComment; } }
+ /// True if tag starts with "<![CDATA["
+ public bool IsCData { get { return internalObject.IsCData; } }
+ /// True if tag starts with one of the DTD starts
+ public bool IsDocumentType { get { return internalObject.IsDocumentType; } }
+ /// True if tag starts with "<!"
+ public bool IsUnknownBang { get { return internalObject.IsUnknownBang; } }
+
+ ///
+ public override void AcceptVisitor(IAXmlVisitor visitor)
+ {
+ visitor.VisitTag(this);
+ }
+ }
+}
diff --git a/ICSharpCode.NRefactory.Xml/AXmlText.cs b/ICSharpCode.NRefactory.Xml/AXmlText.cs
new file mode 100644
index 0000000000..fe50ec48a7
--- /dev/null
+++ b/ICSharpCode.NRefactory.Xml/AXmlText.cs
@@ -0,0 +1,51 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+
+namespace ICSharpCode.NRefactory.Xml
+{
+ ///
+ /// Whitespace or character data
+ ///
+ public class AXmlText : AXmlObject
+ {
+ internal AXmlText(AXmlObject parent, int startOffset, InternalText internalObject)
+ : base(parent, startOffset, internalObject)
+ {
+ }
+
+ /// The text with all entity references resloved
+ public string Value {
+ get { return ((InternalText)internalObject).Value; }
+ }
+
+ /// True if the text contains only whitespace characters
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
+ Justification = "System.Xml also uses 'Whitespace'")]
+ public bool ContainsOnlyWhitespace {
+ get { return ((InternalText)internalObject).ContainsOnlyWhitespace; }
+ }
+
+ ///
+ public override void AcceptVisitor(IAXmlVisitor visitor)
+ {
+ visitor.VisitText(this);
+ }
+ }
+}
diff --git a/ICSharpCode.NRefactory.Xml/IAXmlVisitor.cs b/ICSharpCode.NRefactory.Xml/IAXmlVisitor.cs
new file mode 100644
index 0000000000..d8b82a43da
--- /dev/null
+++ b/ICSharpCode.NRefactory.Xml/IAXmlVisitor.cs
@@ -0,0 +1,37 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+
+namespace ICSharpCode.NRefactory.Xml
+{
+ ///
+ /// Visitor for the XML tree
+ ///
+ public interface IAXmlVisitor
+ {
+ /// Visit tag
+ void VisitTag(AXmlTag tag);
+
+ /// Visit attribute
+ void VisitAttribute(AXmlAttribute attribute);
+
+ /// Visit text
+ void VisitText(AXmlText text);
+ }
+}
diff --git a/ICSharpCode.NRefactory.Xml/ICSharpCode.NRefactory.Xml.csproj b/ICSharpCode.NRefactory.Xml/ICSharpCode.NRefactory.Xml.csproj
new file mode 100644
index 0000000000..fe1b5994e7
--- /dev/null
+++ b/ICSharpCode.NRefactory.Xml/ICSharpCode.NRefactory.Xml.csproj
@@ -0,0 +1,79 @@
+
+
+
+ {DC393B66-92ED-4CAD-AB25-CFEF23F3D7C6}
+ Debug
+ AnyCPU
+ Library
+ ICSharpCode.NRefactory.Xml
+ ICSharpCode.NRefactory.Xml
+ v4.0
+ Client
+ Properties
+ False
+ False
+ 4
+ false
+ ..\ICSharpCode.NRefactory\bin\$(Configuration)\ICSharpCode.NRefactory.Xml.xml
+
+
+ AnyCPU
+ False
+ Auto
+ 4194304
+ 4096
+
+
+ ..\ICSharpCode.NRefactory\bin\Debug\
+ true
+ Full
+ False
+ True
+ DEBUG;TRACE
+
+
+ ..\ICSharpCode.NRefactory\bin\Release\
+ false
+ None
+ True
+ False
+ TRACE
+
+
+
+
+ 3.5
+
+
+
+ 3.5
+
+
+
+
+ Properties\GlobalAssemblyInfo.cs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {3B2A5653-EC97-4001-BB9B-D90F1AF2C371}
+ ICSharpCode.NRefactory
+
+
+
+
\ No newline at end of file
diff --git a/ICSharpCode.NRefactory.Xml/IncrementalParserState.cs b/ICSharpCode.NRefactory.Xml/IncrementalParserState.cs
new file mode 100644
index 0000000000..eb292971f9
--- /dev/null
+++ b/ICSharpCode.NRefactory.Xml/IncrementalParserState.cs
@@ -0,0 +1,32 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+
+namespace ICSharpCode.NRefactory.Xml
+{
+ ///
+ /// Encapsulates the state of the incremental tag soup parser.
+ ///
+ public class IncrementalParserState
+ {
+ internal IncrementalParserState()
+ {
+ }
+ }
+}
diff --git a/ICSharpCode.NRefactory.Xml/InternalDocument.cs b/ICSharpCode.NRefactory.Xml/InternalDocument.cs
new file mode 100644
index 0000000000..6f61312323
--- /dev/null
+++ b/ICSharpCode.NRefactory.Xml/InternalDocument.cs
@@ -0,0 +1,107 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+
+namespace ICSharpCode.NRefactory.Xml
+{
+ abstract class InternalObject
+ {
+ internal int StartRelativeToParent;
+ internal int Length;
+ internal InternalSyntaxError[] SyntaxErrors;
+ internal InternalObject[] NestedObjects;
+
+ internal InternalObject SetStartRelativeToParent(int newStartRelativeToParent)
+ {
+ if (newStartRelativeToParent == StartRelativeToParent)
+ return this;
+ InternalObject obj = (InternalObject)MemberwiseClone();
+ obj.StartRelativeToParent = newStartRelativeToParent;
+ return obj;
+ }
+
+ public abstract AXmlObject CreatePublicObject(AXmlObject parent, int parentStartOffset);
+ }
+
+ sealed class InternalText : InternalObject
+ {
+ internal TextType Type;
+ internal bool ContainsOnlyWhitespace;
+ internal string Value;
+
+ public override AXmlObject CreatePublicObject(AXmlObject parent, int parentStartOffset)
+ {
+ return new AXmlText(parent, parentStartOffset + StartRelativeToParent, this);
+ }
+ }
+
+ sealed class InternalTag : InternalObject
+ {
+ internal string OpeningBracket;
+ internal int RelativeNameStart;
+ internal string Name;
+ internal string ClosingBracket;
+
+ /// True if tag starts with "<"
+ public bool IsStartOrEmptyTag { get { return OpeningBracket == "<"; } }
+ /// True if tag starts with "</"
+ public bool IsEndTag { get { return OpeningBracket == ""; } }
+ /// True if tag starts with "<?"
+ public bool IsProcessingInstruction { get { return OpeningBracket == ""; } }
+ /// True if tag starts with "<!--"
+ public bool IsComment { get { return OpeningBracket == "") OnSyntaxError(brStart, brEnd, "'-->' expected");
+ } else if (tag.IsCData) {
+ if (tag.ClosingBracket != "]]>") OnSyntaxError(brStart, brEnd, "']]>' expected");
+ } else if (tag.IsProcessingInstruction) {
+ if (tag.ClosingBracket != "?>") OnSyntaxError(brStart, brEnd, "'?>' expected");
+ } else if (tag.IsUnknownBang) {
+ if (tag.ClosingBracket != ">") OnSyntaxError(brStart, brEnd, "'>' expected");
+ } else if (tag.IsDocumentType) {
+ if (tag.ClosingBracket != ">") OnSyntaxError(brStart, brEnd, "'>' expected");
+ } else {
+ throw new InternalException(string.Format(CultureInfo.InvariantCulture, "Unknown opening bracket '{0}'", tag.OpeningBracket));
+ }
+
+ // Attribute name may not apper multiple times
+ if (objects.Count > oldObjectCount) {
+ // Move nested objects into tag.NestedObjects:
+ tag.NestedObjects = new InternalObject[objects.Count - oldObjectCount];
+ objects.CopyTo(oldObjectCount, tag.NestedObjects, 0, tag.NestedObjects.Length);
+ objects.RemoveRange(oldObjectCount, objects.Count - oldObjectCount);
+
+ // Look for duplicate attributes:
+ HashSet attributeNames = new HashSet();
+ foreach (var obj in tag.NestedObjects) {
+ InternalAttribute attr = obj as InternalAttribute;
+ if (attr != null && attributeNames.Add(attr.Name)) {
+ int attrStart = tagStart + attr.StartRelativeToParent;
+ OnSyntaxError(attrStart, attrStart + attr.Name.Length, "Attribute with name '{0}' already exists", attr.Name);
+ }
+ }
+ }
+
+ EndInternalObject(frame);
+ }
+ #endregion
+
+ #region Read DTD
+ void ReadContentOfDTD()
+ {
+ int start = this.CurrentLocation;
+ while (HasMoreData()) {
+ 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 (HasMoreData()) {
+ TryMoveToAnyOf('<', ']');
+ if (TryPeek('<')) {
+ if (start != this.CurrentLocation) { // Two following tags
+ MakeText(start, this.CurrentLocation);
+ }
+ 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) {
+ MakeText(start, this.CurrentLocation);
+ }
+ }
+
+ void MakeText(int start, int end)
+ {
+ Log.DebugAssert(end > start, "Empty text");
+ Log.DebugAssert(end == this.CurrentLocation, "end == current location");
+
+ InternalText text = new InternalText();
+ var frame = BeginInternalObject(text, start);
+ text.Type = TextType.Other;
+ text.Value = GetText(start, end);
+ EndInternalObject(frame);
+ }
+ #endregion
+
+ #region Read Brackets
+ ///
+ /// Reads any of the know opening brackets. (only full bracket)
+ /// Context: "<"
+ ///
+ 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 "")) {
+ bracket = "-->";
+ } else if (TryRead("]]>")) {
+ bracket = "]]>";
+ } else {
+ bracket = string.Empty;
+ return false;
+ }
+ return true;
+ }
+ #endregion
+
+ #region Attributes
+ ///
+ /// Context: name or "=\'\""
+ ///
+ void ReadAttribute()
+ {
+ AssertHasMoreData();
+
+ InternalAttribute attr = new InternalAttribute();
+ var frame = BeginInternalObject(attr);
+
+ // Read name
+ string name;
+ if (TryReadName(out name)) {
+ if (!IsValidName(name)) {
+ OnSyntaxError(this.CurrentLocation - name.Length, this.CurrentLocation, "The name '{0}' is invalid", name);
+ }
+ } else {
+ OnSyntaxError("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.EqualsSignLength = this.CurrentLocation - checkpoint;
+ } else {
+ GoBack(checkpoint);
+ OnSyntaxError("'=' expected");
+ attr.EqualsSignLength = 0;
+ }
+
+ // 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("White space or end of tag expected");
+ } else {
+ OnSyntaxError("Quote {0} expected (or add whitespace after the following one)", quoteChar);
+ }
+ } else {
+ OnSyntaxError("White space or end of tag expected");
+ }
+ }
+ } else {
+ // '<' or end of file
+ GoBack(valueStart);
+ ReadAttributeValue(quoteChar);
+ OnSyntaxError("Quote {0} expected", quoteChar);
+ }
+ } else {
+ startsWithQuote = false;
+ int valueStart = this.CurrentLocation;
+ ReadAttributeValue(null);
+ TryRead('\"');
+ TryRead('\'');
+ if (valueStart == this.CurrentLocation) {
+ OnSyntaxError("Attribute value expected");
+ } else {
+ OnSyntaxError(valueStart, this.CurrentLocation, "Attribute value must be quoted");
+ }
+ }
+ string val = GetText(start, this.CurrentLocation);
+ val = Unquote(val);
+ attr.Value = Dereference(val, startsWithQuote ? start + 1 : start);
+
+ EndInternalObject(frame);
+ }
+
+ ///
+ /// Read everything up to quote (excluding), opening/closing tag or attribute signature
+ ///
+ void ReadAttributeValue(char? quote)
+ {
+ while (HasMoreData()) {
+ // 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
+ if (TryReadName()) {
+ 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
+ }
+ }
+
+ /// Remove quoting from the given string
+ 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;
+ }
+ }
+ }
+ #endregion
+
+ #region Text
+ ///
+ /// 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
+ ///
+ void ReadText(TextType type)
+ {
+ const int maxTextFragmentSize = 128;
+ bool finished;
+ do {
+ var text = new InternalText();
+ var frame = BeginInternalObject(text);
+ 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);
+
+ 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(this.CurrentLocation, this.CurrentLocation + 3, "']]>' is not allowed in text");
+ }
+ TryMoveNext();
+ continue;
+ }
+ throw new InternalException("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(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 InternalException("Uknown type " + type);
+ }
+
+ text.ContainsOnlyWhitespace = (wsEnd == this.CurrentLocation);
+
+ // Terminal found or real end was reached;
+ 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 - entitySearchLength, entitySearchLength);
+ if (entityIndex != -1) {
+ GoBack(entityIndex);
+ }
+ }
+ }
+
+ string escapedValue = GetText(start, this.CurrentLocation);
+ if (type == TextType.CharacterData) {
+ // Normalize end of line first
+ text.Value = Dereference(NormalizeEndOfLine(escapedValue), start);
+ } else {
+ text.Value = escapedValue;
+ }
+ text.Value = GetCachedString(text.Value);
+
+ EndInternalObject(frame, storeNewObject: this.CurrentLocation > start);
+
+ } while (!finished);
+ }
+ #endregion
+
+ #region Dereference
+ const int maxEntityLength = 16; // The longest built-in one is 10 ("")
+
+ string Dereference(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(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(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(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(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(errorLoc + 2, errorLoc + 1 + name.Length, "Invalid unicode character U+{0:X} ({0})", num);
+ }
+ } else {
+ replacement = null;
+ }
+ } else if (!IsValidName(name)) {
+ replacement = null;
+ OnSyntaxError(errorLoc + 1, errorLoc + 1, "Invalid entity name");
+ } else {
+ replacement = null;
+ if (tagSoupParser.UnknownEntityReferenceIsError) {
+ OnSyntaxError(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
+
+ #region Syntax Errors
+ List syntaxErrors = new List();
+
+ InternalSyntaxError[] GetSyntaxErrors()
+ {
+ if (syntaxErrors.Count > 0) {
+ var arr = syntaxErrors.ToArray();
+ syntaxErrors.Clear();
+ return arr;
+ } else {
+ return null;
+ }
+ }
+
+ void OnSyntaxError(string message, params object[] args)
+ {
+ OnSyntaxError(this.CurrentLocation, this.CurrentLocation + 1, message, args);
+ }
+
+ void OnSyntaxError(int start, int end, string message, params object[] args)
+ {
+ if (end <= start) end = start + 1;
+ string formattedMessage = string.Format(CultureInfo.InvariantCulture, message, args);
+ Log.WriteLine("Syntax error ({0}-{1}): {2}", start, end, formattedMessage);
+ syntaxErrors.Add(new InternalSyntaxError(start - internalObjectStartPosition, end - internalObjectStartPosition, formattedMessage));
+ }
+ #endregion
+
+ #region Helper functions
+ static bool IsValidName(string name)
+ {
+ try {
+ System.Xml.XmlConvert.VerifyName(name);
+ return true;
+ } catch (System.Xml.XmlException) {
+ return false;
+ }
+ }
+
+ static string NormalizeEndOfLine(string text)
+ {
+ return text.Replace("\r\n", "\n").Replace('\r', '\n');
+ }
+ #endregion
+ }
+}
diff --git a/ICSharpCode.NRefactory.Xml/TagSoupParser.cs b/ICSharpCode.NRefactory.Xml/TagSoupParser.cs
new file mode 100644
index 0000000000..1f6a5944ed
--- /dev/null
+++ b/ICSharpCode.NRefactory.Xml/TagSoupParser.cs
@@ -0,0 +1,70 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using ICSharpCode.NRefactory.Editor;
+
+namespace ICSharpCode.NRefactory.Xml
+{
+ ///
+ /// XML tag soup parser that .
+ ///
+ public class TagSoupParser
+ {
+ ///
+ /// Generate syntax error when seeing entity reference other then the built-in ones
+ ///
+ public bool UnknownEntityReferenceIsError { get; set; }
+
+ ///
+ /// Parses a document.
+ ///
+ /// Parsed tag soup.
+ public IList Parse(ITextSource textSource)
+ {
+ if (textSource == null)
+ throw new ArgumentNullException("textSource");
+ var reader = new TagReader(this, textSource);
+ var internalObjects = reader.ReadAllObjects();
+ var publicObjects = new AXmlObject[internalObjects.Length];
+ int pos = 0;
+ for (int i = 0; i < internalObjects.Length; i++) {
+ publicObjects[i] = internalObjects[i].CreatePublicObject(null, pos);
+ pos += internalObjects[i].Length;
+ }
+ return Array.AsReadOnly(publicObjects);
+ }
+
+ ///
+ /// Parses a document incrementally.
+ ///
+ /// The parser state from a previous call to ParseIncremental(). Use null for the first call.
+ /// The text source for the new document version.
+ /// Out: the new parser state, pass this to the next ParseIncremental() call.
+ /// Parsed tag soup.
+ public IList ParseIncremental(IncrementalParserState oldParserState, ITextSource newTextSource, out IncrementalParserState newParserState)
+ {
+ if (newTextSource == null)
+ throw new ArgumentNullException("newTextSource");
+ // TODO: incremental parser
+ newParserState = null;
+ return Parse(newTextSource);
+ }
+ }
+}
diff --git a/ICSharpCode.NRefactory.Xml/TextType.cs b/ICSharpCode.NRefactory.Xml/TextType.cs
new file mode 100644
index 0000000000..b861ca5dcb
--- /dev/null
+++ b/ICSharpCode.NRefactory.Xml/TextType.cs
@@ -0,0 +1,47 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+
+namespace ICSharpCode.NRefactory.Xml
+{
+ /// Identifies the context in which the text occured
+ enum TextType
+ {
+ /// Ends with non-whitespace
+ WhiteSpace,
+
+ /// Ends with "<"; "]]>" is error
+ CharacterData,
+
+ /// Ends with "-->"; "--" is error
+ Comment,
+
+ /// Ends with "]]>"
+ CData,
+
+ /// Ends with "?>"
+ ProcessingInstruction,
+
+ /// Ends with "<" or ">"
+ UnknownBang,
+
+ /// Unknown
+ Other
+ }
+}
diff --git a/ICSharpCode.NRefactory.Xml/TokenReader.cs b/ICSharpCode.NRefactory.Xml/TokenReader.cs
new file mode 100644
index 0000000000..05814e464c
--- /dev/null
+++ b/ICSharpCode.NRefactory.Xml/TokenReader.cs
@@ -0,0 +1,348 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using ICSharpCode.NRefactory.Editor;
+
+namespace ICSharpCode.NRefactory.Xml
+{
+ class TokenReader
+ {
+ protected readonly ITextSource input;
+ protected readonly 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.
+ // Actual 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(ITextSource input)
+ {
+ this.input = input;
+ this.inputLength = input.TextLength;
+ }
+
+ protected bool IsEndOfFile()
+ {
+ return currentLocation == inputLength;
+ }
+
+ protected bool HasMoreData()
+ {
+ return currentLocation < inputLength;
+ }
+
+ protected void AssertHasMoreData()
+ {
+ Log.Assert(HasMoreData(), "Unexpected end of file");
+ }
+
+ protected bool TryMoveNext()
+ {
+ if (currentLocation == inputLength) return false;
+
+ currentLocation++;
+ return true;
+ }
+
+ protected void Skip(int count)
+ {
+ Log.Assert(currentLocation + count <= inputLength, "Skipping after the end of file");
+ currentLocation += count;
+ }
+
+ protected void GoBack(int oldLocation)
+ {
+ Log.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.GetCharAt(currentLocation) == c) {
+ currentLocation++;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ protected bool TryReadAnyOf(params char[] c)
+ {
+ if (currentLocation == inputLength) return false;
+
+ if (c.Contains(input.GetCharAt(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.GetCharAt(currentLocation - back) == c;
+ }
+
+ protected bool TryPeek(char c)
+ {
+ if (currentLocation == inputLength) return false;
+
+ return input.GetCharAt(currentLocation) == c;
+ }
+
+ protected bool TryPeekAnyOf(params char[] chars)
+ {
+ if (currentLocation == inputLength) return false;
+
+ return chars.Contains(input.GetCharAt(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.GetText(currentLocation, text.Length) == text;
+ }
+
+ protected bool TryPeekWhiteSpace()
+ {
+ if (currentLocation == inputLength) return false;
+
+ char c = input.GetCharAt(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.GetCharAt(currentLocation);
+ if (((int)c <= 0x20) && (c == ' ' || c == '\t' || c == '\n' || c == '\r')) {
+ currentLocation++; // Accept white-space
+ continue;
+ } else {
+ return true; // Found non-white-space
+ }
+ }
+ }
+
+ ///
+ /// Read a name token.
+ /// The following characters are not allowed:
+ /// "" End of file
+ /// " \n\r\t" Whitesapce
+ /// "=\'\"" Attribute value
+ /// "<>/?" Tags
+ ///
+ /// Returns the length of the name
+ protected bool TryReadName()
+ {
+ int start = currentLocation;
+ // Keep reading up to invalid character
+ while (HasMoreData()) {
+ char c = input.GetCharAt(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
+ }
+ }
+ return currentLocation > start;
+ }
+
+ protected bool TryReadName(out string name)
+ {
+ int start = currentLocation;
+ if (TryReadName()) {
+ name = GetCachedString(GetText(start, currentLocation));
+ return true;
+ } else {
+ name = string.Empty;
+ return false;
+ }
+ }
+
+ protected string GetText(int start, int end)
+ {
+ Log.Assert(end <= currentLocation, "Reading ahead of current location");
+ return input.GetText(start, end - start);
+ }
+
+ Dictionary stringCache = new Dictionary();
+
+ #if DEBUG
+ int stringCacheRequestedCount;
+ int stringCacheRequestedSize;
+ int stringCacheStoredCount;
+ int stringCacheStoredSize;
+ #endif
+
+ internal void PrintStringCacheStats()
+ {
+ #if DEBUG
+ Log.WriteLine("String cache: Requested {0} ({1} bytes); Actaully stored {2} ({3} bytes); {4}% stored", stringCacheRequestedCount, stringCacheRequestedSize, stringCacheStoredCount, stringCacheStoredSize, stringCacheRequestedSize == 0 ? 0 : stringCacheStoredSize * 100 / stringCacheRequestedSize);
+ #endif
+ }
+
+ [Conditional("DEBUG")]
+ void AddToRequestedSize(string text)
+ {
+ #if DEBUG
+ stringCacheRequestedCount += 1;
+ stringCacheRequestedSize += 8 + 2 * text.Length;
+ #endif
+ }
+
+ [Conditional("DEBUG")]
+ void AddToStoredSize(string text)
+ {
+ #if DEBUG
+ stringCacheStoredCount += 1;
+ stringCacheStoredSize += 8 + 2 * text.Length;
+ #endif
+ }
+
+ protected string GetCachedString(string cached)
+ {
+ AddToRequestedSize(cached);
+ // Do not bother with long strings
+ if (cached.Length > 32) {
+ AddToStoredSize(cached);
+ return cached;
+ }
+ string result;
+ if (stringCache.TryGetValue(cached, out result)) {
+ // Get the instance from the cache instead
+ return result;
+ } else {
+ // Add to cache
+ AddToStoredSize(cached);
+ stringCache.Add(cached, cached);
+ return cached;
+ }
+ }
+ }
+}
diff --git a/ICSharpCode.NRefactory.Xml/XmlSegment.cs b/ICSharpCode.NRefactory.Xml/XmlSegment.cs
new file mode 100644
index 0000000000..6d0a00ff67
--- /dev/null
+++ b/ICSharpCode.NRefactory.Xml/XmlSegment.cs
@@ -0,0 +1,48 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this
+// software and associated documentation files (the "Software"), to deal in the Software
+// without restriction, including without limitation the rights to use, copy, modify, merge,
+// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
+// to whom the Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or
+// substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+
+using System;
+using ICSharpCode.NRefactory.Editor;
+
+namespace ICSharpCode.NRefactory.Xml
+{
+ sealed class XmlSegment : ISegment
+ {
+ readonly int startOffset, endOffset;
+
+ public XmlSegment(int startOffset, int endOffset)
+ {
+ if (endOffset < startOffset)
+ throw new ArgumentOutOfRangeException();
+ this.startOffset = startOffset;
+ this.endOffset = endOffset;
+ }
+
+ int ISegment.Offset {
+ get { return startOffset; }
+ }
+
+ int ISegment.Length {
+ get { return endOffset - startOffset; }
+ }
+
+ int ISegment.EndOffset {
+ get { return endOffset; }
+ }
+ }
+}
diff --git a/ICSharpCode.NRefactory/Editor/ITextSource.cs b/ICSharpCode.NRefactory/Editor/ITextSource.cs
index 859c3fa5a3..31a48df7d1 100644
--- a/ICSharpCode.NRefactory/Editor/ITextSource.cs
+++ b/ICSharpCode.NRefactory/Editor/ITextSource.cs
@@ -93,6 +93,15 @@ namespace ICSharpCode.NRefactory.Editor
/// offset or length is outside the valid range.
string GetText(ISegment segment);
+ ///
+ /// Gets the index of the first occurrence of the character in the specified array.
+ ///
+ /// Character to search for
+ /// Start index of the area to search.
+ /// Length of the area to search.
+ /// The first index where the character was found; or -1 if no occurrence was found.
+ int IndexOf(char c, int startIndex, int count);
+
///
/// Gets the index of the first occurrence of any character in the specified array.
///
@@ -112,6 +121,17 @@ namespace ICSharpCode.NRefactory.Editor
/// The first index where the search term was found; or -1 if no occurrence was found.
int IndexOf(string searchText, int startIndex, int count, StringComparison comparisonType);
+ ///
+ /// Gets the index of the last occurrence of the specified character in this text source.
+ ///
+ /// The search character
+ /// Start index of the area to search.
+ /// Length of the area to search.
+ /// The last index where the search term was found; or -1 if no occurrence was found.
+ /// The search proceeds backwards from (startIndex+count) to startIndex.
+ /// This is different than the meaning of the parameters on string.LastIndexOf!
+ int LastIndexOf(char c, int startIndex, int count);
+
///
/// Gets the index of the last occurrence of the specified search text in this text source.
///
diff --git a/ICSharpCode.NRefactory/Editor/ReadOnlyDocument.cs b/ICSharpCode.NRefactory/Editor/ReadOnlyDocument.cs
index 40c61085e7..5120fd2e32 100644
--- a/ICSharpCode.NRefactory/Editor/ReadOnlyDocument.cs
+++ b/ICSharpCode.NRefactory/Editor/ReadOnlyDocument.cs
@@ -354,6 +354,12 @@ namespace ICSharpCode.NRefactory.Editor
return textSource.GetText(segment);
}
+ ///
+ public int IndexOf(char c, int startIndex, int count)
+ {
+ return textSource.IndexOf(c, startIndex, count);
+ }
+
///
public int IndexOfAny(char[] anyOf, int startIndex, int count)
{
@@ -366,6 +372,12 @@ namespace ICSharpCode.NRefactory.Editor
return textSource.IndexOf(searchText, startIndex, count, comparisonType);
}
+ ///
+ public int LastIndexOf(char c, int startIndex, int count)
+ {
+ return textSource.LastIndexOf(c, startIndex, count);
+ }
+
///
public int LastIndexOf(string searchText, int startIndex, int count, StringComparison comparisonType)
{
diff --git a/ICSharpCode.NRefactory/Editor/StringTextSource.cs b/ICSharpCode.NRefactory/Editor/StringTextSource.cs
index bafc74cbb1..e16d6749c5 100644
--- a/ICSharpCode.NRefactory/Editor/StringTextSource.cs
+++ b/ICSharpCode.NRefactory/Editor/StringTextSource.cs
@@ -97,6 +97,12 @@ namespace ICSharpCode.NRefactory.Editor
return text.Substring(segment.Offset, segment.Length);
}
+ ///
+ public int IndexOf(char c, int startIndex, int count)
+ {
+ return text.IndexOf(c, startIndex, count);
+ }
+
///
public int IndexOfAny(char[] anyOf, int startIndex, int count)
{
@@ -109,6 +115,12 @@ namespace ICSharpCode.NRefactory.Editor
return text.IndexOf(searchText, startIndex, count, comparisonType);
}
+ ///
+ public int LastIndexOf(char c, int startIndex, int count)
+ {
+ return text.LastIndexOf(c, startIndex + count - 1, count);
+ }
+
///
public int LastIndexOf(string searchText, int startIndex, int count, StringComparison comparisonType)
{
diff --git a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj
index 97e9772c33..16388e2ce2 100644
--- a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj
+++ b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj
@@ -79,6 +79,7 @@
+
diff --git a/ICSharpCode.NRefactory/Properties/AssemblyInfo.cs b/ICSharpCode.NRefactory/Properties/AssemblyInfo.cs
index d9600a3f2a..5e49f566fd 100644
--- a/ICSharpCode.NRefactory/Properties/AssemblyInfo.cs
+++ b/ICSharpCode.NRefactory/Properties/AssemblyInfo.cs
@@ -10,23 +10,6 @@ using System.Runtime.InteropServices;
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ICSharpCode.NRefactory")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("ICSharpCode")]
-[assembly: AssemblyProduct("SharpDevelop/MonoDevelop")]
-[assembly: AssemblyCopyright("Copyright 2010-2012 AlphaSierraPapa")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
+[assembly: AssemblyDescription("Type system and other language-independent parts of NRefactory")]
-// This sets the default COM visibility of types in the assembly to invisible.
-// If you need to expose a type to COM, use [ComVisible(true)] on that type.
-[assembly: ComVisible(false)]
[assembly: CLSCompliant(true)]
-
-// The assembly version has following format :
-//
-// Major.Minor.Build.Revision
-//
-// You can specify all the values or you can use the default the Revision and
-// Build Numbers by using the '*' as shown below:
-[assembly: AssemblyVersion("5.0.0.4")]
diff --git a/ICSharpCode.NRefactory/Properties/GlobalAssemblyInfo.cs b/ICSharpCode.NRefactory/Properties/GlobalAssemblyInfo.cs
new file mode 100644
index 0000000000..bc1f282e7a
--- /dev/null
+++ b/ICSharpCode.NRefactory/Properties/GlobalAssemblyInfo.cs
@@ -0,0 +1,26 @@
+#region Using directives
+
+using System;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+#endregion
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyCompany("ICSharpCode")]
+[assembly: AssemblyProduct("SharpDevelop/MonoDevelop")]
+[assembly: AssemblyCopyright("Copyright 2010-2012 AlphaSierraPapa")]
+
+// This sets the default COM visibility of types in the assembly to invisible.
+// If you need to expose a type to COM, use [ComVisible(true)] on that type.
+[assembly: ComVisible(false)]
+
+// The assembly version has following format :
+//
+// Major.Minor.Build.Revision
+//
+// You can specify all the values or you can use the default the Revision and
+// Build Numbers by using the '*' as shown below:
+[assembly: AssemblyVersion("5.0.0.4")]
diff --git a/NRefactory.sln b/NRefactory.sln
index 08b92e6157..4b31212a5b 100644
--- a/NRefactory.sln
+++ b/NRefactory.sln
@@ -26,6 +26,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.NRefactory.GtkD
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.NRefactory.ConsistencyCheck", "ICSharpCode.NRefactory.ConsistencyCheck\ICSharpCode.NRefactory.ConsistencyCheck.csproj", "{D81206EF-3DCA-4A30-897B-E262A2AD9EE3}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.NRefactory.Xml", "ICSharpCode.NRefactory.Xml\ICSharpCode.NRefactory.Xml.csproj", "{DC393B66-92ED-4CAD-AB25-CFEF23F3D7C6}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -91,13 +93,21 @@ Global
{D68133BD-1E63-496E-9EDE-4FBDBF77B486}.Release|x86.ActiveCfg = net_4_0_Release|Any CPU
{D68133BD-1E63-496E-9EDE-4FBDBF77B486}.Release|x86.Build.0 = net_4_0_Release|Any CPU
{D81206EF-3DCA-4A30-897B-E262A2AD9EE3}.Debug|Any CPU.Build.0 = Debug|x86
- {D81206EF-3DCA-4A30-897B-E262A2AD9EE3}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {D81206EF-3DCA-4A30-897B-E262A2AD9EE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D81206EF-3DCA-4A30-897B-E262A2AD9EE3}.Debug|x86.Build.0 = Debug|x86
{D81206EF-3DCA-4A30-897B-E262A2AD9EE3}.Debug|x86.ActiveCfg = Debug|x86
{D81206EF-3DCA-4A30-897B-E262A2AD9EE3}.Release|Any CPU.Build.0 = Release|x86
- {D81206EF-3DCA-4A30-897B-E262A2AD9EE3}.Release|Any CPU.ActiveCfg = Release|x86
+ {D81206EF-3DCA-4A30-897B-E262A2AD9EE3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D81206EF-3DCA-4A30-897B-E262A2AD9EE3}.Release|x86.Build.0 = Release|x86
{D81206EF-3DCA-4A30-897B-E262A2AD9EE3}.Release|x86.ActiveCfg = Release|x86
+ {DC393B66-92ED-4CAD-AB25-CFEF23F3D7C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DC393B66-92ED-4CAD-AB25-CFEF23F3D7C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DC393B66-92ED-4CAD-AB25-CFEF23F3D7C6}.Debug|x86.Build.0 = Debug|Any CPU
+ {DC393B66-92ED-4CAD-AB25-CFEF23F3D7C6}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {DC393B66-92ED-4CAD-AB25-CFEF23F3D7C6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DC393B66-92ED-4CAD-AB25-CFEF23F3D7C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DC393B66-92ED-4CAD-AB25-CFEF23F3D7C6}.Release|x86.Build.0 = Release|Any CPU
+ {DC393B66-92ED-4CAD-AB25-CFEF23F3D7C6}.Release|x86.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
StartupItem = ICSharpCode.NRefactory.Demo\ICSharpCode.NRefactory.Demo.csproj
diff --git a/doc/XML Documentation.html b/doc/XML Documentation.html
index c81b7e8fff..c3cae8ba0c 100644
--- a/doc/XML Documentation.html
+++ b/doc/XML Documentation.html
@@ -12,7 +12,7 @@
When parsing C# code, the TypeSystemConvertVisitor will load the documentation
- from the XML comments by default. (this can be disabled using TODO).
+ from the XML comments by default. (this can be disabled using the SkipXmlDocumentation property).
Internally, the type system does not store XML documentation - instead, the documentation is