source: branches/2994-AutoDiffForIntervals/HeuristicLab.CodeEditor/3.4/LanguageFeatures/CodeFolding/XML/XmlCodeFoldingStrategy.cs @ 17209

Last change on this file since 17209 was 17209, checked in by gkronber, 4 months ago

#2994: merged r17132:17198 from trunk to branch

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