326 lines
11 KiB
C#
326 lines
11 KiB
C#
// 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();
|
|
}
|
|
}
|
|
}
|