.Net插件框架的实现及分析(三)
2011-09-29 10:51 w i n s o n 阅读(2478) 评论(2) 编辑 收藏 举报
话接上回(.Net插件框架的实现及分析(二)),这次我想讨论下的是如何使用之前建立的框架来创建一个插件。现在我们主要以格式化插件为例,因此准备创建一个代码高亮的插件,在发表文章时,可以插入相关的代码语法高亮功能,以下实现的插件修改自Screwturn Wiki's 的 SyntaxHighlight 插件,所在一些不太重要的代码中的英文注释我就不一一翻译了,只为说明如何配置此框架使用。
此代码高亮插件使用的也是SyntaxHighlight JS版的插件,在此就不再多作介绍了,相必大家都应该知道。OK,接下来就直接说代码了:
因为一个插件最终需要生成一个独立的DLL文件,因此我们需要先建立一个新的插件项目,就名为:CoderBlog.Plugin.SyntaxHighlighter
此语法高亮插件包含了2个类,一个专门处理程序语言的,此为辅助类:
此代码高亮插件使用的也是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");
}
}
}
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 文件
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
}
}
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 文件,填写以下测试函数:
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'>");
}
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