代码改变世界

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

2011-09-29 10:51  w i n s o n  阅读(2421)  评论(2编辑  收藏  举报

 

.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<stringstring> m_Brushes = new Dictionary<stringstring>(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