Click here to Skip to main content
15,896,912 members
Articles / Desktop Programming / WPF

Using AvalonEdit (WPF Text Editor)

Rate me:
Please Sign up or sign in to vote.
4.97/5 (271 votes)
1 Apr 2013LGPL313 min read 1.9M   72.3K   535  
AvalonEdit is an extensible Open-Source text editor with support for syntax highlighting and folding.
// 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.Collections.Generic;
using System.Diagnostics;
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 2.0.
	/// Version 2.0 files are recognized by the namespace.
	/// </summary>
	static class V2Loader
	{
		public const string Namespace = "http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008";
		
		static XmlSchemaSet schemaSet;
		
		static XmlSchemaSet SchemaSet {
			get {
				if (schemaSet == null) {
					schemaSet = HighlightingLoader.LoadSchemaSet(new XmlTextReader(
						Resources.OpenStream("ModeV2.xsd")));
				}
				return schemaSet;
			}
		}
		
		public static XshdSyntaxDefinition LoadDefinition(XmlReader reader, bool skipValidation)
		{
			reader = HighlightingLoader.GetValidatingReader(reader, true, skipValidation ? null : SchemaSet);
			reader.Read();
			return ParseDefinition(reader);
		}
		
		static XshdSyntaxDefinition ParseDefinition(XmlReader reader)
		{
			Debug.Assert(reader.LocalName == "SyntaxDefinition");
			XshdSyntaxDefinition def = new XshdSyntaxDefinition();
			def.Name = reader.GetAttribute("name");
			string extensions = reader.GetAttribute("extensions");
			if (extensions != null)
				def.Extensions.AddRange(extensions.Split(';'));
			ParseElements(def.Elements, reader);
			Debug.Assert(reader.NodeType == XmlNodeType.EndElement);
			Debug.Assert(reader.LocalName == "SyntaxDefinition");
			return def;
		}
		
		static void ParseElements(ICollection<XshdElement> c, XmlReader reader)
		{
			if (reader.IsEmptyElement)
				return;
			while (reader.Read() && reader.NodeType != XmlNodeType.EndElement) {
				Debug.Assert(reader.NodeType == XmlNodeType.Element);
				if (reader.NamespaceURI != Namespace) {
					if (!reader.IsEmptyElement)
						reader.Skip();
					continue;
				}
				switch (reader.Name) {
					case "RuleSet":
						c.Add(ParseRuleSet(reader));
						break;
					case "Property":
						c.Add(ParseProperty(reader));
						break;
					case "Color":
						c.Add(ParseNamedColor(reader));
						break;
					case "Keywords":
						c.Add(ParseKeywords(reader));
						break;
					case "Span":
						c.Add(ParseSpan(reader));
						break;
					case "Import":
						c.Add(ParseImport(reader));
						break;
					case "Rule":
						c.Add(ParseRule(reader));
						break;
					default:
						throw new NotSupportedException("Unknown element " + reader.Name);
				}
			}
		}
		
		static XshdElement ParseProperty(XmlReader reader)
		{
			XshdProperty property = new XshdProperty();
			SetPosition(property, reader);
			property.Name = reader.GetAttribute("name");
			property.Value = reader.GetAttribute("value");
			return property;
		}
		
		static XshdRuleSet ParseRuleSet(XmlReader reader)
		{
			XshdRuleSet ruleSet = new XshdRuleSet();
			SetPosition(ruleSet, reader);
			ruleSet.Name = reader.GetAttribute("name");
			ruleSet.IgnoreCase = reader.GetBoolAttribute("ignoreCase");
			
			CheckElementName(reader, ruleSet.Name);
			ParseElements(ruleSet.Elements, reader);
			return ruleSet;
		}
		
		static XshdRule ParseRule(XmlReader reader)
		{
			XshdRule rule = new XshdRule();
			SetPosition(rule, reader);
			rule.ColorReference = ParseColorReference(reader);
			if (!reader.IsEmptyElement) {
				reader.Read();
				if (reader.NodeType == XmlNodeType.Text) {
					rule.Regex = reader.ReadContentAsString();
					rule.RegexType = XshdRegexType.IgnorePatternWhitespace;
				}
			}
			return rule;
		}
		
		static XshdKeywords ParseKeywords(XmlReader reader)
		{
			XshdKeywords keywords = new XshdKeywords();
			SetPosition(keywords, reader);
			keywords.ColorReference = ParseColorReference(reader);
			reader.Read();
			while (reader.NodeType != XmlNodeType.EndElement) {
				Debug.Assert(reader.NodeType == XmlNodeType.Element);
				keywords.Words.Add(reader.ReadElementString());
			}
			return keywords;
		}
		
		static XshdImport ParseImport(XmlReader reader)
		{
			XshdImport import = new XshdImport();
			SetPosition(import, reader);
			import.RuleSetReference = ParseRuleSetReference(reader);
			if (!reader.IsEmptyElement)
				reader.Skip();
			return import;
		}
		
		static XshdSpan ParseSpan(XmlReader reader)
		{
			XshdSpan span = new XshdSpan();
			SetPosition(span, reader);
			span.BeginRegex = reader.GetAttribute("begin");
			span.EndRegex = reader.GetAttribute("end");
			span.Multiline = reader.GetBoolAttribute("multiline") ?? false;
			span.SpanColorReference = ParseColorReference(reader);
			span.RuleSetReference = ParseRuleSetReference(reader);
			if (!reader.IsEmptyElement) {
				reader.Read();
				while (reader.NodeType != XmlNodeType.EndElement) {
					Debug.Assert(reader.NodeType == XmlNodeType.Element);
					switch (reader.Name) {
						case "Begin":
							if (span.BeginRegex != null)
								throw Error(reader, "Duplicate Begin regex");
							span.BeginColorReference = ParseColorReference(reader);
							span.BeginRegex = reader.ReadElementString();
							span.BeginRegexType = XshdRegexType.IgnorePatternWhitespace;
							break;
						case "End":
							if (span.EndRegex != null)
								throw Error(reader, "Duplicate End regex");
							span.EndColorReference = ParseColorReference(reader);
							span.EndRegex = reader.ReadElementString();
							span.EndRegexType = XshdRegexType.IgnorePatternWhitespace;
							break;
						case "RuleSet":
							if (span.RuleSetReference.ReferencedElement != null)
								throw Error(reader, "Cannot specify both inline RuleSet and RuleSet reference");
							span.RuleSetReference = new XshdReference<XshdRuleSet>(ParseRuleSet(reader));
							reader.Read();
							break;
						default:
							throw new NotSupportedException("Unknown element " + reader.Name);
					}
				}
			}
			return span;
		}
		
		static Exception Error(XmlReader reader, string message)
		{
			return Error(reader as IXmlLineInfo, message);
		}
		
		static Exception Error(IXmlLineInfo lineInfo, string message)
		{
			if (lineInfo != null)
				return new HighlightingDefinitionInvalidException(HighlightingLoader.FormatExceptionMessage(message, lineInfo.LineNumber, lineInfo.LinePosition));
			else
				return new HighlightingDefinitionInvalidException(message);
		}
		
		/// <summary>
		/// Sets the element's position to the XmlReader's position.
		/// </summary>
		static void SetPosition(XshdElement element, XmlReader reader)
		{
			IXmlLineInfo lineInfo = reader as IXmlLineInfo;
			if (lineInfo != null) {
				element.LineNumber = lineInfo.LineNumber;
				element.ColumnNumber = lineInfo.LinePosition;
			}
		}
		
		static XshdReference<XshdRuleSet> ParseRuleSetReference(XmlReader reader)
		{
			string ruleSet = reader.GetAttribute("ruleSet");
			if (ruleSet != null) {
				// '/' is valid in highlighting definition names, so we need the last occurence
				int pos = ruleSet.LastIndexOf('/');
				if (pos >= 0) {
					return new XshdReference<XshdRuleSet>(ruleSet.Substring(0, pos), ruleSet.Substring(pos + 1));
				} else {
					return new XshdReference<XshdRuleSet>(null, ruleSet);
				}
			} else {
				return new XshdReference<XshdRuleSet>();
			}
		}
		
		static void CheckElementName(XmlReader reader, string name)
		{
			if (name != null) {
				if (name.Length == 0)
					throw Error(reader, "The empty string is not a valid name.");
				if (name.IndexOf('/') >= 0)
					throw Error(reader, "Element names must not contain a slash.");
			}
		}
		
		#region ParseColor
		static XshdColor ParseNamedColor(XmlReader reader)
		{
			XshdColor color = ParseColorAttributes(reader);
			// check removed: invisible named colors may be useful now that apps can read highlighting data
			//if (color.Foreground == null && color.FontWeight == null && color.FontStyle == null)
			//	throw Error(reader, "A named color must have at least one element.");
			color.Name = reader.GetAttribute("name");
			CheckElementName(reader, color.Name);
			color.ExampleText = reader.GetAttribute("exampleText");
			return color;
		}
		
		static XshdReference<XshdColor> ParseColorReference(XmlReader reader)
		{
			string color = reader.GetAttribute("color");
			if (color != null) {
				int pos = color.LastIndexOf('/');
				if (pos >= 0) {
					return new XshdReference<XshdColor>(color.Substring(0, pos), color.Substring(pos + 1));
				} else {
					return new XshdReference<XshdColor>(null, color);
				}
			} else {
				return new XshdReference<XshdColor>(ParseColorAttributes(reader));
			}
		}
		
		static XshdColor ParseColorAttributes(XmlReader reader)
		{
			XshdColor color = new XshdColor();
			SetPosition(color, reader);
			IXmlLineInfo position = reader as IXmlLineInfo;
			color.Foreground = ParseColor(position, reader.GetAttribute("foreground"));
			color.Background = ParseColor(position, reader.GetAttribute("background"));
			color.FontWeight = ParseFontWeight(reader.GetAttribute("fontWeight"));
			color.FontStyle = ParseFontStyle(reader.GetAttribute("fontStyle"));
			return color;
		}
		
		internal readonly static ColorConverter ColorConverter = new ColorConverter();
		internal readonly static FontWeightConverter FontWeightConverter = new FontWeightConverter();
		internal readonly static FontStyleConverter FontStyleConverter = new FontStyleConverter();
		
		static HighlightingBrush ParseColor(IXmlLineInfo lineInfo, string color)
		{
			if (string.IsNullOrEmpty(color))
				return null;
			if (color.StartsWith("SystemColors.", StringComparison.Ordinal))
				return GetSystemColorBrush(lineInfo, color);
			else
				return FixedColorHighlightingBrush((Color?)ColorConverter.ConvertFromInvariantString(color));
		}
		
		internal static SystemColorHighlightingBrush GetSystemColorBrush(IXmlLineInfo lineInfo, string name)
		{
			Debug.Assert(name.StartsWith("SystemColors.", StringComparison.Ordinal));
			string shortName = name.Substring(13);
			var property = typeof(SystemColors).GetProperty(shortName + "Brush");
			if (property == null)
				throw Error(lineInfo, "Cannot find '" + name + "'.");
			return new SystemColorHighlightingBrush(property);
		}
		
		static HighlightingBrush FixedColorHighlightingBrush(Color? color)
		{
			if (color == null)
				return null;
			return new SimpleHighlightingBrush(color.Value);
		}
		
		static FontWeight? ParseFontWeight(string fontWeight)
		{
			if (string.IsNullOrEmpty(fontWeight))
				return null;
			return (FontWeight?)FontWeightConverter.ConvertFromInvariantString(fontWeight);
		}
		
		static FontStyle? ParseFontStyle(string fontStyle)
		{
			if (string.IsNullOrEmpty(fontStyle))
				return null;
			return (FontStyle?)FontStyleConverter.ConvertFromInvariantString(fontStyle);
		}
		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Germany Germany
I am the lead developer on the SharpDevelop open source project.

Comments and Discussions