// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
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
{
///
/// Loads .xshd files, version 2.0.
/// Version 2.0 files are recognized by the namespace.
///
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 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(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);
}
///
/// Sets the element's position to the XmlReader's position.
///
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 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(ruleSet.Substring(0, pos), ruleSet.Substring(pos + 1));
} else {
return new XshdReference(null, ruleSet);
}
} else {
return new XshdReference();
}
}
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 ParseColorReference(XmlReader reader)
{
string color = reader.GetAttribute("color");
if (color != null) {
int pos = color.LastIndexOf('/');
if (pos >= 0) {
return new XshdReference(color.Substring(0, pos), color.Substring(pos + 1));
} else {
return new XshdReference(null, color);
}
} else {
return new XshdReference(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
}
}