.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< 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

 

你可能感兴趣的:(.net)