Free cookie consent management tool by TermsFeed Policy Generator

source: branches/Async/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/Highlighting/Xshd/XmlHighlightingDefinition.cs @ 13329

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

#2077: created branch and added first version

File size: 14.4 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.Diagnostics;
22using System.Linq;
23using System.Runtime.Serialization;
24using System.Text;
25using System.Text.RegularExpressions;
26using ICSharpCode.AvalonEdit.Utils;
27
28namespace ICSharpCode.AvalonEdit.Highlighting.Xshd
29{
30  [Serializable]
31  sealed class XmlHighlightingDefinition : IHighlightingDefinition
32  {
33    public string Name { get; private set; }
34   
35    public XmlHighlightingDefinition(XshdSyntaxDefinition xshd, IHighlightingDefinitionReferenceResolver resolver)
36    {
37      this.Name = xshd.Name;
38      // Create HighlightingRuleSet instances
39      var rnev = new RegisterNamedElementsVisitor(this);
40      xshd.AcceptElements(rnev);
41      // Assign MainRuleSet so that references can be resolved
42      foreach (XshdElement element in xshd.Elements) {
43        XshdRuleSet xrs = element as XshdRuleSet;
44        if (xrs != null && xrs.Name == null) {
45          if (MainRuleSet != null)
46            throw Error(element, "Duplicate main RuleSet. There must be only one nameless RuleSet!");
47          else
48            MainRuleSet = rnev.ruleSets[xrs];
49        }
50      }
51      if (MainRuleSet == null)
52        throw new HighlightingDefinitionInvalidException("Could not find main RuleSet.");
53      // Translate elements within the rulesets (resolving references and processing imports)
54      xshd.AcceptElements(new TranslateElementVisitor(this, rnev.ruleSets, resolver));
55     
56      foreach (var p in xshd.Elements.OfType<XshdProperty>())
57        propDict.Add(p.Name, p.Value);
58    }
59   
60    #region RegisterNamedElements
61    sealed class RegisterNamedElementsVisitor : IXshdVisitor
62    {
63      XmlHighlightingDefinition def;
64      internal readonly Dictionary<XshdRuleSet, HighlightingRuleSet> ruleSets
65        = new Dictionary<XshdRuleSet, HighlightingRuleSet>();
66     
67      public RegisterNamedElementsVisitor(XmlHighlightingDefinition def)
68      {
69        this.def = def;
70      }
71     
72      public object VisitRuleSet(XshdRuleSet ruleSet)
73      {
74        HighlightingRuleSet hrs = new HighlightingRuleSet();
75        ruleSets.Add(ruleSet, hrs);
76        if (ruleSet.Name != null) {
77          if (ruleSet.Name.Length == 0)
78            throw Error(ruleSet, "Name must not be the empty string");
79          if (def.ruleSetDict.ContainsKey(ruleSet.Name))
80            throw Error(ruleSet, "Duplicate rule set name '" + ruleSet.Name + "'.");
81         
82          def.ruleSetDict.Add(ruleSet.Name, hrs);
83        }
84        ruleSet.AcceptElements(this);
85        return null;
86      }
87     
88      public object VisitColor(XshdColor color)
89      {
90        if (color.Name != null) {
91          if (color.Name.Length == 0)
92            throw Error(color, "Name must not be the empty string");
93          if (def.colorDict.ContainsKey(color.Name))
94            throw Error(color, "Duplicate color name '" + color.Name + "'.");
95         
96          def.colorDict.Add(color.Name, new HighlightingColor());
97        }
98        return null;
99      }
100     
101      public object VisitKeywords(XshdKeywords keywords)
102      {
103        return keywords.ColorReference.AcceptVisitor(this);
104      }
105     
106      public object VisitSpan(XshdSpan span)
107      {
108        span.BeginColorReference.AcceptVisitor(this);
109        span.SpanColorReference.AcceptVisitor(this);
110        span.EndColorReference.AcceptVisitor(this);
111        return span.RuleSetReference.AcceptVisitor(this);
112      }
113     
114      public object VisitImport(XshdImport import)
115      {
116        return import.RuleSetReference.AcceptVisitor(this);
117      }
118     
119      public object VisitRule(XshdRule rule)
120      {
121        return rule.ColorReference.AcceptVisitor(this);
122      }
123    }
124    #endregion
125   
126    #region TranslateElements
127    sealed class TranslateElementVisitor : IXshdVisitor
128    {
129      readonly XmlHighlightingDefinition def;
130      readonly Dictionary<XshdRuleSet, HighlightingRuleSet> ruleSetDict;
131      readonly Dictionary<HighlightingRuleSet, XshdRuleSet> reverseRuleSetDict;
132      readonly IHighlightingDefinitionReferenceResolver resolver;
133      HashSet<XshdRuleSet> processingStartedRuleSets = new HashSet<XshdRuleSet>();
134      HashSet<XshdRuleSet> processedRuleSets = new HashSet<XshdRuleSet>();
135      bool ignoreCase;
136     
137      public TranslateElementVisitor(XmlHighlightingDefinition def, Dictionary<XshdRuleSet, HighlightingRuleSet> ruleSetDict, IHighlightingDefinitionReferenceResolver resolver)
138      {
139        Debug.Assert(def != null);
140        Debug.Assert(ruleSetDict != null);
141        this.def = def;
142        this.ruleSetDict = ruleSetDict;
143        this.resolver = resolver;
144        reverseRuleSetDict = new Dictionary<HighlightingRuleSet, XshdRuleSet>();
145        foreach (var pair in ruleSetDict) {
146          reverseRuleSetDict.Add(pair.Value, pair.Key);
147        }
148      }
149     
150      public object VisitRuleSet(XshdRuleSet ruleSet)
151      {
152        HighlightingRuleSet rs = ruleSetDict[ruleSet];
153        if (processedRuleSets.Contains(ruleSet))
154          return rs;
155        if (!processingStartedRuleSets.Add(ruleSet))
156          throw Error(ruleSet, "RuleSet cannot be processed because it contains cyclic <Import>");
157       
158        bool oldIgnoreCase = ignoreCase;
159        if (ruleSet.IgnoreCase != null)
160          ignoreCase = ruleSet.IgnoreCase.Value;
161       
162        rs.Name = ruleSet.Name;
163       
164        foreach (XshdElement element in ruleSet.Elements) {
165          object o = element.AcceptVisitor(this);
166          HighlightingRuleSet elementRuleSet = o as HighlightingRuleSet;
167          if (elementRuleSet != null) {
168            Merge(rs, elementRuleSet);
169          } else {
170            HighlightingSpan span = o as HighlightingSpan;
171            if (span != null) {
172              rs.Spans.Add(span);
173            } else {
174              HighlightingRule elementRule = o as HighlightingRule;
175              if (elementRule != null) {
176                rs.Rules.Add(elementRule);
177              }
178            }
179          }
180        }
181       
182        ignoreCase = oldIgnoreCase;
183        processedRuleSets.Add(ruleSet);
184       
185        return rs;
186      }
187     
188      static void Merge(HighlightingRuleSet target, HighlightingRuleSet source)
189      {
190        target.Rules.AddRange(source.Rules);
191        target.Spans.AddRange(source.Spans);
192      }
193     
194      public object VisitColor(XshdColor color)
195      {
196        HighlightingColor c;
197        if (color.Name != null)
198          c = def.colorDict[color.Name];
199        else if (color.Foreground == null && color.FontStyle == null && color.FontWeight == null)
200          return null;
201        else
202          c = new HighlightingColor();
203       
204        c.Name = color.Name;
205        c.Foreground = color.Foreground;
206        c.Background = color.Background;
207        c.FontStyle = color.FontStyle;
208        c.FontWeight = color.FontWeight;
209        return c;
210      }
211     
212      public object VisitKeywords(XshdKeywords keywords)
213      {
214        if (keywords.Words.Count == 0)
215          return Error(keywords, "Keyword group must not be empty.");
216        foreach (string keyword in keywords.Words) {
217          if (string.IsNullOrEmpty(keyword))
218            throw Error(keywords, "Cannot use empty string as keyword");
219        }
220        StringBuilder keyWordRegex = new StringBuilder();
221        // We can use "\b" only where the keyword starts/ends with a letter or digit, otherwise we don't
222        // highlight correctly. (example: ILAsm-Mode.xshd with ".maxstack" keyword)
223        if (keywords.Words.All(IsSimpleWord)) {
224          keyWordRegex.Append(@"\b(?>");
225          // (?> = atomic group
226          // atomic groups increase matching performance, but we
227          // must ensure that the keywords are sorted correctly.
228          // "\b(?>in|int)\b" does not match "int" because the atomic group captures "in".
229          // To solve this, we are sorting the keywords by descending length.
230          int i = 0;
231          foreach (string keyword in keywords.Words.OrderByDescending(w=>w.Length)) {
232            if (i++ > 0)
233              keyWordRegex.Append('|');
234            keyWordRegex.Append(Regex.Escape(keyword));
235          }
236          keyWordRegex.Append(@")\b");
237        } else {
238          keyWordRegex.Append('(');
239          int i = 0;
240          foreach (string keyword in keywords.Words) {
241            if (i++ > 0)
242              keyWordRegex.Append('|');
243            if (char.IsLetterOrDigit(keyword[0]))
244              keyWordRegex.Append(@"\b");
245            keyWordRegex.Append(Regex.Escape(keyword));
246            if (char.IsLetterOrDigit(keyword[keyword.Length - 1]))
247              keyWordRegex.Append(@"\b");
248          }
249          keyWordRegex.Append(')');
250        }
251        return new HighlightingRule {
252          Color = GetColor(keywords, keywords.ColorReference),
253          Regex = CreateRegex(keywords, keyWordRegex.ToString(), XshdRegexType.Default)
254        };
255      }
256     
257      static bool IsSimpleWord(string word)
258      {
259        return char.IsLetterOrDigit(word[0]) && char.IsLetterOrDigit(word, word.Length - 1);
260      }
261     
262      Regex CreateRegex(XshdElement position, string regex, XshdRegexType regexType)
263      {
264        if (regex == null)
265          throw Error(position, "Regex missing");
266        RegexOptions options = RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture;
267        if (regexType == XshdRegexType.IgnorePatternWhitespace)
268          options |= RegexOptions.IgnorePatternWhitespace;
269        if (ignoreCase)
270          options |= RegexOptions.IgnoreCase;
271        try {
272          return new Regex(regex, options);
273        } catch (ArgumentException ex) {
274          throw Error(position, ex.Message);
275        }
276      }
277     
278      HighlightingColor GetColor(XshdElement position, XshdReference<XshdColor> colorReference)
279      {
280        if (colorReference.InlineElement != null) {
281          return (HighlightingColor)colorReference.InlineElement.AcceptVisitor(this);
282        } else if (colorReference.ReferencedElement != null) {
283          IHighlightingDefinition definition = GetDefinition(position, colorReference.ReferencedDefinition);
284          HighlightingColor color = definition.GetNamedColor(colorReference.ReferencedElement);
285          if (color == null)
286            throw Error(position, "Could not find color named '" + colorReference.ReferencedElement + "'.");
287          return color;
288        } else {
289          return null;
290        }
291      }
292     
293      IHighlightingDefinition GetDefinition(XshdElement position, string definitionName)
294      {
295        if (definitionName == null)
296          return def;
297        if (resolver == null)
298          throw Error(position, "Resolving references to other syntax definitions is not possible because the IHighlightingDefinitionReferenceResolver is null.");
299        IHighlightingDefinition d = resolver.GetDefinition(definitionName);
300        if (d == null)
301          throw Error(position, "Could not find definition with name '" + definitionName + "'.");
302        return d;
303      }
304     
305      HighlightingRuleSet GetRuleSet(XshdElement position, XshdReference<XshdRuleSet> ruleSetReference)
306      {
307        if (ruleSetReference.InlineElement != null) {
308          return (HighlightingRuleSet)ruleSetReference.InlineElement.AcceptVisitor(this);
309        } else if (ruleSetReference.ReferencedElement != null) {
310          IHighlightingDefinition definition = GetDefinition(position, ruleSetReference.ReferencedDefinition);
311          HighlightingRuleSet ruleSet = definition.GetNamedRuleSet(ruleSetReference.ReferencedElement);
312          if (ruleSet == null)
313            throw Error(position, "Could not find rule set named '" + ruleSetReference.ReferencedElement + "'.");
314          return ruleSet;
315        } else {
316          return null;
317        }
318      }
319     
320      public object VisitSpan(XshdSpan span)
321      {
322        string endRegex = span.EndRegex;
323        if (string.IsNullOrEmpty(span.BeginRegex) && string.IsNullOrEmpty(span.EndRegex))
324          throw Error(span, "Span has no start/end regex.");
325        if (!span.Multiline) {
326          if (endRegex == null)
327            endRegex = "$";
328          else if (span.EndRegexType == XshdRegexType.IgnorePatternWhitespace)
329            endRegex = "($|" + endRegex + "\n)";
330          else
331            endRegex = "($|" + endRegex + ")";
332        }
333        HighlightingColor wholeSpanColor = GetColor(span, span.SpanColorReference);
334        return new HighlightingSpan {
335          StartExpression = CreateRegex(span, span.BeginRegex, span.BeginRegexType),
336          EndExpression = CreateRegex(span, endRegex, span.EndRegexType),
337          RuleSet = GetRuleSet(span, span.RuleSetReference),
338          StartColor = GetColor(span, span.BeginColorReference),
339          SpanColor = wholeSpanColor,
340          EndColor = GetColor(span, span.EndColorReference),
341          SpanColorIncludesStart = true,
342          SpanColorIncludesEnd = true
343        };
344      }
345     
346      public object VisitImport(XshdImport import)
347      {
348        HighlightingRuleSet hrs = GetRuleSet(import, import.RuleSetReference);
349        XshdRuleSet inputRuleSet;
350        if (reverseRuleSetDict.TryGetValue(hrs, out inputRuleSet)) {
351          // ensure the ruleset is processed before importing its members
352          if (VisitRuleSet(inputRuleSet) != hrs)
353            Debug.Fail("this shouldn't happen");
354        }
355        return hrs;
356      }
357     
358      public object VisitRule(XshdRule rule)
359      {
360        return new HighlightingRule {
361          Color = GetColor(rule, rule.ColorReference),
362          Regex = CreateRegex(rule, rule.Regex, rule.RegexType)
363        };
364      }
365    }
366    #endregion
367   
368    static Exception Error(XshdElement element, string message)
369    {
370      if (element.LineNumber > 0)
371        return new HighlightingDefinitionInvalidException(
372          "Error at line " + element.LineNumber + ":\n" + message);
373      else
374        return new HighlightingDefinitionInvalidException(message);
375    }
376   
377    Dictionary<string, HighlightingRuleSet> ruleSetDict = new Dictionary<string, HighlightingRuleSet>();
378    Dictionary<string, HighlightingColor> colorDict = new Dictionary<string, HighlightingColor>();
379    [OptionalField]
380    Dictionary<string, string> propDict = new Dictionary<string, string>();
381   
382    public HighlightingRuleSet MainRuleSet { get; private set; }
383   
384    public HighlightingRuleSet GetNamedRuleSet(string name)
385    {
386      if (string.IsNullOrEmpty(name))
387        return MainRuleSet;
388      HighlightingRuleSet r;
389      if (ruleSetDict.TryGetValue(name, out r))
390        return r;
391      else
392        return null;
393    }
394   
395    public HighlightingColor GetNamedColor(string name)
396    {
397      HighlightingColor c;
398      if (colorDict.TryGetValue(name, out c))
399        return c;
400      else
401        return null;
402    }
403   
404    public IEnumerable<HighlightingColor> NamedHighlightingColors {
405      get {
406        return colorDict.Values;
407      }
408    }
409   
410    public override string ToString()
411    {
412      return this.Name;
413    }
414   
415    public IDictionary<string, string> Properties {
416      get {
417        return propDict;
418      }
419    }
420  }
421}
Note: See TracBrowser for help on using the repository browser.