您的位置:首页 > 其它

.Net插件框架的实现及分析(三)

2011-09-29 10:51 561 查看
.Net插件框架的实现及分析导航

.Net插件框架的实现及分析(一)
.Net插件框架的实现及分析(二)


.Net插件框架的实现及分析(三)

话接上回(.Net插件框架的实现及分析(二)),这次我想讨论下的是如何使用之前建立的框架来创建一个插件。现在我们主要以格式化插件为例,因此准备创建一个代码高亮的插件,在发表文章时,可以插入相关的代码语法高亮功能,以下实现的插件修改自Screwturn Wiki's 的 SyntaxHighlight 插件,所在一些不太重要的代码中的英文注释我就不一一翻译了,只为说明如何配置此框架使用。

此代码高亮插件使用的也是SyntaxHighlight JS版的插件,在此就不再多作介绍了,相必大家都应该知道。OK,接下来就直接说代码了:

因为一个插件最终需要生成一个独立的DLL文件,因此我们需要先建立一个新的插件项目,就名为:CoderBlog.Plugin.SyntaxHighlighter

此语法高亮插件包含了2个类,一个专门处理程序语言的,此为辅助类:

Languages.cs文件

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

/// 此插件修改自原 Screwturn Wiki's 的 SyntaxHighlight 插件,在此只作演示代码用,原插件地址

/// http://greenicicleblog.com/ScrewTurnSyntaxHighlighter
/// 此类只是本插件的一个辅助类,以下英文注释我就不翻译了,都不难的,呵。

namespace CoderBlog.Plugin.SyntaxHighlighter

{

public class Languages

{

private Dictionary<string, string> m_Brushes = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

/// <summary>

/// Constructor. When a Languages class is created,

/// a list of predefined languages ("brushes") is created.

/// </summary>

public Languages()

{

InitializeBrushes();

}

/// <summary>

/// Returns if a programming language is supported by the formatter.

/// If there is a brush for it, a language is supported.

/// </summary>

/// <param name="language">The programming language name to test.</param>

/// <returns><c>true</c> if the provided language is supported,

/// otherwise, <c>false.</c></returns>

public bool IsSupported(string language)

{

if (string.IsNullOrEmpty(language))

{

throw new ArgumentNullException("language");

}

string key = language.Trim();

return m_Brushes.ContainsKey(key);

}

/// <summary>

/// Returns the brush CSS file for a programming language.

/// </summary>

/// <param name="language">The programming language name.</param>

/// <returns>The name of a brush CSS file, or <c>null</c>

/// if the language is not supported.</returns>

public string GetStylesheetFile(string language)

{

if (string.IsNullOrEmpty(language))

{

throw new ArgumentNullException("language");

}

string stylesheetFile;

string key = language.Trim();

m_Brushes.TryGetValue(key, out stylesheetFile);

return stylesheetFile;

}

/// <summary>

/// Adds a user-defined language brush to the set of supported languages.

/// </summary>

/// <param name="language">The name of the language, as given as the first word in a code block</param>

/// <param name="stylesheetFile">Name of the additional language ("brush") javascript file

/// within the syntax hightlighter directory.</param>

public void AddLanguage(string language, string stylesheetFile)

{

if (string.IsNullOrEmpty(language))

{

throw new ArgumentNullException("language");

}

if (string.IsNullOrEmpty(stylesheetFile))

{

throw new ArgumentNullException("stylesheetFile");

}

m_Brushes[language.ToLowerInvariant()] = stylesheetFile;

}

/// <summary>

/// Initializes the list of supported language names and associates them

/// with a brush style sheet.

/// </summary>

private void InitializeBrushes()

{

m_Brushes.Add("as3", "shBrushAS3.js");

m_Brushes.Add("actionscript3", "shBrushAS3.js");

m_Brushes.Add("bash", "shBrushBash.js");

m_Brushes.Add("shell", "shBrushBash.js");

m_Brushes.Add("cf", "shBrushColdFusion.js");

m_Brushes.Add("coldfusion", "shBrushColdFusion.js");

m_Brushes.Add("c-sharp", "shBrushCSharp.js");

m_Brushes.Add("csharp", "shBrushCSharp.js");

m_Brushes.Add("cpp", "shBrushCpp.js");

m_Brushes.Add("c", "shBrushCpp.js");

m_Brushes.Add("css", "shBrushCss.js");

m_Brushes.Add("delphi", "shBrushDelphi.js");

m_Brushes.Add("pas", "shBrushDelphi.js");

m_Brushes.Add("pascal", "shBrushDelphi.js");

m_Brushes.Add("diff", "shBrushDiff.js");

m_Brushes.Add("patch", "shBrushDiff.js");

m_Brushes.Add("erl", "shBrushErlang.js");

m_Brushes.Add("erlang", "shBrushErlang.js");

m_Brushes.Add("groovy", "shBrushGroovy.js");

m_Brushes.Add("js", "shBrushJScript.js");

m_Brushes.Add("jscript", "shBrushJScript.js");

m_Brushes.Add("javascript", "shBrushJScript.js");

m_Brushes.Add("java", "shBrushJava.js");

m_Brushes.Add("jfx", "shBrushJavaFX.js");

m_Brushes.Add("javafx", "shBrushJavaFX.js");

m_Brushes.Add("pl", "shBrushPerl.js");

m_Brushes.Add("perl", "shBrushPerl.js");

m_Brushes.Add("php", "shBrushPhp.js");

m_Brushes.Add("plain", "shBrushPlain.js");

m_Brushes.Add("text", "shBrushPlain.js");

m_Brushes.Add("ps", "shBrushPowerShell.js");

m_Brushes.Add("powershell", "shBrushPowerShell.js");

m_Brushes.Add("py", "shBrushPython.js");

m_Brushes.Add("python", "shBrushPython.js");

m_Brushes.Add("rails", "shBrushRuby.js");

m_Brushes.Add("ror", "shBrushRuby.js");

m_Brushes.Add("ruby", "shBrushRuby.js");

m_Brushes.Add("scala", "shBrushScala.js");

m_Brushes.Add("sql", "shBrushSql.js");

m_Brushes.Add("vb", "shBrushVb.js");

m_Brushes.Add("vbnet", "shBrushVb.js");

m_Brushes.Add("xml", "shBrushXml.js");

m_Brushes.Add("xhtml", "shBrushXml.js");

m_Brushes.Add("html", "shBrushXml.js");

m_Brushes.Add("xslt", "shBrushXml.js");

m_Brushes.Add("xaml", "shBrushXml.js");

}

}

}

接下来就要实现具体的插件类了,此类必须继承自我们之前创建的格式化接口 IFormatterProvider,由于已有了 IHost 主程序的接口,所以插件项目只需引用一个 CoderBlog.PluginFramework 项目即可了,不需直接与主程序接触:

SyntaxHighlighter.cs 文件

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Text.RegularExpressions;

using CoderBlog.PluginFramework;

/// 此插件修改自原 Screwturn Wiki's 的 SyntaxHighlight 插件,在此只作演示代码用,原插件地址

/// http://greenicicleblog.com/ScrewTurnSyntaxHighlighter
namespace CoderBlog.Plugin.SyntaxHighlighter

{

/// <summary>

/// 添加语法高亮效果

/// </summary>

/// <remarks>

/// <para>

/// 支持定义默认语言

/// </para>

/// <example>

/// 以下为使用示例:

/// 使用 @@ Languages 开始,并以 @@ 为结束

/// <code>

/// @@ csharp

/// public const string WebSiteURL="http://www.CoderBlog.in";

/// @@

/// </code>

/// </example>

/// </remarks>

public class SyntaxHighlighter : IFormatterProvider

{

IHost host;

/// <summary>

/// 设置插件信息

/// </summary>

private static readonly PluginInfo info = new PluginInfo("SyntaxHighlighter Plugin", "Winson", "1.0.0.0", "http://www.CoderBlog.in", "http://www.CoderBlog.in/download/Plugins/SyntaxHighlighter.txt");

private string m_ConfigurationString;

private string m_ClientScriptBaseUrl;

// 准备一个程序语言代码列表变量

IList<string> foundLanguages = new List<string>();

/// <summary>

/// 客户端代码的 URL

/// JavaScript and CSS files.

/// </summary>

protected internal string ClientScriptBaseUrl

{

get

{

string baseUrl = m_ClientScriptBaseUrl ?? DefaultClientScriptBaseUrl;

if (!baseUrl.EndsWith("/"))

{

baseUrl = baseUrl + "/";

}

return baseUrl;

}

set

{

m_ClientScriptBaseUrl = value;

}

}

/// <summary>

/// 设置获取默认语言

/// </summary>

protected internal string DefaultLanguage

{

get;

set;

}

/// <summary>

/// 语言的主题

/// </summary>

protected internal string Theme

{

get;

set;

}

/// <summary>

/// 创建新的语法高亮实例

/// </summary>

public SyntaxHighlighter()

{

// 设置默认值

DefaultLanguage = "text";

Theme = "Default";

Languages = new Languages();

}

/// <summary>

/// 解析语言名称 (如 "csharp" 或 "html") 到对应的 javascript 文件

/// </summary>

protected internal Languages Languages

{

get;

private set;

}

/// <summary>

/// 默认的客户端脚本路径

/// </summary>

public const string DefaultClientScriptBaseUrl = "~/Plugins/SyntaxHighlighter/";

/// <summary>

/// 语法高亮的起始标签

/// </summary>

protected internal const string CodeBlockTag = "<pre>";

/// <summary>

/// 配置文件的文本

/// </summary>

protected internal string ConfigurationString

{

get

{

return m_ConfigurationString;

}

set

{

m_ConfigurationString = value;

//此处可通过主程序读取配置文件信息,在此就不作详细代码了,请自行实现,

//其实只是读取文本文件的配置,当然你也可以用XML,以下代码就先注释掉了

//Dictionary<string, string> config = host.ReadProviderConfiguration(m_ConfigurationString);

//ClientScriptBaseUrl = config["scripturl"];

//Theme = config["theme"];

//DefaultLanguage = config["defaultlang"];

//string customlang = config["customlang"];

////添加自定义语言

//foreach (string option in customlang.Split('|'))

//{

// string[] parts = option.Split(':');

// if (parts.Length == 2)

// {

// string key = parts[0].Trim();

// string val = parts[1].Trim();

// if (!string.IsNullOrEmpty(key))

// {

// Languages.AddLanguage(key, val);

// }

// }

//}

}

}

#region 扩展方法,以下注释我也不一一说明啦,英文都很简单,大家自己看看吧

/// <summary>

/// Appends the reference to a CSS style sheet to the formatted text.

/// </summary>

/// <param name="textBuilder"><see cref="StringBuilder"/> that creates the formatted text.</param>

/// <param name="styleSheetName">File name of the style sheet.</param>

protected internal virtual void AppendStyleSheet(StringBuilder textBuilder, string styleSheetName)

{

textBuilder.Append("<link href='");

textBuilder.Append(ClientScriptBaseUrl);

textBuilder.Append("styles/");

textBuilder.Append(styleSheetName);

textBuilder.Append("' rel='stylesheet' type='text/css'/>\n");

}

/// <summary>

/// Appends the reference to a client-side script to the formatted text.

/// </summary>

/// <param name="textBuilder"><see cref="StringBuilder"/> that creates the formatted text.</param>

/// <param name="scriptName">File name of the script.</param>

protected internal virtual void AppendClientScript(StringBuilder textBuilder, string scriptName)

{

textBuilder.Append("<script src='");

textBuilder.Append(ClientScriptBaseUrl);

textBuilder.Append("scripts/");

textBuilder.Append(scriptName);

textBuilder.Append("' type='text/javascript'></script>\n");

}

/// <summary>

/// Appends the reference to a programming language specific

/// script ("brush") to the formatted text

/// </summary>

/// <param name="textBuilder"><see cref="StringBuilder"/> that creates the formatted text.</param>

/// <param name="language">The language.</param>

protected internal virtual void AppendBrushScript(StringBuilder textBuilder, string language)

{

string scriptFile = Languages.GetStylesheetFile(language);

AppendClientScript(textBuilder, scriptFile);

}

/// <summary>

/// Appends the reference to the theme-specific style sheet.

/// </summary>

/// <param name="textBuilder"><see cref="StringBuilder"/> that creates the formatted text.</param>

protected internal virtual void AppendThemeStylesheet(StringBuilder textBuilder)

{

string stylesheet = string.Format("shTheme{0}.css", Theme);

AppendStyleSheet(textBuilder, stylesheet);

}

#endregion

#region IFormatterProvider Members

/// <summary>

/// Called by the postbar engine in order to format text.

/// </summary>

/// <param name="raw">The unformatted text.</param>

/// <param name="context">Contextual information for the transformation -

/// like the user name, HTTP context, or purpose of the formatter run.</param>

/// <returns>Formatted text.</returns>

/// <remarks>

/// <para>

/// This formatter detects blocks of source code. The first word within a source code

/// block is considered a hint on in which language the block is - if this first word

/// is one of the supported languages.

/// </para>

/// <para>

/// Postbar formats code blocks with a "pre" tag - this is done in phase 1 of the transformation;

/// this is why we run in phase 2. THe Syntax Highlighter scripts also use the "pre" tag,

/// augmented with a CSS class that defines the language ("brush", as it cals it).

/// Now all we need to do is:<br/>

/// * Look for "pre" tags

/// * Get the first word behind it.

/// * If this first word is a suported language, use it; otherwise ignore and use the

/// default language.

/// * Add the CSS class for the brush.

/// to the "pre" tag.

/// * Da capo al fine.

/// * Inject links to the Syntax Highlighter CSS and script files. Each language

/// has its own CSS file; in order to make things more efficient we only add files

/// that are actually needed because the language was found in a script block.

/// </para>

/// </remarks>

public virtual string Format(string raw, ContextInfo context)

{

// Only format the post content

if (context.FormatContext.CompareTo(FormattingContext.PostContent) != 0)

return raw;

// Buckets for the remainders of unformatted text, and already formatted text.

string sourceText = raw;

StringBuilder targetText = new StringBuilder();

// Find any part of the unformatted text that is enclosed in in a "pre" tags.

// ScrewTurn formats code blocks into preformatted HTML tags.

string openingTag = @"<pre>";

string closingTag = @"</pre>";

Regex regex = new Regex(openingTag + ".+?" + closingTag, RegexOptions.IgnoreCase | RegexOptions.Singleline);

Match match = regex.Match(sourceText);

while (match.Success)

{

// Push the text before the found code block into the target text

// without alteration

if (match.Index > 0)

{

targetText.Append(sourceText.Substring(0, match.Index));

}

// Remove the part before the found code block, and the code block, from the remaining

// source text

sourceText = sourceText.Substring(match.Index + match.Length);

// Get the content of the found code block

string content = match.Value;

// The RegEx match still contains the opening and closing tags. Remove them so we get only the

// text within the tag.

int openingTagLen = openingTag.Length;

int closingTagLen = closingTag.Length;

int contentLen = content.Length - closingTagLen - openingTagLen;

content = content.Substring(openingTagLen, contentLen);

// Get the first word of the code block. If it matched one of the highlighter

// languages, use it as a hint on how to format the code block and remove it from the document.

var wordSeparators = new char[] { ' ', '\n', '\r' };

string firstWord = content

.Split(wordSeparators, StringSplitOptions.RemoveEmptyEntries)

.FirstOrDefault();

// If a first word could be extracted (the block can as well be empty...),

// and the language is supported, then...

string language;

if (!string.IsNullOrEmpty(firstWord) && Languages.IsSupported(firstWord))

{

// ... set the language for this block...

language = firstWord;

// ... and remove the first word from the code block content.

int firstWordIndex = content.IndexOf(firstWord);

content = content.Substring(firstWordIndex + firstWord.Length);

}

else

{

// If no langauge could be found, use the default language.

language = DefaultLanguage;

}

// Track the languages found on the page so we can include the correct set of script files.

if (!foundLanguages.Contains(language))

{

foundLanguages.Add(language);

}

// Add an opening "pre" tag with a language ("brush") definition...

targetText.AppendFormat(

"<pre class='brush: {0}'>\n",

language.ToLowerInvariant());

// ... the content...

targetText.Append(content);

// ... and a closing tag.

targetText.Append("</pre>");

// Get the next code block.

match = regex.Match(sourceText);

}

// Append rest of source text to target.

targetText.Append(sourceText);

// Return the formatted text.

return targetText.ToString();

}

/// <summary>

/// Format the post title .

/// the title is not modified by this plugin.

/// </summary>

/// <param name="title">The post title.</param>

/// <param name="context">The context information.</param>

/// <returns>The original title.</returns>

public string FormatPostTitle(string title, ContextInfo context)

{

return title;

}

/// <summary>

/// Format the page head, if any.

/// </summary>

/// <param name="head">The head content</param>

/// <param name="context">The context information.</param>

/// <returns>The formatted head</returns>

public string FormatPageHead(string head, ContextInfo context)

{

return head;

}

/// <summary>

/// Format the page foot, if any.

/// Just return the js script registration in the head

/// </summary>

/// <param name="foot">The foot content</param>

/// <param name="context">The context information.</param>

/// <returns>The original foot</returns>

public string FormatPageFoot(string foot, ContextInfo context)

{

StringBuilder targetText = new StringBuilder();

targetText.AppendLine("\n<!-- START GreenIcicle code syntax highlighter, modified by Winson for PostBar -->\n");

AppendStyleSheet(targetText, "shCore.css");

AppendThemeStylesheet(targetText);

AppendClientScript(targetText, "shCore.js");

foreach (var language in foundLanguages)

{

AppendBrushScript(targetText, language);

}

// Add script that hooks up the Flash-based clipboard helper, and the activate the

// syntax highlighter.

targetText.Append("<script language='javascript'>\nSyntaxHighlighter.config.clipboardSwf = '");

targetText.Append(ClientScriptBaseUrl);

targetText.Append("scripts/clipboard.swf'\nSyntaxHighlighter.all();\n</script>");

targetText.AppendLine("\n<!-- END GreenIcicle code syntax highlighter, modified by Winson for PostBar -->\n");

foot += targetText.ToString();

return foot;

}

/// <summary>

/// Initializes the plugin. This is the very first method called on the

/// class.

/// </summary>

/// <param name="host">Provides access to the wiki's API</param>

/// <param name="config">Configuration string for the plugin.</param>

public void Init(IHost host, string config)

{

this.host = host;

ConfigurationString = config;

}

/// <summary>

/// Shuts the plugin down. Very last method called on the clas.

/// </summary>

public void Shutdown()

{

// Nothing to do to shut the formatter down

}

/// <summary>

/// Plugin information

/// </summary>

public virtual PluginInfo Information

{

get

{

return info;

}

}

/// <summary>

/// HTML text displayed as help for configuring the plugin

/// </summary>

public virtual string ConfigHelpHtml

{

get

{

return @"

<div>

<b>选项:</b><br/>

<ul>

<li><b>ScriptUrl</b> 加载JS和CSS文件的路径,可以是网络地址。</li>

<li><b>Theme</b> 语法高亮插件所使用的主题,不设置将使用默认主题。</li>

<li><b>DefaultLang</b> 默认语言,即当不设置高亮语言时,所有默认输出的语言类型,如不设置此项,默认语言为 text</li>

<li><b>CustomLang</b> 添加自定义语言,指定其对应的CSS文件</li>

</ul>

<b>例子:</b><br/>

加载JS和CSS文件的路径为本地路径,使用 Django 主题,默认语言为 C#,添加自定义语言为 MyNewLang1 和 MyNewLang2 并指定其JS语法文件为 MyNewlang1.js 和 MyNewlang2.js,两者间使用分号:隔开,如要添加多个自定义语言,需使用|以分隔开, 配置如下:<br>

ScriptUrl=~/Plugins/SyntaxHighlighter/;<br>

Theme=Django;<br>

DefaultLang=csharp;<br>

CustomLang=MyNewLang1:MyNewlang1.js|MyNewLang2:MyNewlang2.js;

</div>

";

}

}

/// <summary>

/// 插件加载的优先级

/// </summary>

public int ExecutionPriority

{

get

{

return 20;

}

}

#endregion

}

}

OK,现在插件已做好了,为了测试插件是否成功,我们还需要创建一个测试项目,项目名为:PluginTest,然后添加以下单元测试类:

PluginsTester.cs 文件,填写以下测试函数:

[TestMethod]

public void SyntaxHighlighter_TestWithoutCore()

{

IFormatterProvider syntax = new SyntaxHighlighter();

string str = "<pre>Code</pre>";

string cshart = "<pre>Csharp Code</pre>";

string foot = "copyright";

ContextInfo info = new ContextInfo(FormattingContext.PageFooter, HttpContext.Current, "Winson");

foot = syntax.FormatPageFoot(foot, info);

info = new ContextInfo(FormattingContext.PostContent, HttpContext.Current, "Winson");

str = syntax.Format(str, info);

info = new ContextInfo(FormattingContext.PostContent, HttpContext.Current, "Winson");

cshart = syntax.Format(cshart, info);

TestContext.WriteLine(str + cshart + foot);

StringAssert.Contains(str, "<pre class='brush: text'>");

}

呵,本例经测试正常通过啦,最后提供全套源码给大家下载吧,感兴趣的朋友可慢慢研究下


下载Demo代码:CoderBlog.PluginFramework.rar
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: