zoukankan      html  css  js  c++  java
  • .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=' {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=' text'>");
    }

     

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

     

     下载Demo代码:CoderBlog.PluginFramework.rar

  • 相关阅读:
    工厂与抽象工厂
    memcache的分布式配置
    Windows下的 mysql 5.5主从同步配置
    sql执行超时处理
    RabbitMQ远程访问配置
    事件驱动与流程驱动
    数据库连接池的配置和使用
    mysql常见的hint(转载的)
    数据库结构的设计与如何最大限度的利用索引
    [asp.net webfrom+spring.net Error] Resource handler for the 'web' protocol is not defined.
  • 原文地址:https://www.cnblogs.com/winsonet/p/2195213.html
Copyright © 2011-2022 走看看