Free cookie consent management tool by TermsFeed Policy Generator

source: branches/RemoveBackwardsCompatibility/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/Folding/XmlFoldingStrategy.cs @ 13346

Last change on this file since 13346 was 11700, checked in by jkarder, 10 years ago

#2077: created branch and added first version

File size: 8.2 KB
Line 
1// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4// software and associated documentation files (the "Software"), to deal in the Software
5// without restriction, including without limitation the rights to use, copy, modify, merge,
6// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7// to whom the Software is furnished to do so, subject to the following conditions:
8//
9// The above copyright notice and this permission notice shall be included in all copies or
10// substantial portions of the Software.
11//
12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17// DEALINGS IN THE SOFTWARE.
18
19using System;
20using System.Collections.Generic;
21using System.Linq;
22using System.Text;
23using System.Xml;
24
25using ICSharpCode.AvalonEdit.Document;
26
27namespace ICSharpCode.AvalonEdit.Folding
28{
29  /// <summary>
30  /// Holds information about the start of a fold in an xml string.
31  /// </summary>
32  sealed class XmlFoldStart : NewFolding
33  {
34    internal int StartLine;
35  }
36 
37  /// <summary>
38  /// Determines folds for an xml string in the editor.
39  /// </summary>
40  public class XmlFoldingStrategy
41  {
42    /// <summary>
43    /// Flag indicating whether attributes should be displayed on folded
44    /// elements.
45    /// </summary>
46    public bool ShowAttributesWhenFolded { get; set; }
47   
48    /// <summary>
49    /// Create <see cref="NewFolding"/>s for the specified document and updates the folding manager with them.
50    /// </summary>
51    public void UpdateFoldings(FoldingManager manager, TextDocument document)
52    {
53      int firstErrorOffset;
54      IEnumerable<NewFolding> foldings = CreateNewFoldings(document, out firstErrorOffset);
55      manager.UpdateFoldings(foldings, firstErrorOffset);
56    }
57   
58    /// <summary>
59    /// Create <see cref="NewFolding"/>s for the specified document.
60    /// </summary>
61    public IEnumerable<NewFolding> CreateNewFoldings(TextDocument document, out int firstErrorOffset)
62    {
63      try {
64        XmlTextReader reader = new XmlTextReader(document.CreateReader());
65        reader.XmlResolver = null; // don't resolve DTDs
66        return CreateNewFoldings(document, reader, out firstErrorOffset);
67      } catch (XmlException) {
68        firstErrorOffset = 0;
69        return Enumerable.Empty<NewFolding>();
70      }
71    }
72   
73    /// <summary>
74    /// Create <see cref="NewFolding"/>s for the specified document.
75    /// </summary>
76    public IEnumerable<NewFolding> CreateNewFoldings(TextDocument document, XmlReader reader, out int firstErrorOffset)
77    {
78      Stack<XmlFoldStart> stack = new Stack<XmlFoldStart>();
79      List<NewFolding> foldMarkers = new List<NewFolding>();
80      try {
81        while (reader.Read()) {
82          switch (reader.NodeType) {
83            case XmlNodeType.Element:
84              if (!reader.IsEmptyElement) {
85                XmlFoldStart newFoldStart = CreateElementFoldStart(document, reader);
86                stack.Push(newFoldStart);
87              }
88              break;
89             
90            case XmlNodeType.EndElement:
91              XmlFoldStart foldStart = stack.Pop();
92              CreateElementFold(document, foldMarkers, reader, foldStart);
93              break;
94             
95            case XmlNodeType.Comment:
96              CreateCommentFold(document, foldMarkers, reader);
97              break;
98          }
99        }
100        firstErrorOffset = -1;
101      } catch (XmlException ex) {
102        // ignore errors at invalid positions (prevent ArgumentOutOfRangeException)
103        if (ex.LineNumber >= 1 && ex.LineNumber <= document.LineCount)
104          firstErrorOffset = document.GetOffset(ex.LineNumber, ex.LinePosition);
105        else
106          firstErrorOffset = 0;
107      }
108      foldMarkers.Sort((a,b) => a.StartOffset.CompareTo(b.StartOffset));
109      return foldMarkers;
110    }
111   
112    static int GetOffset(TextDocument document, XmlReader reader)
113    {
114      IXmlLineInfo info = reader as IXmlLineInfo;
115      if (info != null && info.HasLineInfo()) {
116        return document.GetOffset(info.LineNumber, info.LinePosition);
117      } else {
118        throw new ArgumentException("XmlReader does not have positioning information.");
119      }
120    }
121   
122    /// <summary>
123    /// Creates a comment fold if the comment spans more than one line.
124    /// </summary>
125    /// <remarks>The text displayed when the comment is folded is the first
126    /// line of the comment.</remarks>
127    static void CreateCommentFold(TextDocument document, List<NewFolding> foldMarkers, XmlReader reader)
128    {
129      string comment = reader.Value;
130      if (comment != null) {
131        int firstNewLine = comment.IndexOf('\n');
132        if (firstNewLine >= 0) {
133         
134          // Take off 4 chars to get the actual comment start (takes
135          // into account the <!-- chars.
136         
137          int startOffset = GetOffset(document, reader) - 4;
138          int endOffset = startOffset + comment.Length + 7;
139         
140          string foldText = String.Concat("<!--", comment.Substring(0, firstNewLine).TrimEnd('\r') , "-->");
141          foldMarkers.Add(new NewFolding(startOffset, endOffset) { Name = foldText } );
142        }
143      }
144    }
145   
146    /// <summary>
147    /// Creates an XmlFoldStart for the start tag of an element.
148    /// </summary>
149    XmlFoldStart CreateElementFoldStart(TextDocument document, XmlReader reader)
150    {
151      // Take off 1 from the offset returned
152      // from the xml since it points to the start
153      // of the element name and not the beginning
154      // tag.
155      //XmlFoldStart newFoldStart = new XmlFoldStart(reader.Prefix, reader.LocalName, reader.LineNumber - 1, reader.LinePosition - 2);
156      XmlFoldStart newFoldStart = new XmlFoldStart();
157     
158      IXmlLineInfo lineInfo = (IXmlLineInfo)reader;
159      newFoldStart.StartLine = lineInfo.LineNumber;
160      newFoldStart.StartOffset = document.GetOffset(newFoldStart.StartLine, lineInfo.LinePosition - 1);
161     
162      if (this.ShowAttributesWhenFolded && reader.HasAttributes) {
163        newFoldStart.Name = String.Concat("<", reader.Name, " ", GetAttributeFoldText(reader), ">");
164      } else {
165        newFoldStart.Name = String.Concat("<", reader.Name, ">");
166      }
167     
168      return newFoldStart;
169    }
170   
171    /// <summary>
172    /// Create an element fold if the start and end tag are on
173    /// different lines.
174    /// </summary>
175    static void CreateElementFold(TextDocument document, List<NewFolding> foldMarkers, XmlReader reader, XmlFoldStart foldStart)
176    {
177      IXmlLineInfo lineInfo = (IXmlLineInfo)reader;
178      int endLine = lineInfo.LineNumber;
179      if (endLine > foldStart.StartLine) {
180        int endCol = lineInfo.LinePosition + reader.Name.Length + 1;
181        foldStart.EndOffset = document.GetOffset(endLine, endCol);
182        foldMarkers.Add(foldStart);
183      }
184    }
185   
186    /// <summary>
187    /// Gets the element's attributes as a string on one line that will
188    /// be displayed when the element is folded.
189    /// </summary>
190    /// <remarks>
191    /// Currently this puts all attributes from an element on the same
192    /// line of the start tag.  It does not cater for elements where attributes
193    /// are not on the same line as the start tag.
194    /// </remarks>
195    static string GetAttributeFoldText(XmlReader reader)
196    {
197      StringBuilder text = new StringBuilder();
198     
199      for (int i = 0; i < reader.AttributeCount; ++i) {
200        reader.MoveToAttribute(i);
201       
202        text.Append(reader.Name);
203        text.Append("=");
204        text.Append(reader.QuoteChar.ToString());
205        text.Append(XmlEncodeAttributeValue(reader.Value, reader.QuoteChar));
206        text.Append(reader.QuoteChar.ToString());
207       
208        // Append a space if this is not the
209        // last attribute.
210        if (i < reader.AttributeCount - 1) {
211          text.Append(" ");
212        }
213      }
214     
215      return text.ToString();
216    }
217   
218    /// <summary>
219    /// Xml encode the attribute string since the string returned from
220    /// the XmlTextReader is the plain unencoded string and .NET
221    /// does not provide us with an xml encode method.
222    /// </summary>
223    static string XmlEncodeAttributeValue(string attributeValue, char quoteChar)
224    {
225      StringBuilder encodedValue = new StringBuilder(attributeValue);
226     
227      encodedValue.Replace("&", "&amp;");
228      encodedValue.Replace("<", "&lt;");
229      encodedValue.Replace(">", "&gt;");
230     
231      if (quoteChar == '"') {
232        encodedValue.Replace("\"", "&quot;");
233      } else {
234        encodedValue.Replace("'", "&apos;");
235      }
236     
237      return encodedValue.ToString();
238    }
239  }
240}
Note: See TracBrowser for help on using the repository browser.