Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/xamarin/NRefactory.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Grunwald <daniel@danielgrunwald.de>2012-02-21 16:20:51 +0400
committerDaniel Grunwald <daniel@danielgrunwald.de>2012-02-21 16:20:51 +0400
commitd2806842da448bf3ee1f1e8c4e2d1cc7559ef14a (patch)
tree2717c41356c1899e9eb497e7573b0c402a837d00 /ICSharpCode.NRefactory.Xml
parentd62f36694ec77512943f7ea54ab1f654cec4293d (diff)
Put properly nested elements into AXmlElement.
Diffstat (limited to 'ICSharpCode.NRefactory.Xml')
-rw-r--r--ICSharpCode.NRefactory.Xml/AXmlAttribute.cs50
-rw-r--r--ICSharpCode.NRefactory.Xml/AXmlDocument.cs46
-rw-r--r--ICSharpCode.NRefactory.Xml/AXmlElement.cs182
-rw-r--r--ICSharpCode.NRefactory.Xml/AXmlObject.cs9
-rw-r--r--ICSharpCode.NRefactory.Xml/AXmlParser.cs137
-rw-r--r--ICSharpCode.NRefactory.Xml/AXmlTag.cs4
-rw-r--r--ICSharpCode.NRefactory.Xml/IAXmlVisitor.cs6
-rw-r--r--ICSharpCode.NRefactory.Xml/ICSharpCode.NRefactory.Xml.csproj6
-rw-r--r--ICSharpCode.NRefactory.Xml/InternalDocument.cs29
-rw-r--r--ICSharpCode.NRefactory.Xml/ObjectIterator.cs91
-rw-r--r--ICSharpCode.NRefactory.Xml/ReuseEqualityComparer.cs52
-rw-r--r--ICSharpCode.NRefactory.Xml/TagReader.cs119
-rw-r--r--ICSharpCode.NRefactory.Xml/TagSoupParser.cs85
13 files changed, 696 insertions, 120 deletions
diff --git a/ICSharpCode.NRefactory.Xml/AXmlAttribute.cs b/ICSharpCode.NRefactory.Xml/AXmlAttribute.cs
index ae24bcf0..f792434b 100644
--- a/ICSharpCode.NRefactory.Xml/AXmlAttribute.cs
+++ b/ICSharpCode.NRefactory.Xml/AXmlAttribute.cs
@@ -52,6 +52,56 @@ namespace ICSharpCode.NRefactory.Xml
public ISegment ValueSegment {
get { return new XmlSegment(startOffset + Name.Length + InternalAttribute.EqualsSignLength, this.EndOffset); }
}
+ /// <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";
+ }
+ }
/// <inheritdoc/>
public override void AcceptVisitor(IAXmlVisitor visitor)
diff --git a/ICSharpCode.NRefactory.Xml/AXmlDocument.cs b/ICSharpCode.NRefactory.Xml/AXmlDocument.cs
new file mode 100644
index 00000000..90b54714
--- /dev/null
+++ b/ICSharpCode.NRefactory.Xml/AXmlDocument.cs
@@ -0,0 +1,46 @@
+// 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.Globalization;
+
+namespace ICSharpCode.NRefactory.Xml
+{
+ /// <summary>
+ /// The root object of the XML document
+ /// </summary>
+ public class AXmlDocument : AXmlObject
+ {
+ internal AXmlDocument(AXmlObject parent, int startOffset, InternalDocument internalObject)
+ : base(parent, startOffset, internalObject)
+ {
+ }
+
+ /// <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/ICSharpCode.NRefactory.Xml/AXmlElement.cs b/ICSharpCode.NRefactory.Xml/AXmlElement.cs
new file mode 100644
index 00000000..9a8dfb19
--- /dev/null
+++ b/ICSharpCode.NRefactory.Xml/AXmlElement.cs
@@ -0,0 +1,182 @@
+// 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.Globalization;
+using System.Linq;
+
+namespace ICSharpCode.NRefactory.Xml
+{
+ /// <summary>
+ /// XML element.
+ /// </summary>
+ public class AXmlElement : AXmlObject
+ {
+ internal AXmlElement(AXmlObject parent, int startOffset, InternalElement internalObject)
+ : base(parent, startOffset, internalObject)
+ {
+ Log.Assert(internalObject.NestedObjects[0] is InternalTag, "First child of element must be start tag");
+ }
+
+ /// <summary> No tags are missing anywhere within this element (recursive) </summary>
+ public bool IsProperlyNested {
+ get { return ((InternalElement)internalObject).IsPropertyNested; }
+ }
+
+ /// <summary>The start or empty-element tag for this element.</summary>
+ public AXmlTag StartTag {
+ get { return (AXmlTag)this.Children[0]; }
+ }
+
+ /// <summary>Name with namespace prefix - exactly as in source</summary>
+ public string Name {
+ get { return ((InternalTag)internalObject.NestedObjects[0]).Name; }
+ }
+
+ /// <summary>Gets whether an end tag exists for this node.</summary>
+ public bool HasEndTag {
+ get { return ((InternalElement)internalObject).HasEndTag; }
+ }
+
+ /// <summary> The end tag, if there is any. Returns null for empty elements "&lt;Element/>" and missing end tags in malformed XML.</summary>
+ public AXmlTag EndTag {
+ get {
+ if (HasEndTag)
+ return (AXmlTag)this.Children[this.Children.Count - 1];
+ else
+ return null;
+ }
+ }
+
+ /// <summary>
+ /// Gets the attributes.
+ /// </summary>
+ public IEnumerable<AXmlAttribute> Attributes {
+ get {
+ return ((AXmlTag)this.Children[0]).Children.OfType<AXmlAttribute>();
+ }
+ }
+
+ /// <summary>
+ /// Gets the content (all children except for the start and end tags)
+ /// </summary>
+ public IEnumerable<AXmlObject> Content {
+ get {
+ int end = this.Children.Count;
+ if (HasEndTag)
+ end--;
+ for (int i = 1; i < end; i++) {
+ yield return this.Children[i];
+ }
+ }
+ }
+
+ /// <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;
+ return ResolvePrefix(prefix);
+ }
+ }
+
+ /// <summary> Find the defualt namespace for this context </summary>
+ public string FindDefaultNamespace()
+ {
+ return ResolvePrefix(string.Empty);
+ }
+
+ /// <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 (prefix == null)
+ throw new ArgumentNullException("prefix");
+
+ // Implicit namesapces
+ if (prefix == "xml") return XmlNamespace;
+ if (prefix == "xmlns") return XmlnsNamespace;
+
+ string lookFor = (prefix.Length > 0 ? "xmlns:" + prefix : "xmlns");
+ for (AXmlElement current = this; current != null; current = current.Parent as AXmlElement) {
+ foreach (var attr in current.Attributes) {
+ if (attr.Name == lookFor)
+ return attr.Value;
+ }
+ }
+ 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) {
+ if (attr.LocalName == localName && attr.Namespace == @namespace)
+ return attr.Value;
+ }
+ return null;
+ }
+
+ /// <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.StartTag.Children.Count, this.Children.Count, this.IsProperlyNested ? "Ok" : "Bad");
+ }
+ }
+}
diff --git a/ICSharpCode.NRefactory.Xml/AXmlObject.cs b/ICSharpCode.NRefactory.Xml/AXmlObject.cs
index 826d4ebe..49e99273 100644
--- a/ICSharpCode.NRefactory.Xml/AXmlObject.cs
+++ b/ICSharpCode.NRefactory.Xml/AXmlObject.cs
@@ -29,6 +29,15 @@ namespace ICSharpCode.NRefactory.Xml
/// </summary>
public abstract class AXmlObject : ISegment
{
+ /// <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/";
+
readonly AXmlObject parent;
internal readonly int startOffset;
internal readonly InternalObject internalObject;
diff --git a/ICSharpCode.NRefactory.Xml/AXmlParser.cs b/ICSharpCode.NRefactory.Xml/AXmlParser.cs
new file mode 100644
index 00000000..95e81220
--- /dev/null
+++ b/ICSharpCode.NRefactory.Xml/AXmlParser.cs
@@ -0,0 +1,137 @@
+// 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 System.Xml.Linq;
+using ICSharpCode.NRefactory.Editor;
+using ICSharpCode.NRefactory.Utils;
+
+namespace ICSharpCode.NRefactory.Xml
+{
+ /// <summary>
+ /// XML tag soup parser that .
+ /// </summary>
+ public class AXmlParser
+ {
+ /// <summary>
+ /// Generate syntax error when seeing entity reference other then the built-in ones
+ /// </summary>
+ public bool UnknownEntityReferenceIsError { get; set; }
+
+ IList<AXmlObject> CreatePublic(IList<InternalObject> internalObjects)
+ {
+ var publicObjects = new AXmlObject[internalObjects.Count];
+ int pos = 0;
+ for (int i = 0; i < internalObjects.Count; i++) {
+ publicObjects[i] = internalObjects[i].CreatePublicObject(null, pos);
+ pos += internalObjects[i].Length;
+ }
+ return Array.AsReadOnly(publicObjects);
+ }
+
+ /// <summary>
+ /// Parses a document into a flat list of tags.
+ /// </summary>
+ /// <returns>Parsed tag soup.</returns>
+ public IList<AXmlObject> ParseTagSoup(ITextSource textSource)
+ {
+ if (textSource == null)
+ throw new ArgumentNullException("textSource");
+ var reader = new TagReader(this, textSource, false);
+ var internalObjects = reader.ReadAllObjects();
+ return CreatePublic(internalObjects);
+ }
+
+ /// <summary>
+ /// Parses a document incrementally into a flat list of tags.
+ /// </summary>
+ /// <param name="oldParserState">The parser state from a previous call to ParseIncremental(). Use null for the first call.</param>
+ /// <param name="newTextSource">The text source for the new document version.</param>
+ /// <param name="newParserState">Out: the new parser state, pass this to the next ParseIncremental() call.</param>
+ /// <returns>Parsed tag soup.</returns>
+ public IList<AXmlObject> ParseTagSoupIncremental(IncrementalParserState oldParserState, ITextSource newTextSource, out IncrementalParserState newParserState)
+ {
+ if (newTextSource == null)
+ throw new ArgumentNullException("newTextSource");
+ var internalObjects = InternalParseIncremental(oldParserState, newTextSource, out newParserState, false);
+ return CreatePublic(internalObjects);
+ }
+
+ List<InternalObject> InternalParseIncremental(IncrementalParserState oldParserState, ITextSource newTextSource, out IncrementalParserState newParserState, bool collapseProperlyNestedElements)
+ {
+ var reader = new TagReader(this, newTextSource, collapseProperlyNestedElements);
+ ITextSourceVersion newVersion = newTextSource.Version;
+ var reuseMap = oldParserState != null ? oldParserState.GetReuseMapTo(newVersion) : null;
+
+ List<InternalObject> internalObjects;
+ if (reuseMap != null)
+ internalObjects = reader.ReadAllObjectsIncremental(oldParserState.Objects, reuseMap);
+ else
+ internalObjects = reader.ReadAllObjects();
+
+ if (newVersion != null)
+ newParserState = new IncrementalParserState(newTextSource.TextLength, newVersion, internalObjects.ToArray());
+ else
+ newParserState = null;
+
+ return internalObjects;
+ }
+
+ /// <summary>
+ /// Parses a document.
+ /// </summary>
+ public AXmlDocument Parse(ITextSource textSource)
+ {
+ if (textSource == null)
+ throw new ArgumentNullException("textSource");
+ var reader = new TagReader(this, textSource, true);
+ var internalObjects = reader.ReadAllObjects();
+ return CreateDocument(internalObjects);
+ }
+
+ /// <summary>
+ /// Parses a document incrementally into a flat list of tags.
+ /// </summary>
+ /// <param name="oldParserState">The parser state from a previous call to ParseIncremental(). Use null for the first call.</param>
+ /// <param name="newTextSource">The text source for the new document version.</param>
+ /// <param name="newParserState">Out: the new parser state, pass this to the next ParseIncremental() call.</param>
+ /// <returns>Parsed tag soup.</returns>
+ public AXmlDocument ParseIncremental(IncrementalParserState oldParserState, ITextSource newTextSource, out IncrementalParserState newParserState)
+ {
+ if (newTextSource == null)
+ throw new ArgumentNullException("newTextSource");
+ var internalObjects = InternalParseIncremental(oldParserState, newTextSource, out newParserState, true);
+ return CreateDocument(internalObjects);
+ }
+
+ AXmlDocument CreateDocument(List<InternalObject> internalObjects)
+ {
+ InternalObject[] documentChildren = new InternalObject[internalObjects.Count];
+ int pos = 0;
+ for (int i = 0; i < documentChildren.Length; i++) {
+ documentChildren[i] = internalObjects[i].SetStartRelativeToParent(pos);
+ pos += documentChildren[i].Length;
+ }
+ var document = new InternalDocument { NestedObjects = documentChildren, Length = pos };
+ return new AXmlDocument(null, 0, document);
+ }
+ }
+}
diff --git a/ICSharpCode.NRefactory.Xml/AXmlTag.cs b/ICSharpCode.NRefactory.Xml/AXmlTag.cs
index adca935c..c8e211a5 100644
--- a/ICSharpCode.NRefactory.Xml/AXmlTag.cs
+++ b/ICSharpCode.NRefactory.Xml/AXmlTag.cs
@@ -67,9 +67,9 @@ namespace ICSharpCode.NRefactory.Xml
/// <summary> True if tag starts with "&lt;" </summary>
public bool IsStartOrEmptyTag { get { return internalObject.IsStartOrEmptyTag; } }
/// <summary> True if tag starts with "&lt;" and ends with "&gt;" </summary>
- public bool IsStartTag { get { return internalObject.IsStartOrEmptyTag && ClosingBracket == ">"; } }
+ public bool IsStartTag { get { return internalObject.IsStartTag; } }
/// <summary> True if tag starts with "&lt;" and does not end with "&gt;" </summary>
- public bool IsEmptyTag { get { return internalObject.IsStartOrEmptyTag && ClosingBracket != ">" ; } }
+ public bool IsEmptyTag { get { return internalObject.IsEmptyTag; } }
/// <summary> True if tag starts with "&lt;/" </summary>
public bool IsEndTag { get { return internalObject.IsEndTag; } }
/// <summary> True if tag starts with "&lt;?" </summary>
diff --git a/ICSharpCode.NRefactory.Xml/IAXmlVisitor.cs b/ICSharpCode.NRefactory.Xml/IAXmlVisitor.cs
index d8b82a43..bd0eeaf7 100644
--- a/ICSharpCode.NRefactory.Xml/IAXmlVisitor.cs
+++ b/ICSharpCode.NRefactory.Xml/IAXmlVisitor.cs
@@ -25,6 +25,9 @@ namespace ICSharpCode.NRefactory.Xml
/// </summary>
public interface IAXmlVisitor
{
+ /// <summary> Visit document </summary>
+ void VisitDocument(AXmlDocument document);
+
/// <summary> Visit tag </summary>
void VisitTag(AXmlTag tag);
@@ -33,5 +36,8 @@ namespace ICSharpCode.NRefactory.Xml
/// <summary> Visit text </summary>
void VisitText(AXmlText text);
+
+ /// <summary> Visit element </summary>
+ void VisitElement(AXmlElement element);
}
}
diff --git a/ICSharpCode.NRefactory.Xml/ICSharpCode.NRefactory.Xml.csproj b/ICSharpCode.NRefactory.Xml/ICSharpCode.NRefactory.Xml.csproj
index fe1b5994..d86e8dd4 100644
--- a/ICSharpCode.NRefactory.Xml/ICSharpCode.NRefactory.Xml.csproj
+++ b/ICSharpCode.NRefactory.Xml/ICSharpCode.NRefactory.Xml.csproj
@@ -54,18 +54,22 @@
<Link>Properties\GlobalAssemblyInfo.cs</Link>
</Compile>
<Compile Include="AXmlAttribute.cs" />
+ <Compile Include="AXmlDocument.cs" />
+ <Compile Include="AXmlElement.cs" />
<Compile Include="AXmlObject.cs" />
<Compile Include="AXmlTag.cs" />
<Compile Include="AXmlText.cs" />
<Compile Include="IAXmlVisitor.cs" />
<Compile Include="IncrementalParserState.cs" />
<Compile Include="InternalDocument.cs" />
+ <Compile Include="ObjectIterator.cs" />
+ <Compile Include="ReuseEqualityComparer.cs" />
<Compile Include="SyntaxError.cs" />
<Compile Include="TextType.cs" />
<Compile Include="Log.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TagReader.cs" />
- <Compile Include="TagSoupParser.cs" />
+ <Compile Include="AXmlParser.cs" />
<Compile Include="TokenReader.cs" />
<Compile Include="XmlSegment.cs" />
</ItemGroup>
diff --git a/ICSharpCode.NRefactory.Xml/InternalDocument.cs b/ICSharpCode.NRefactory.Xml/InternalDocument.cs
index 4892fe50..8daf26dd 100644
--- a/ICSharpCode.NRefactory.Xml/InternalDocument.cs
+++ b/ICSharpCode.NRefactory.Xml/InternalDocument.cs
@@ -41,6 +41,14 @@ namespace ICSharpCode.NRefactory.Xml
public abstract AXmlObject CreatePublicObject(AXmlObject parent, int parentStartOffset);
}
+ sealed class InternalDocument : InternalObject
+ {
+ public override AXmlObject CreatePublicObject(AXmlObject parent, int parentStartOffset)
+ {
+ return new AXmlDocument(parent, (parent != null ? parentStartOffset + StartRelativeToParent : parentStartOffset), this);
+ }
+ }
+
sealed class InternalText : InternalObject
{
public TextType Type;
@@ -49,7 +57,7 @@ namespace ICSharpCode.NRefactory.Xml
public override AXmlObject CreatePublicObject(AXmlObject parent, int parentStartOffset)
{
- return new AXmlText(parent, parentStartOffset + StartRelativeToParent, this);
+ return new AXmlText(parent, (parent != null ? parentStartOffset + StartRelativeToParent : parentStartOffset), this);
}
}
@@ -62,6 +70,10 @@ namespace ICSharpCode.NRefactory.Xml
/// <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>
@@ -77,7 +89,7 @@ namespace ICSharpCode.NRefactory.Xml
public override AXmlObject CreatePublicObject(AXmlObject parent, int parentStartOffset)
{
- return new AXmlTag(parent, parentStartOffset + StartRelativeToParent, this);
+ return new AXmlTag(parent, (parent != null ? parentStartOffset + StartRelativeToParent : parentStartOffset), this);
}
}
@@ -103,7 +115,18 @@ namespace ICSharpCode.NRefactory.Xml
public override AXmlObject CreatePublicObject(AXmlObject parent, int parentStartOffset)
{
- return new AXmlAttribute(parent, parentStartOffset + StartRelativeToParent, this);
+ return new AXmlAttribute(parent, (parent != null ? parentStartOffset + StartRelativeToParent : parentStartOffset), this);
+ }
+ }
+
+ class InternalElement : InternalObject
+ {
+ public bool HasEndTag;
+ public bool IsPropertyNested;
+
+ public override AXmlObject CreatePublicObject(AXmlObject parent, int parentStartOffset)
+ {
+ return new AXmlElement(parent, (parent != null ? parentStartOffset + StartRelativeToParent : parentStartOffset), this);
}
}
}
diff --git a/ICSharpCode.NRefactory.Xml/ObjectIterator.cs b/ICSharpCode.NRefactory.Xml/ObjectIterator.cs
new file mode 100644
index 00000000..1d83f3e2
--- /dev/null
+++ b/ICSharpCode.NRefactory.Xml/ObjectIterator.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.Generic;
+
+namespace ICSharpCode.NRefactory.Xml
+{
+ /// <summary>
+ /// Iterates through an internal object tree.
+ /// </summary>
+ class ObjectIterator
+ {
+ Stack<InternalObject[]> listStack = new Stack<InternalObject[]>();
+ Stack<int> indexStack = new Stack<int>();
+
+ InternalObject[] objects;
+ int currentIndex;
+ InternalObject currentObject;
+ int currentPosition;
+
+ public ObjectIterator(InternalObject[] objects)
+ {
+ this.objects = objects;
+ if (objects.Length > 0)
+ this.currentObject = objects[0];
+ }
+
+ public InternalObject CurrentObject {
+ get { return currentObject; }
+ }
+
+ public int CurrentPosition {
+ get { return currentPosition; }
+ }
+
+ public void MoveNext()
+ {
+ if (currentObject == null)
+ return;
+ currentIndex++;
+ currentPosition += currentObject.Length;
+ while (currentIndex >= objects.Length && listStack.Count > 0) {
+ objects = listStack.Pop();
+ currentIndex = indexStack.Pop() + 1;
+ }
+ currentObject = (currentIndex < objects.Length ? objects[currentIndex] : null);
+ }
+
+ public void MoveInto()
+ {
+ if (!(currentObject is InternalElement)) {
+ MoveNext();
+ } else {
+ listStack.Push(objects);
+ indexStack.Push(currentIndex);
+ objects = currentObject.NestedObjects;
+ currentIndex = 0;
+ currentObject = objects[0];
+ }
+ }
+
+ /// <summary>
+ /// Skips all nodes in front of 'position'
+ /// </summary>
+ public void SkipTo(int position)
+ {
+ while (currentObject != null && currentPosition < position) {
+ if (currentPosition + currentObject.Length <= position)
+ MoveNext();
+ else
+ MoveInto();
+ }
+ }
+ }
+}
diff --git a/ICSharpCode.NRefactory.Xml/ReuseEqualityComparer.cs b/ICSharpCode.NRefactory.Xml/ReuseEqualityComparer.cs
new file mode 100644
index 00000000..5adc3061
--- /dev/null
+++ b/ICSharpCode.NRefactory.Xml/ReuseEqualityComparer.cs
@@ -0,0 +1,52 @@
+// 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;
+
+namespace ICSharpCode.NRefactory.Xml
+{
+ /// <summary>
+ /// Determines whether two objects are identical (one is a reused version of the other).
+ /// </summary>
+ public class ReuseEqualityComparer : IEqualityComparer<AXmlObject>
+ {
+ /// <summary>
+ /// Determines whether two objects are identical (one is a reused version of the other).
+ /// </summary>
+ public bool Equals(AXmlObject x, AXmlObject y)
+ {
+ if (x == y)
+ return true;
+ if (x == null || y == null)
+ return false;
+ return x.internalObject == y.internalObject;
+ }
+
+ /// <summary>
+ /// Gets the object's hash code so that reused versions of an object have the same hash code.
+ /// </summary>
+ public int GetHashCode(AXmlObject obj)
+ {
+ if (obj == null)
+ return 0;
+ else
+ return obj.internalObject.GetHashCode();
+ }
+ }
+}
diff --git a/ICSharpCode.NRefactory.Xml/TagReader.cs b/ICSharpCode.NRefactory.Xml/TagReader.cs
index fd209c69..60527dea 100644
--- a/ICSharpCode.NRefactory.Xml/TagReader.cs
+++ b/ICSharpCode.NRefactory.Xml/TagReader.cs
@@ -28,30 +28,27 @@ namespace ICSharpCode.NRefactory.Xml
{
class TagReader : TokenReader
{
- readonly TagSoupParser tagSoupParser;
+ readonly AXmlParser tagSoupParser;
+ readonly Stack<string> elementNameStack;
- public TagReader(TagSoupParser tagSoupParser, ITextSource input) : base(input)
+ public TagReader(AXmlParser tagSoupParser, ITextSource input, bool collapseProperlyNestedElements) : base(input)
{
this.tagSoupParser = tagSoupParser;
+ if (collapseProperlyNestedElements)
+ elementNameStack = new Stack<string>();
}
- public InternalObject[] ReadAllObjects()
+ public List<InternalObject> ReadAllObjects()
{
while (HasMoreData()) {
ReadObject();
}
- for (int i = 0; i < objects.Count; i++) {
- objects[i].StartRelativeToParent = 0;
- }
- var arr = objects.ToArray();
- objects.Clear();
- return arr;
+ return objects;
}
- public InternalObject[] ReadAllObjectsIncremental(InternalObject[] oldObjects, List<UnchangedSegment> reuseMap)
+ public List<InternalObject> ReadAllObjectsIncremental(InternalObject[] oldObjects, List<UnchangedSegment> reuseMap)
{
- int oldObjectIndex = 0;
- int oldObjectPosition = 0;
+ ObjectIterator oldObjectIterator = new ObjectIterator(oldObjects);
int reuseMapIndex = 0;
while (reuseMapIndex < reuseMap.Count) {
var reuseEntry = reuseMap[reuseMapIndex];
@@ -66,31 +63,95 @@ namespace ICSharpCode.NRefactory.Xml
// reuse the nodes within this reuseEntry starting at oldOffset:
int oldOffset = this.CurrentLocation - reuseEntry.NewOffset + reuseEntry.OldOffset;
// seek to oldOffset in the oldObjects array:
- while (oldObjectPosition < oldOffset && oldObjectIndex < oldObjects.Length) {
- oldObjectPosition += oldObjects[oldObjectIndex++].Length;
- }
- if (oldObjectPosition == oldOffset) {
+ oldObjectIterator.SkipTo(oldOffset);
+ if (oldObjectIterator.CurrentPosition == oldOffset) {
// reuse old objects within this reuse entry:
int reuseEnd = reuseEntry.OldOffset + reuseEntry.Length;
- while ((oldObjectIndex < oldObjects.Length) && (oldObjectPosition + oldObjects[oldObjectIndex].LengthTouched < reuseEnd)) {
- var oldObject = oldObjects[oldObjectIndex++];
- Debug.Assert(oldObject.StartRelativeToParent == 0);
- oldObjectPosition += oldObject.Length;
- objects.Add(oldObject);
- Skip(oldObject.Length);
+ while (oldObjectIterator.CurrentObject != null && oldObjectIterator.CurrentPosition + oldObjectIterator.CurrentObject.LengthTouched < reuseEnd) {
+ StoreObject(oldObjectIterator.CurrentObject);
+ Skip(oldObjectIterator.CurrentObject.Length);
+ oldObjectIterator.MoveNext();
}
+ reuseMapIndex++; // go to next re-use map
+ } else {
+ // We are in a region where old objects are available, but aren't aligned correctly.
+ // Don't skip this reuse entry, and read a single object so that we can re-align
+ ReadObject();
}
- reuseMapIndex++;
}
while (HasMoreData()) {
ReadObject();
}
- for (int i = 0; i < objects.Count; i++) {
- objects[i].StartRelativeToParent = 0;
+ return objects;
+ }
+
+ void StoreObject(InternalObject obj)
+ {
+ objects.Add(obj);
+
+ // Now combine properly-nested elements:
+ if (elementNameStack == null)
+ return; // parsing tag soup
+ InternalTag tag = obj as InternalTag;
+ if (tag == null)
+ return;
+ if (tag.IsEmptyTag) {
+ // the tag is its own element
+ objects[objects.Count - 1] = new InternalElement() {
+ Length = tag.Length,
+ LengthTouched = tag.LengthTouched,
+ IsPropertyNested = true,
+ StartRelativeToParent = tag.StartRelativeToParent,
+ NestedObjects = new [] { tag.SetStartRelativeToParent(0) }
+ };
+ } else if (tag.IsStartTag) {
+ elementNameStack.Push(tag.Name);
+ } else if (tag.IsEndTag && elementNameStack.Count > 0) {
+ // Now look for the start element:
+ int startIndex = objects.Count - 2;
+ bool ok = false;
+ string expectedName = elementNameStack.Pop();
+ if (tag.Name == expectedName) {
+ while (startIndex > 0) {
+ var startTag = objects[startIndex] as InternalTag;
+ if (startTag != null) {
+ if (startTag.IsStartTag) {
+ ok = (startTag.Name == expectedName);
+ break;
+ } else if (startTag.IsEndTag) {
+ break;
+ }
+ }
+ startIndex--;
+ }
+ }
+ if (ok) {
+ // We found a correct nesting, let's create an element:
+ InternalObject[] nestedObjects = new InternalObject[objects.Count - startIndex];
+ int oldStartRelativeToParent = objects[startIndex].StartRelativeToParent;
+ int pos = 0;
+ int maxLengthTouched = 0;
+ for (int i = 0; i < nestedObjects.Length; i++) {
+ nestedObjects[i] = objects[startIndex + i].SetStartRelativeToParent(pos);
+ maxLengthTouched = Math.Max(maxLengthTouched, pos + nestedObjects[i].LengthTouched);
+ pos += nestedObjects[i].Length;
+ }
+ objects.RemoveRange(startIndex, nestedObjects.Length);
+ objects.Add(
+ new InternalElement {
+ HasEndTag = true,
+ IsPropertyNested = true,
+ Length = pos,
+ LengthTouched = maxLengthTouched,
+ StartRelativeToParent = oldStartRelativeToParent,
+ NestedObjects = nestedObjects
+ });
+ } else {
+ // Mismatched name - the nesting isn't properly;
+ // clear the whole stack so that none of the currently open elements are closed as property-nested.
+ elementNameStack.Clear();
+ }
}
- var arr = objects.ToArray();
- objects.Clear();
- return arr;
}
/// <summary>
@@ -146,7 +207,7 @@ namespace ICSharpCode.NRefactory.Xml
frame.InternalObject.LengthTouched = this.MaxTouchedLocation - internalObjectStartPosition;
frame.InternalObject.SyntaxErrors = GetSyntaxErrors();
if (storeNewObject)
- objects.Add(frame.InternalObject);
+ StoreObject(frame.InternalObject);
internalObjectStartPosition = frame.ParentStartPosition;
}
#endregion
diff --git a/ICSharpCode.NRefactory.Xml/TagSoupParser.cs b/ICSharpCode.NRefactory.Xml/TagSoupParser.cs
deleted file mode 100644
index 292591f5..00000000
--- a/ICSharpCode.NRefactory.Xml/TagSoupParser.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-// 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.Xml.Linq;
-using ICSharpCode.NRefactory.Editor;
-using ICSharpCode.NRefactory.Utils;
-
-namespace ICSharpCode.NRefactory.Xml
-{
- /// <summary>
- /// XML tag soup parser that .
- /// </summary>
- public class TagSoupParser
- {
- /// <summary>
- /// Generate syntax error when seeing entity reference other then the built-in ones
- /// </summary>
- public bool UnknownEntityReferenceIsError { get; set; }
-
- /// <summary>
- /// Parses a document.
- /// </summary>
- /// <returns>Parsed tag soup.</returns>
- public IList<AXmlObject> Parse(ITextSource textSource)
- {
- if (textSource == null)
- throw new ArgumentNullException("textSource");
- var reader = new TagReader(this, textSource);
- var internalObjects = reader.ReadAllObjects();
- return CreatePublic(internalObjects);
- }
-
- IList<AXmlObject> CreatePublic(InternalObject[] internalObjects)
- {
- 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);
- }
-
- /// <summary>
- /// Parses a document incrementally.
- /// </summary>
- /// <param name="oldParserState">The parser state from a previous call to ParseIncremental(). Use null for the first call.</param>
- /// <param name="newTextSource">The text source for the new document version.</param>
- /// <param name="newParserState">Out: the new parser state, pass this to the next ParseIncremental() call.</param>
- /// <returns>Parsed tag soup.</returns>
- public IList<AXmlObject> ParseIncremental(IncrementalParserState oldParserState, ITextSource newTextSource, out IncrementalParserState newParserState)
- {
- if (newTextSource == null)
- throw new ArgumentNullException("newTextSource");
- var reader = new TagReader(this, newTextSource);
- var reuseMap = oldParserState != null ? oldParserState.GetReuseMapTo(newTextSource.Version) : null;
-
- InternalObject[] internalObjects;
- if (reuseMap != null)
- internalObjects = reader.ReadAllObjectsIncremental(oldParserState.Objects, reuseMap);
- else
- internalObjects = reader.ReadAllObjects();
- newParserState = new IncrementalParserState(newTextSource.TextLength, newTextSource.Version, internalObjects);
- return CreatePublic(internalObjects);
- }
- }
-}