mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
325 lines
11 KiB
325 lines
11 KiB
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) |
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) |
|
|
|
using System; |
|
using System.Globalization; |
|
using System.Text; |
|
using System.Text.RegularExpressions; |
|
using System.Windows; |
|
using System.Windows.Media; |
|
using System.Xml; |
|
using System.Xml.Schema; |
|
|
|
using ICSharpCode.AvalonEdit.Utils; |
|
|
|
namespace ICSharpCode.AvalonEdit.Highlighting.Xshd |
|
{ |
|
/// <summary> |
|
/// Loads .xshd files, version 1.0. |
|
/// </summary> |
|
sealed class V1Loader |
|
{ |
|
static XmlSchemaSet schemaSet; |
|
|
|
static XmlSchemaSet SchemaSet { |
|
get { |
|
if (schemaSet == null) { |
|
schemaSet = HighlightingLoader.LoadSchemaSet(new XmlTextReader( |
|
Resources.OpenStream("ModeV1.xsd"))); |
|
} |
|
return schemaSet; |
|
} |
|
} |
|
|
|
public static XshdSyntaxDefinition LoadDefinition(XmlReader reader, bool skipValidation) |
|
{ |
|
reader = HighlightingLoader.GetValidatingReader(reader, false, skipValidation ? null : SchemaSet); |
|
XmlDocument document = new XmlDocument(); |
|
document.Load(reader); |
|
V1Loader loader = new V1Loader(); |
|
return loader.ParseDefinition(document.DocumentElement); |
|
} |
|
|
|
XshdSyntaxDefinition ParseDefinition(XmlElement syntaxDefinition) |
|
{ |
|
XshdSyntaxDefinition def = new XshdSyntaxDefinition(); |
|
def.Name = syntaxDefinition.GetAttributeOrNull("name"); |
|
if (syntaxDefinition.HasAttribute("extensions")) { |
|
def.Extensions.AddRange(syntaxDefinition.GetAttribute("extensions").Split(';', '|')); |
|
} |
|
|
|
XshdRuleSet mainRuleSetElement = null; |
|
foreach (XmlElement element in syntaxDefinition.GetElementsByTagName("RuleSet")) { |
|
XshdRuleSet ruleSet = ImportRuleSet(element); |
|
def.Elements.Add(ruleSet); |
|
if (ruleSet.Name == null) |
|
mainRuleSetElement = ruleSet; |
|
|
|
if (syntaxDefinition["Digits"] != null) { |
|
// create digit highlighting rule |
|
|
|
const string optionalExponent = @"([eE][+-]?[0-9]+)?"; |
|
const string floatingPoint = @"\.[0-9]+"; |
|
ruleSet.Elements.Add( |
|
new XshdRule { |
|
ColorReference = GetColorReference(syntaxDefinition["Digits"]), |
|
RegexType = XshdRegexType.IgnorePatternWhitespace, |
|
Regex = @"\b0[xX][0-9a-fA-F]+" |
|
+ @"|" |
|
+ @"(\b\d+(" + floatingPoint + ")?" |
|
+ @"|" + floatingPoint + ")" |
|
+ optionalExponent |
|
}); |
|
} |
|
} |
|
|
|
if (syntaxDefinition.HasAttribute("extends") && mainRuleSetElement != null) { |
|
// convert 'extends="HTML"' to '<Import ruleSet="HTML/" />' in main rule set. |
|
mainRuleSetElement.Elements.Add( |
|
new XshdImport { RuleSetReference = new XshdReference<XshdRuleSet>( |
|
syntaxDefinition.GetAttribute("extends"), string.Empty |
|
) }); |
|
} |
|
return def; |
|
} |
|
|
|
static XshdColor GetColorFromElement(XmlElement element) |
|
{ |
|
if (!element.HasAttribute("bold") && !element.HasAttribute("italic") && !element.HasAttribute("color") && !element.HasAttribute("bgcolor")) |
|
return null; |
|
XshdColor color = new XshdColor(); |
|
if (element.HasAttribute("bold")) |
|
color.FontWeight = XmlConvert.ToBoolean(element.GetAttribute("bold")) ? FontWeights.Bold : FontWeights.Normal; |
|
if (element.HasAttribute("italic")) |
|
color.FontStyle = XmlConvert.ToBoolean(element.GetAttribute("italic")) ? FontStyles.Italic : FontStyles.Normal; |
|
if (element.HasAttribute("color")) |
|
color.Foreground = ParseColor(element.GetAttribute("color")); |
|
if (element.HasAttribute("bgcolor")) |
|
color.Background = ParseColor(element.GetAttribute("bgcolor")); |
|
return color; |
|
} |
|
|
|
static XshdReference<XshdColor> GetColorReference(XmlElement element) |
|
{ |
|
XshdColor color = GetColorFromElement(element); |
|
if (color != null) |
|
return new XshdReference<XshdColor>(color); |
|
else |
|
return new XshdReference<XshdColor>(); |
|
} |
|
|
|
static HighlightingBrush ParseColor(string c) |
|
{ |
|
if (c.StartsWith("#", StringComparison.Ordinal)) { |
|
int a = 255; |
|
int offset = 0; |
|
if (c.Length > 7) { |
|
offset = 2; |
|
a = Int32.Parse(c.Substring(1,2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); |
|
} |
|
|
|
int r = Int32.Parse(c.Substring(1 + offset,2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); |
|
int g = Int32.Parse(c.Substring(3 + offset,2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); |
|
int b = Int32.Parse(c.Substring(5 + offset,2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); |
|
return new SimpleHighlightingBrush(Color.FromArgb((byte)a, (byte)r, (byte)g, (byte)b)); |
|
} else if (c.StartsWith("SystemColors.", StringComparison.Ordinal)) { |
|
return V2Loader.GetSystemColorBrush(null, c); |
|
} else { |
|
return new SimpleHighlightingBrush((Color)V2Loader.ColorConverter.ConvertFromInvariantString(c)); |
|
} |
|
} |
|
|
|
char ruleSetEscapeCharacter; |
|
|
|
XshdRuleSet ImportRuleSet(XmlElement element) |
|
{ |
|
XshdRuleSet ruleSet = new XshdRuleSet(); |
|
ruleSet.Name = element.GetAttributeOrNull("name"); |
|
|
|
if (element.HasAttribute("escapecharacter")) { |
|
ruleSetEscapeCharacter = element.GetAttribute("escapecharacter")[0]; |
|
} else { |
|
ruleSetEscapeCharacter = '\0'; |
|
} |
|
|
|
if (element.HasAttribute("reference")) { |
|
ruleSet.Elements.Add( |
|
new XshdImport { RuleSetReference = new XshdReference<XshdRuleSet>( |
|
element.GetAttribute("reference"), string.Empty |
|
) }); |
|
} |
|
ruleSet.IgnoreCase = element.GetBoolAttribute("ignorecase"); |
|
|
|
foreach (XmlElement el in element.GetElementsByTagName("KeyWords")) { |
|
XshdKeywords keywords = new XshdKeywords(); |
|
keywords.ColorReference = GetColorReference(el); |
|
// we have to handle old syntax highlighting definitions that contain |
|
// empty keywords or empty keyword groups |
|
foreach (XmlElement node in el.GetElementsByTagName("Key")) { |
|
string word = node.GetAttribute("word"); |
|
if (!string.IsNullOrEmpty(word)) |
|
keywords.Words.Add(word); |
|
} |
|
if (keywords.Words.Count > 0) { |
|
ruleSet.Elements.Add(keywords); |
|
} |
|
} |
|
|
|
foreach (XmlElement el in element.GetElementsByTagName("Span")) { |
|
ruleSet.Elements.Add(ImportSpan(el)); |
|
} |
|
|
|
foreach (XmlElement el in element.GetElementsByTagName("MarkPrevious")) { |
|
ruleSet.Elements.Add(ImportMarkPrevNext(el, false)); |
|
} |
|
foreach (XmlElement el in element.GetElementsByTagName("MarkFollowing")) { |
|
ruleSet.Elements.Add(ImportMarkPrevNext(el, true)); |
|
} |
|
|
|
return ruleSet; |
|
} |
|
|
|
static XshdRule ImportMarkPrevNext(XmlElement el, bool markFollowing) |
|
{ |
|
bool markMarker = el.GetBoolAttribute("markmarker") ?? false; |
|
string what = Regex.Escape(el.InnerText); |
|
const string identifier = @"[\d\w_]+"; |
|
const string whitespace = @"\s*"; |
|
|
|
string regex; |
|
if (markFollowing) { |
|
if (markMarker) { |
|
regex = what + whitespace + identifier; |
|
} else { |
|
regex = "(?<=(" + what + whitespace + "))" + identifier; |
|
} |
|
} else { |
|
if (markMarker) { |
|
regex = identifier + whitespace + what; |
|
} else { |
|
regex = identifier + "(?=(" + whitespace + what + "))"; |
|
} |
|
} |
|
return new XshdRule { |
|
ColorReference = GetColorReference(el), |
|
Regex = regex, |
|
RegexType = XshdRegexType.IgnorePatternWhitespace |
|
}; |
|
} |
|
|
|
XshdSpan ImportSpan(XmlElement element) |
|
{ |
|
XshdSpan span = new XshdSpan(); |
|
if (element.HasAttribute("rule")) { |
|
span.RuleSetReference = new XshdReference<XshdRuleSet>(null, element.GetAttribute("rule")); |
|
} |
|
char escapeCharacter = ruleSetEscapeCharacter; |
|
if (element.HasAttribute("escapecharacter")) { |
|
escapeCharacter = element.GetAttribute("escapecharacter")[0]; |
|
} |
|
span.Multiline = !(element.GetBoolAttribute("stopateol") ?? false); |
|
|
|
span.SpanColorReference = GetColorReference(element); |
|
|
|
span.BeginRegexType = XshdRegexType.IgnorePatternWhitespace; |
|
span.BeginRegex = ImportRegex(element["Begin"].InnerText, |
|
element["Begin"].GetBoolAttribute("singleword") ?? false, |
|
element["Begin"].GetBoolAttribute("startofline")); |
|
span.BeginColorReference = GetColorReference(element["Begin"]); |
|
|
|
string endElementText = string.Empty; |
|
if (element["End"] != null) { |
|
span.EndRegexType = XshdRegexType.IgnorePatternWhitespace; |
|
endElementText = element["End"].InnerText; |
|
span.EndRegex = ImportRegex(endElementText, |
|
element["End"].GetBoolAttribute("singleword") ?? false, |
|
null); |
|
span.EndColorReference = GetColorReference(element["End"]); |
|
} |
|
|
|
if (escapeCharacter != '\0') { |
|
XshdRuleSet ruleSet = new XshdRuleSet(); |
|
if (endElementText.Length == 1 && endElementText[0] == escapeCharacter) { |
|
// ""-style escape |
|
ruleSet.Elements.Add(new XshdSpan { |
|
BeginRegex = Regex.Escape(endElementText + endElementText), |
|
EndRegex = "" |
|
}); |
|
} else { |
|
// \"-style escape |
|
ruleSet.Elements.Add(new XshdSpan { |
|
BeginRegex = Regex.Escape(escapeCharacter.ToString()), |
|
EndRegex = "." |
|
}); |
|
} |
|
if (span.RuleSetReference.ReferencedElement != null) { |
|
ruleSet.Elements.Add(new XshdImport { RuleSetReference = span.RuleSetReference }); |
|
} |
|
span.RuleSetReference = new XshdReference<XshdRuleSet>(ruleSet); |
|
} |
|
return span; |
|
} |
|
|
|
static string ImportRegex(string expr, bool singleWord, bool? startOfLine) |
|
{ |
|
StringBuilder b = new StringBuilder(); |
|
if (startOfLine != null) { |
|
if (startOfLine.Value) { |
|
b.Append(@"(?<=(^\s*))"); |
|
} else { |
|
b.Append(@"(?<!(^\s*))"); |
|
} |
|
} else { |
|
if (singleWord) |
|
b.Append(@"\b"); |
|
} |
|
for (int i = 0; i < expr.Length; i++) { |
|
char c = expr[i]; |
|
if (c == '@') { |
|
++i; |
|
if (i == expr.Length) |
|
throw new HighlightingDefinitionInvalidException("Unexpected end of @ sequence, use @@ to look for a single @."); |
|
switch (expr[i]) { |
|
case 'C': // match whitespace or punctuation |
|
b.Append(@"[^\w\d_]"); |
|
break; |
|
case '!': // negative lookahead |
|
{ |
|
StringBuilder whatmatch = new StringBuilder(); |
|
++i; |
|
while (i < expr.Length && expr[i] != '@') { |
|
whatmatch.Append(expr[i++]); |
|
} |
|
b.Append("(?!("); |
|
b.Append(Regex.Escape(whatmatch.ToString())); |
|
b.Append("))"); |
|
} |
|
break; |
|
case '-': // negative lookbehind |
|
{ |
|
StringBuilder whatmatch = new StringBuilder(); |
|
++i; |
|
while (i < expr.Length && expr[i] != '@') { |
|
whatmatch.Append(expr[i++]); |
|
} |
|
b.Append("(?<!("); |
|
b.Append(Regex.Escape(whatmatch.ToString())); |
|
b.Append("))"); |
|
} |
|
break; |
|
case '@': |
|
b.Append("@"); |
|
break; |
|
default: |
|
throw new HighlightingDefinitionInvalidException("Unknown character in @ sequence."); |
|
} |
|
} else { |
|
b.Append(Regex.Escape(c.ToString())); |
|
} |
|
} |
|
if (singleWord) |
|
b.Append(@"\b"); |
|
return b.ToString(); |
|
} |
|
} |
|
}
|
|
|