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 " + /// Reads any of the know closing brackets. (only full bracket) + /// Context: any + /// + bool TryReadClosingBracket(out string bracket) + { + // We are using a lot of string literals so that the memory instances are shared + if (TryRead('>')) { + bracket = ">"; + } else if (TryRead("/>")) { + bracket = "/>"; + } else if (TryRead("?>")) { + bracket = "?>"; + } else if (TryRead("-->")) { + bracket = "-->"; + } else if (TryRead("]]>")) { + bracket = "]]>"; + } else { + bracket = string.Empty; + return false; + } + return true; + } + #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