diff options
author | Daniel Grunwald <daniel@danielgrunwald.de> | 2012-02-21 16:20:51 +0400 |
---|---|---|
committer | Daniel Grunwald <daniel@danielgrunwald.de> | 2012-02-21 16:20:51 +0400 |
commit | d2806842da448bf3ee1f1e8c4e2d1cc7559ef14a (patch) | |
tree | 2717c41356c1899e9eb497e7573b0c402a837d00 /ICSharpCode.NRefactory.Xml | |
parent | d62f36694ec77512943f7ea54ab1f654cec4293d (diff) |
Put properly nested elements into AXmlElement.
Diffstat (limited to 'ICSharpCode.NRefactory.Xml')
-rw-r--r-- | ICSharpCode.NRefactory.Xml/AXmlAttribute.cs | 50 | ||||
-rw-r--r-- | ICSharpCode.NRefactory.Xml/AXmlDocument.cs | 46 | ||||
-rw-r--r-- | ICSharpCode.NRefactory.Xml/AXmlElement.cs | 182 | ||||
-rw-r--r-- | ICSharpCode.NRefactory.Xml/AXmlObject.cs | 9 | ||||
-rw-r--r-- | ICSharpCode.NRefactory.Xml/AXmlParser.cs | 137 | ||||
-rw-r--r-- | ICSharpCode.NRefactory.Xml/AXmlTag.cs | 4 | ||||
-rw-r--r-- | ICSharpCode.NRefactory.Xml/IAXmlVisitor.cs | 6 | ||||
-rw-r--r-- | ICSharpCode.NRefactory.Xml/ICSharpCode.NRefactory.Xml.csproj | 6 | ||||
-rw-r--r-- | ICSharpCode.NRefactory.Xml/InternalDocument.cs | 29 | ||||
-rw-r--r-- | ICSharpCode.NRefactory.Xml/ObjectIterator.cs | 91 | ||||
-rw-r--r-- | ICSharpCode.NRefactory.Xml/ReuseEqualityComparer.cs | 52 | ||||
-rw-r--r-- | ICSharpCode.NRefactory.Xml/TagReader.cs | 119 | ||||
-rw-r--r-- | ICSharpCode.NRefactory.Xml/TagSoupParser.cs | 85 |
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 "<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 "<" </summary> public bool IsStartOrEmptyTag { get { return internalObject.IsStartOrEmptyTag; } } /// <summary> True if tag starts with "<" and ends with ">" </summary> - public bool IsStartTag { get { return internalObject.IsStartOrEmptyTag && ClosingBracket == ">"; } } + public bool IsStartTag { get { return internalObject.IsStartTag; } } /// <summary> True if tag starts with "<" and does not end with ">" </summary> - public bool IsEmptyTag { get { return internalObject.IsStartOrEmptyTag && ClosingBracket != ">" ; } } + public bool IsEmptyTag { get { return internalObject.IsEmptyTag; } } /// <summary> True if tag starts with "</" </summary> public bool IsEndTag { get { return internalObject.IsEndTag; } } /// <summary> True if tag starts with "<?" </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 "<" </summary> public bool IsStartOrEmptyTag { get { return OpeningBracket == "<"; } } + /// <summary> True if tag starts with "<" and ends with ">" </summary> + public bool IsStartTag { get { return OpeningBracket == "<" && ClosingBracket == ">"; } } + /// <summary> True if tag starts with "<" and does not end with ">" </summary> + public bool IsEmptyTag { get { return OpeningBracket == "<" && ClosingBracket != ">" ; } } /// <summary> True if tag starts with "</" </summary> public bool IsEndTag { get { return OpeningBracket == "</"; } } /// <summary> True if tag starts with "<?" </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); - } - } -} |