[11700] | 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 | |
---|
| 19 | using System; |
---|
| 20 | using System.Globalization; |
---|
| 21 | using System.Text; |
---|
| 22 | using System.Text.RegularExpressions; |
---|
| 23 | using System.Windows; |
---|
| 24 | using System.Windows.Media; |
---|
| 25 | using System.Xml; |
---|
| 26 | using System.Xml.Schema; |
---|
| 27 | |
---|
| 28 | using ICSharpCode.AvalonEdit.Utils; |
---|
| 29 | |
---|
| 30 | namespace ICSharpCode.AvalonEdit.Highlighting.Xshd |
---|
| 31 | { |
---|
| 32 | /// <summary> |
---|
| 33 | /// Loads .xshd files, version 1.0. |
---|
| 34 | /// </summary> |
---|
| 35 | sealed class V1Loader |
---|
| 36 | { |
---|
| 37 | static XmlSchemaSet schemaSet; |
---|
| 38 | |
---|
| 39 | static XmlSchemaSet SchemaSet { |
---|
| 40 | get { |
---|
| 41 | if (schemaSet == null) { |
---|
| 42 | schemaSet = HighlightingLoader.LoadSchemaSet(new XmlTextReader( |
---|
| 43 | Resources.OpenStream("ModeV1.xsd"))); |
---|
| 44 | } |
---|
| 45 | return schemaSet; |
---|
| 46 | } |
---|
| 47 | } |
---|
| 48 | |
---|
| 49 | public static XshdSyntaxDefinition LoadDefinition(XmlReader reader, bool skipValidation) |
---|
| 50 | { |
---|
| 51 | reader = HighlightingLoader.GetValidatingReader(reader, false, skipValidation ? null : SchemaSet); |
---|
| 52 | XmlDocument document = new XmlDocument(); |
---|
| 53 | document.Load(reader); |
---|
| 54 | V1Loader loader = new V1Loader(); |
---|
| 55 | return loader.ParseDefinition(document.DocumentElement); |
---|
| 56 | } |
---|
| 57 | |
---|
| 58 | XshdSyntaxDefinition ParseDefinition(XmlElement syntaxDefinition) |
---|
| 59 | { |
---|
| 60 | XshdSyntaxDefinition def = new XshdSyntaxDefinition(); |
---|
| 61 | def.Name = syntaxDefinition.GetAttributeOrNull("name"); |
---|
| 62 | if (syntaxDefinition.HasAttribute("extensions")) { |
---|
| 63 | def.Extensions.AddRange(syntaxDefinition.GetAttribute("extensions").Split(';', '|')); |
---|
| 64 | } |
---|
| 65 | |
---|
| 66 | XshdRuleSet mainRuleSetElement = null; |
---|
| 67 | foreach (XmlElement element in syntaxDefinition.GetElementsByTagName("RuleSet")) { |
---|
| 68 | XshdRuleSet ruleSet = ImportRuleSet(element); |
---|
| 69 | def.Elements.Add(ruleSet); |
---|
| 70 | if (ruleSet.Name == null) |
---|
| 71 | mainRuleSetElement = ruleSet; |
---|
| 72 | |
---|
| 73 | if (syntaxDefinition["Digits"] != null) { |
---|
| 74 | // create digit highlighting rule |
---|
| 75 | |
---|
| 76 | const string optionalExponent = @"([eE][+-]?[0-9]+)?"; |
---|
| 77 | const string floatingPoint = @"\.[0-9]+"; |
---|
| 78 | ruleSet.Elements.Add( |
---|
| 79 | new XshdRule { |
---|
| 80 | ColorReference = GetColorReference(syntaxDefinition["Digits"]), |
---|
| 81 | RegexType = XshdRegexType.IgnorePatternWhitespace, |
---|
| 82 | Regex = @"\b0[xX][0-9a-fA-F]+" |
---|
| 83 | + @"|" |
---|
| 84 | + @"(\b\d+(" + floatingPoint + ")?" |
---|
| 85 | + @"|" + floatingPoint + ")" |
---|
| 86 | + optionalExponent |
---|
| 87 | }); |
---|
| 88 | } |
---|
| 89 | } |
---|
| 90 | |
---|
| 91 | if (syntaxDefinition.HasAttribute("extends") && mainRuleSetElement != null) { |
---|
| 92 | // convert 'extends="HTML"' to '<Import ruleSet="HTML/" />' in main rule set. |
---|
| 93 | mainRuleSetElement.Elements.Add( |
---|
| 94 | new XshdImport { RuleSetReference = new XshdReference<XshdRuleSet>( |
---|
| 95 | syntaxDefinition.GetAttribute("extends"), string.Empty |
---|
| 96 | ) }); |
---|
| 97 | } |
---|
| 98 | return def; |
---|
| 99 | } |
---|
| 100 | |
---|
| 101 | static XshdColor GetColorFromElement(XmlElement element) |
---|
| 102 | { |
---|
| 103 | if (!element.HasAttribute("bold") && !element.HasAttribute("italic") && !element.HasAttribute("color") && !element.HasAttribute("bgcolor")) |
---|
| 104 | return null; |
---|
| 105 | XshdColor color = new XshdColor(); |
---|
| 106 | if (element.HasAttribute("bold")) |
---|
| 107 | color.FontWeight = XmlConvert.ToBoolean(element.GetAttribute("bold")) ? FontWeights.Bold : FontWeights.Normal; |
---|
| 108 | if (element.HasAttribute("italic")) |
---|
| 109 | color.FontStyle = XmlConvert.ToBoolean(element.GetAttribute("italic")) ? FontStyles.Italic : FontStyles.Normal; |
---|
| 110 | if (element.HasAttribute("color")) |
---|
| 111 | color.Foreground = ParseColor(element.GetAttribute("color")); |
---|
| 112 | if (element.HasAttribute("bgcolor")) |
---|
| 113 | color.Background = ParseColor(element.GetAttribute("bgcolor")); |
---|
| 114 | return color; |
---|
| 115 | } |
---|
| 116 | |
---|
| 117 | static XshdReference<XshdColor> GetColorReference(XmlElement element) |
---|
| 118 | { |
---|
| 119 | XshdColor color = GetColorFromElement(element); |
---|
| 120 | if (color != null) |
---|
| 121 | return new XshdReference<XshdColor>(color); |
---|
| 122 | else |
---|
| 123 | return new XshdReference<XshdColor>(); |
---|
| 124 | } |
---|
| 125 | |
---|
| 126 | static HighlightingBrush ParseColor(string c) |
---|
| 127 | { |
---|
| 128 | if (c.StartsWith("#", StringComparison.Ordinal)) { |
---|
| 129 | int a = 255; |
---|
| 130 | int offset = 0; |
---|
| 131 | if (c.Length > 7) { |
---|
| 132 | offset = 2; |
---|
| 133 | a = Int32.Parse(c.Substring(1,2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); |
---|
| 134 | } |
---|
| 135 | |
---|
| 136 | int r = Int32.Parse(c.Substring(1 + offset,2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); |
---|
| 137 | int g = Int32.Parse(c.Substring(3 + offset,2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); |
---|
| 138 | int b = Int32.Parse(c.Substring(5 + offset,2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); |
---|
| 139 | return new SimpleHighlightingBrush(Color.FromArgb((byte)a, (byte)r, (byte)g, (byte)b)); |
---|
| 140 | } else if (c.StartsWith("SystemColors.", StringComparison.Ordinal)) { |
---|
| 141 | return V2Loader.GetSystemColorBrush(null, c); |
---|
| 142 | } else { |
---|
| 143 | return new SimpleHighlightingBrush((Color)V2Loader.ColorConverter.ConvertFromInvariantString(c)); |
---|
| 144 | } |
---|
| 145 | } |
---|
| 146 | |
---|
| 147 | char ruleSetEscapeCharacter; |
---|
| 148 | |
---|
| 149 | XshdRuleSet ImportRuleSet(XmlElement element) |
---|
| 150 | { |
---|
| 151 | XshdRuleSet ruleSet = new XshdRuleSet(); |
---|
| 152 | ruleSet.Name = element.GetAttributeOrNull("name"); |
---|
| 153 | |
---|
| 154 | if (element.HasAttribute("escapecharacter")) { |
---|
| 155 | ruleSetEscapeCharacter = element.GetAttribute("escapecharacter")[0]; |
---|
| 156 | } else { |
---|
| 157 | ruleSetEscapeCharacter = '\0'; |
---|
| 158 | } |
---|
| 159 | |
---|
| 160 | if (element.HasAttribute("reference")) { |
---|
| 161 | ruleSet.Elements.Add( |
---|
| 162 | new XshdImport { RuleSetReference = new XshdReference<XshdRuleSet>( |
---|
| 163 | element.GetAttribute("reference"), string.Empty |
---|
| 164 | ) }); |
---|
| 165 | } |
---|
| 166 | ruleSet.IgnoreCase = element.GetBoolAttribute("ignorecase"); |
---|
| 167 | |
---|
| 168 | foreach (XmlElement el in element.GetElementsByTagName("KeyWords")) { |
---|
| 169 | XshdKeywords keywords = new XshdKeywords(); |
---|
| 170 | keywords.ColorReference = GetColorReference(el); |
---|
| 171 | // we have to handle old syntax highlighting definitions that contain |
---|
| 172 | // empty keywords or empty keyword groups |
---|
| 173 | foreach (XmlElement node in el.GetElementsByTagName("Key")) { |
---|
| 174 | string word = node.GetAttribute("word"); |
---|
| 175 | if (!string.IsNullOrEmpty(word)) |
---|
| 176 | keywords.Words.Add(word); |
---|
| 177 | } |
---|
| 178 | if (keywords.Words.Count > 0) { |
---|
| 179 | ruleSet.Elements.Add(keywords); |
---|
| 180 | } |
---|
| 181 | } |
---|
| 182 | |
---|
| 183 | foreach (XmlElement el in element.GetElementsByTagName("Span")) { |
---|
| 184 | ruleSet.Elements.Add(ImportSpan(el)); |
---|
| 185 | } |
---|
| 186 | |
---|
| 187 | foreach (XmlElement el in element.GetElementsByTagName("MarkPrevious")) { |
---|
| 188 | ruleSet.Elements.Add(ImportMarkPrevNext(el, false)); |
---|
| 189 | } |
---|
| 190 | foreach (XmlElement el in element.GetElementsByTagName("MarkFollowing")) { |
---|
| 191 | ruleSet.Elements.Add(ImportMarkPrevNext(el, true)); |
---|
| 192 | } |
---|
| 193 | |
---|
| 194 | return ruleSet; |
---|
| 195 | } |
---|
| 196 | |
---|
| 197 | static XshdRule ImportMarkPrevNext(XmlElement el, bool markFollowing) |
---|
| 198 | { |
---|
| 199 | bool markMarker = el.GetBoolAttribute("markmarker") ?? false; |
---|
| 200 | string what = Regex.Escape(el.InnerText); |
---|
| 201 | const string identifier = @"[\d\w_]+"; |
---|
| 202 | const string whitespace = @"\s*"; |
---|
| 203 | |
---|
| 204 | string regex; |
---|
| 205 | if (markFollowing) { |
---|
| 206 | if (markMarker) { |
---|
| 207 | regex = what + whitespace + identifier; |
---|
| 208 | } else { |
---|
| 209 | regex = "(?<=(" + what + whitespace + "))" + identifier; |
---|
| 210 | } |
---|
| 211 | } else { |
---|
| 212 | if (markMarker) { |
---|
| 213 | regex = identifier + whitespace + what; |
---|
| 214 | } else { |
---|
| 215 | regex = identifier + "(?=(" + whitespace + what + "))"; |
---|
| 216 | } |
---|
| 217 | } |
---|
| 218 | return new XshdRule { |
---|
| 219 | ColorReference = GetColorReference(el), |
---|
| 220 | Regex = regex, |
---|
| 221 | RegexType = XshdRegexType.IgnorePatternWhitespace |
---|
| 222 | }; |
---|
| 223 | } |
---|
| 224 | |
---|
| 225 | XshdSpan ImportSpan(XmlElement element) |
---|
| 226 | { |
---|
| 227 | XshdSpan span = new XshdSpan(); |
---|
| 228 | if (element.HasAttribute("rule")) { |
---|
| 229 | span.RuleSetReference = new XshdReference<XshdRuleSet>(null, element.GetAttribute("rule")); |
---|
| 230 | } |
---|
| 231 | char escapeCharacter = ruleSetEscapeCharacter; |
---|
| 232 | if (element.HasAttribute("escapecharacter")) { |
---|
| 233 | escapeCharacter = element.GetAttribute("escapecharacter")[0]; |
---|
| 234 | } |
---|
| 235 | span.Multiline = !(element.GetBoolAttribute("stopateol") ?? false); |
---|
| 236 | |
---|
| 237 | span.SpanColorReference = GetColorReference(element); |
---|
| 238 | |
---|
| 239 | span.BeginRegexType = XshdRegexType.IgnorePatternWhitespace; |
---|
| 240 | span.BeginRegex = ImportRegex(element["Begin"].InnerText, |
---|
| 241 | element["Begin"].GetBoolAttribute("singleword") ?? false, |
---|
| 242 | element["Begin"].GetBoolAttribute("startofline")); |
---|
| 243 | span.BeginColorReference = GetColorReference(element["Begin"]); |
---|
| 244 | |
---|
| 245 | string endElementText = string.Empty; |
---|
| 246 | if (element["End"] != null) { |
---|
| 247 | span.EndRegexType = XshdRegexType.IgnorePatternWhitespace; |
---|
| 248 | endElementText = element["End"].InnerText; |
---|
| 249 | span.EndRegex = ImportRegex(endElementText, |
---|
| 250 | element["End"].GetBoolAttribute("singleword") ?? false, |
---|
| 251 | null); |
---|
| 252 | span.EndColorReference = GetColorReference(element["End"]); |
---|
| 253 | } |
---|
| 254 | |
---|
| 255 | if (escapeCharacter != '\0') { |
---|
| 256 | XshdRuleSet ruleSet = new XshdRuleSet(); |
---|
| 257 | if (endElementText.Length == 1 && endElementText[0] == escapeCharacter) { |
---|
| 258 | // ""-style escape |
---|
| 259 | ruleSet.Elements.Add(new XshdSpan { |
---|
| 260 | BeginRegex = Regex.Escape(endElementText + endElementText), |
---|
| 261 | EndRegex = "" |
---|
| 262 | }); |
---|
| 263 | } else { |
---|
| 264 | // \"-style escape |
---|
| 265 | ruleSet.Elements.Add(new XshdSpan { |
---|
| 266 | BeginRegex = Regex.Escape(escapeCharacter.ToString()), |
---|
| 267 | EndRegex = "." |
---|
| 268 | }); |
---|
| 269 | } |
---|
| 270 | if (span.RuleSetReference.ReferencedElement != null) { |
---|
| 271 | ruleSet.Elements.Add(new XshdImport { RuleSetReference = span.RuleSetReference }); |
---|
| 272 | } |
---|
| 273 | span.RuleSetReference = new XshdReference<XshdRuleSet>(ruleSet); |
---|
| 274 | } |
---|
| 275 | return span; |
---|
| 276 | } |
---|
| 277 | |
---|
| 278 | static string ImportRegex(string expr, bool singleWord, bool? startOfLine) |
---|
| 279 | { |
---|
| 280 | StringBuilder b = new StringBuilder(); |
---|
| 281 | if (startOfLine != null) { |
---|
| 282 | if (startOfLine.Value) { |
---|
| 283 | b.Append(@"(?<=(^\s*))"); |
---|
| 284 | } else { |
---|
| 285 | b.Append(@"(?<!(^\s*))"); |
---|
| 286 | } |
---|
| 287 | } else { |
---|
| 288 | if (singleWord) |
---|
| 289 | b.Append(@"\b"); |
---|
| 290 | } |
---|
| 291 | for (int i = 0; i < expr.Length; i++) { |
---|
| 292 | char c = expr[i]; |
---|
| 293 | if (c == '@') { |
---|
| 294 | ++i; |
---|
| 295 | if (i == expr.Length) |
---|
| 296 | throw new HighlightingDefinitionInvalidException("Unexpected end of @ sequence, use @@ to look for a single @."); |
---|
| 297 | switch (expr[i]) { |
---|
| 298 | case 'C': // match whitespace or punctuation |
---|
| 299 | b.Append(@"[^\w\d_]"); |
---|
| 300 | break; |
---|
| 301 | case '!': // negative lookahead |
---|
| 302 | { |
---|
| 303 | StringBuilder whatmatch = new StringBuilder(); |
---|
| 304 | ++i; |
---|
| 305 | while (i < expr.Length && expr[i] != '@') { |
---|
| 306 | whatmatch.Append(expr[i++]); |
---|
| 307 | } |
---|
| 308 | b.Append("(?!("); |
---|
| 309 | b.Append(Regex.Escape(whatmatch.ToString())); |
---|
| 310 | b.Append("))"); |
---|
| 311 | } |
---|
| 312 | break; |
---|
| 313 | case '-': // negative lookbehind |
---|
| 314 | { |
---|
| 315 | StringBuilder whatmatch = new StringBuilder(); |
---|
| 316 | ++i; |
---|
| 317 | while (i < expr.Length && expr[i] != '@') { |
---|
| 318 | whatmatch.Append(expr[i++]); |
---|
| 319 | } |
---|
| 320 | b.Append("(?<!("); |
---|
| 321 | b.Append(Regex.Escape(whatmatch.ToString())); |
---|
| 322 | b.Append("))"); |
---|
| 323 | } |
---|
| 324 | break; |
---|
| 325 | case '@': |
---|
| 326 | b.Append("@"); |
---|
| 327 | break; |
---|
| 328 | default: |
---|
| 329 | throw new HighlightingDefinitionInvalidException("Unknown character in @ sequence."); |
---|
| 330 | } |
---|
| 331 | } else { |
---|
| 332 | b.Append(Regex.Escape(c.ToString())); |
---|
| 333 | } |
---|
| 334 | } |
---|
| 335 | if (singleWord) |
---|
| 336 | b.Append(@"\b"); |
---|
| 337 | return b.ToString(); |
---|
| 338 | } |
---|
| 339 | } |
---|
| 340 | } |
---|