Windows Live Writer插件开发经验

来源:互联网 发布:知行理工怎么登不上 编辑:程序博客网 时间:2024/05/01 15:12
题注:在我写这篇文章的时候,CSDN还支持MetaBlogAPI,但自从blog改版后,CSDN已经不再支持MetaBlogAPI了,但是下面介绍的这些技巧对于Window Live Writer的插件开发以及博客园等支持MetaBlogAPI的站点依然适用。

背景

经常写博客的朋友也许经常使用Windows Live Writer,尤其对于有些程序员来说,时常辗转于CSDN、Windows Live、cnBlogs等众多社区,使用WLW对博客进行管理和更新会方便很多。我最近在写一些技术类博客文章的时候,时常需要插入代码,目前也有许多第三方的代码格式化插件,如“Insert Code”、“Code Snippet”等,这些插件能让代码插入后输出的HTML中产生语法高亮、行号、隔行阴影等特效,但是与CSDN本身的“插入代码”功能相比,这些插件输出的内容中会包含大量的样式,当代码量较大时,会造成文章大小急剧地膨胀。

如果要解决内联样式造成的HTML膨胀问题,可以采用CSS+JavaScript的解决方案,如著名的的“Syntax Highlighter”项目,但是这需要博客系统和平台的支持,Wordpress与CSDN都是采用Syntax Hightlighter。因此,我在研究了几个WLW的插件和Syntax Hightlighter的接口后,决定自己开发一个利用CSDN本身的“插入代码”功能的插件。

 

WLW 插件SDK

微软已经在其MSDN上发布了Windows Live Writer的SDK,其中关于插件编写的部分可以参看:Windows Live Writer Plugin API和这篇博客http://www.cnblogs.com/yaoshiyou/archive/2009/11/28/1612746.html。

WLW的插件可以分为三类:

  1. 发布通知插件:这类插件主要用于在博客文章发布前后调用,可以用来对内容的XHTML进行检查或者在某些条件下取消发布。
  2. 内容插件:这类插件用于让用户插入某种形式的内容,并对内容进行格式化处理,例如某些插件可以让用户选择插入本地的一张图片,然后生成缩略图上传并最终返回链接地址。
  3. 页眉页脚插件:这类插件主要用于自动在发布文章首尾添加一些内容,如签名、链接等等。

本次需求就是开发第二类插件,让用户输入源程序代码,生成符合CSDN样式的HTML文本。

 

插件开发步骤

WLW的插件开发的大致步骤如下:

  1. 创建一个新的.Net类库项目(framework 1.1以上)。
  2. 在解决方案资源管理器中右键单击该项目,选择“添加引用”,然后选择“浏览”的选项,导入对WindowsLive.Writer.Api.dll的引用,此文件位于Window Live Writer执行文件所在文件夹下,如:C:/Program Files/Windows Live/Writer/。
  3. 创建一个类,继承自WindowsLive.Writer.Api.ContentSource(如果创建其他类型插件,可能继承自其他类,可以参看文档)。
  4. 对这个类应用WindowsLive.Writer.Api.WriterPluginAttribute属性,以指明插件的名称、图标及唯一ID等信息。
  5. ContentSource有3个虚方法:CreateContent()、CreateContentFromLiveClipboard()、CreateContentFromUrl(),根据源内容的输入方式的不同,后续的步骤有一定的不同:
  • 对话框输入:如果希望点击插件后,弹出一个对话框来让用户输入内容;这时需要为这个类应用WindowsLive.Writer.Api.InsertableContentSourceAttribute属性,然后重载

    CreateContent()方法。

  • 剪切板输入:这种模式是先将内容复制到剪切板中(准确的说应该是Live Clipboard,微软为Web开发一种数据交换技术,采用XML描述),然后点击插件,插件则直接读取剪切板中的内容作为自己的源;这时需要为这个类应用WindowsLive.Writer.Api.LiveClipboardContentSourceAttribute属性,然后重载CreateContentFromLiveClipboard()方法。
  • URL输入:根据WindowsLiveWriterApplication.BlogThisLink中的URL或者粘贴或拖入到编辑器中的URL作为数据源输入;这时需要为这个类应用WindowsLive.Writer.UrlContentSourceAttribute属性,然后重载CreateContentFromUrl()方法。
  • 当然,有时候插件可能不需要任何输入源,只需当点击插件按钮后输出信息即可(如个人签名、当前日期等),则可以以上任选一种方法,在重载函数中忽略输入的内容即可。

重载方法后,编译成dll,然后将其放置到Window Live Writer安装目录下的Plugins目录下,重启WLW,即可以看见新的插件了,也可以在项目的生成后事件添加如下语句,自动复制到插件目录下进行调试: XCOPY /D /Y /R "$(TargetPath)" "C:/Program Files/Windows Live/Writer/Plugins/",调试时只能先启动WLW,然后手动附加到进程。

 

示例详解

为了详解以上步骤中的几个难点,下面我列出一个最简单的插件示例。

   1: Imports WindowsLive.Writer.Api
   2: Imports System.Windows.Forms
   3: Imports System.Text
   4:  
   5: <WriterPlugin("4cbc0496-d225-4abb-afe3-58299032fde3", _
   6:               "CSDN Coding", _
   7:               Description:="Windows Live Writer Plugin for CSDN", _
   8:               ImagePath:="blog.bmp", _
   9:               PublisherUrl:="http://blog.csdn.net/icefireelf")> _
  10: <InsertableContentSource("CSDN代码")> _
  11: Public Class PluginAdapter
  12:     Inherits ContentSource
  13:     Public Overrides Function CreateContent(ByVal dialogOwner As IWin32Window, ByRef content As String) As System.Windows.Forms.DialogResult
  14:         Dim ret = MessageBox.Show("是否插入一条问候?", "问候语插件", MessageBoxButtons.OKCancel)
  15:         If (ret = DialogResult.OK) Then
  16:             content = "hello"
  17:         End If
  18:         Return ret
  19:     End Function
  20: End Class

WriterPluginAttribute

WriterPlugin有2个必填属性:

  • Id:即上面代码中的构造的第一个参数,此参数用于唯一标识一个插件,必须用GUID,使用项目属性中程序集的GUID即可。
  • Name:即上面代码中的构造的第二个参数,此参数即WLW的插件栏中显示的名称,建议不要太长。

此外还有几个可选属性:

  • Description:字符串属性,此参数即WLW的插件详细信息中显示的内容,简单描述插件功能。
  • ImagePath: 图标路径,此参数决定WLW参见栏名称前的图标,如果不填则没有图标。图标须采用16*16的位图或PNG图,且必须作为嵌入的资源(将图标添加到工程中,在解决方案资源管理器中右键点击图标,选择“属性”,然后将属性框中的“生成操作”设为“嵌入的资源”即可)。注意:如果图标放置在工程目录下,则直接填图标名称即可;如果放置在工程目录的子级目录下时,使用“.”作为目录分隔符,例如:将图标放置在工程目录下的image/目录中,图标路径应写为“image.blog.bmp”,而非“image/blog.bmp”。

  • PublisherUrl:发表者的URL,如果设置了此属性,在WLW的插件信息中点击详细信息的文字链接时,会打开此URL。

  • HasEditableOptions: 详细信息里面是否有“选项”按钮(如下图所示),此参数默认为False,即不带“选项”按钮,当其设置为True时,则必须为本类重载EditOptions()方法,在该方法中,可以启动一个用于配置插件的窗体,获取参数,然后保存在配置文件或本类对象的成员中,共后续访问使用。

InsertableContentSourceAttribute

此属性本用于定义插件在WLW的“Insert”菜单和“Insert”快捷面板的名称,但新版的WLW已经去除了插入菜单与侧边栏,因此应用此属性只是为了保持向前兼容。

ContentSource对象生存期

由上面的代码可以看出,每个内容插件都对应一个ContentSource派生类的对象。那么这个对象是如何维护的呢?对于每个Writer的进程而言,所有ContentSource实例都是单例的,即当插件对象创建后,以后每次点击插件,使用的都是同一个对象。所以,如果该对象拥有任何实例成员,则其在整个进程的上下文中都是有效的,可以用来存储一些关于插件的全局参数(例如Options属性)。

ContentSource类继承自WriterPlugin类,因此也继承了WriterPlugin.Options属性与WriterPlugin.Initialize()方法。

  • WriterPlugin.Options是一个键值对集合对象,一般用于保存与查询插件的参数记录(如最后一次的配置)。
  • WriterPlugin.Initialize()方法在本对象初始化时调用,可以在子类中可以重载此方法,添加自己的初始化行为,但一定不要忘记住在重载方法中调用基类的Initialize()实现。

CreateContent方法

ContentContent方法是本插件的入口,在样例代码中,我们实现了一个小功能:每次点击插件,弹出一个对话框,询问用户是否插入问候语,如果用户点击确认,则输出“hello”的字符串,否则不输出。此接口的形式如下:

public virtual DialogResult CreateContent( IWin32Window dialogOwner, ref string content );

本方法有两个参数:

  • dialogOwner:对话框的拥有者。
  • content:从ref可以看出这个参数是做为输出用,即最后输出编辑区的内容。

如果输出了内容,则返回DialogResult.OK,否则,返回DialogResult.Cancel

注意:前面提到过,由于ContentSource对象是单例的,所以在实现本方法,不应该使用任何实例成员来保存临时变量,应做到可重入。

 

Syntax Highlighter

Syntax Highlight支持<pre>与<textarea>两种元素,下面为两个例子:

   1: <pre name="code" class="c-sharp:nogutter:collapse">代码内容</pre>
   2: <texterea name="code" class="cpp:nocontrols:firstline[10]" clos="60" rows="10">代码内容</texterea>

由上可以看出,<pre>与<textarea>的name属性必须为"code" ,而class属性里可以指定代码的语言类型,是否显示行号、是否显示控制条、是否折叠、其实行号等属性,具体如下:

  • 语言项 目前支持的语言项有cpp、c-sharp、css、java、javascript、vb、sql、ruby、delphi、python、php、xhtml
  • nogutter  如果添加此属性,将不显示行号。
  • nocontrols  如果添加此属性,将不会在代码块顶部显示控制器(包含折叠、打印、复制等命令)。
  • collapse  如果添加此属性,将默认折叠代码。
  • firstline[value]  如果添加此属性,行号从value开始计数,默认值是 1。
  • showcolumns  如果添加此属性,将在第一行显示列号。

其中代码内容需要用HtmlEncode进行编码,替换掉'<'、'>'等escape符号。如果查看过CSDN的“插入代码”输出的HTML源码,可以发现其采用的就是<textarea>方案,而且到目前为止还存在bug,因为其没有对插入c++代码进行Html编码,所以当代码中有掉'<'或'>'时,会导致插入代码错误。

 

My Blog Plugin

在了解了Syntax Highlighter的输出格式后,就可以设置ContentSource的输出方式了,我的设计是当点击插件后,弹出以下对话框:

在其中选择好语言种类和各类配置后,在文本框中输入代码,点击插入后,就按照Syntax Highlighter的格式输出HTML文本,核心代码如下:

   1: Imports WindowsLive.Writer.Api
   2: Imports System.Windows.Forms
   3: Imports System.Text
   4:  
   5: <WriterPlugin("4cbc0496-d225-4abb-afe3-58299032fde3", _
   6:               "CSDN Coding", _
   7:               Description:="Windows Live Writer Plugin for CSDN", _
   8:               ImagePath:="blog.bmp", _
   9:               PublisherUrl:="http://blog.csdn.net/icefireelf")> _
  10: <InsertableContentSource("CSDN代码")> _
  11: Public Class PluginAdapter
  12:     Inherits ContentSource
  13:  
  14:     Private Sub SaveSettings(ByVal cf As CodeForm)
  15:         Options.SetBoolean("CSDN_EnableFolding", cf.EnableFolding)
  16:         Options.SetBoolean("CSDN_EnableLineNum", cf.EnableLineNum)
  17:         Options.SetBoolean("CSDN_EnableToolBar", cf.EnableToolBar)
  18:         Options.SetInt("CSDN_SelectedIndex", cf.SelectedIndex)
  19:     End Sub
  20:  
  21:     Private Sub LoadSettings(ByVal cf As CodeForm)
  22:         cf.EnableFolding = Options.GetBoolean("CSDN_EnableFolding", False)
  23:         cf.EnableLineNum = Options.GetBoolean("CSDN_EnableLineNum", True)
  24:         cf.EnableToolBar = Options.GetBoolean("CSDN_EnableToolBar", True)
  25:         Dim index As Integer = Options.GetInt("CSDN_SelectedIndex", -1)
  26:         If (index >= 0) Then
  27:             cf.SelectedIndex = index
  28:         End If
  29:     End Sub
  30:  
  31:     Public Function HtmlEncode(ByVal content As String) As String
  32:         Dim ret As String = HtmlServices.HtmlEncode(content)
  33:         Return ret
  34:     End Function
  35:  
  36:     Public Overrides Function CreateContent(ByVal dialogOwner As IWin32Window, ByRef content As String) As System.Windows.Forms.DialogResult
  37:         Try
  38:             Using cf As New CodeForm
  39:                 LoadSettings(cf)
  40:                 If (cf.ShowDialog(dialogOwner) = DialogResult.No OrElse String.IsNullOrEmpty(cf.GetCode)) Then
  41:                     Return DialogResult.No
  42:                 End If
  43:                 SaveSettings(cf)
  44:                 content = "<pre name=""code"" class=""" & cf.GetLanguage
  45:                 If (cf.EnableFolding) Then
  46:                     content &= ":collapse"
  47:                 End If
  48:  
  49:                 If (Not cf.EnableLineNum()) Then
  50:                     content &= ":nogutter"
  51:                 Else
  52:                     Dim first As Integer = cf.GetFirstLine()
  53:                     If (first > 1) Then
  54:                         content &= ":firstline[" & first.ToString & "]"
  55:                     End If
  56:                 End If
  57:  
  58:                 If (Not cf.EnableToolBar) Then
  59:                     content &= ":nocontrols"
  60:                 End If
  61:                 content &= """>" & HtmlEncode(cf.GetCode()) & "</pre>"
  62:                 Return DialogResult.OK
  63:             End Using
  64:         Catch ex As Exception
  65:             MessageBox.Show(ex.Message)
  66:             Return DialogResult.No
  67:         End Try
  68:     End Function
  69: End Class

通过代码可以看出,我选择了用<pre>标签,而没有采用CSDN的<textarea>方案,这是因为Windows Live Writer目前存在一个Bug:会在编辑模式下自动将<textarea>中的回车都删除,这就会导致所有代码被缩成一行(在CSDN网页上写好的代码用WLW下载下来后千万不要上传,因为它会修改<textarea>的格式)。

目前此工程的所有源代码已经上传至 http://download.csdn.net/source/3156349 ,有兴趣的朋友可以下载参考,或者直接编译后将生成的dll放入WLW的插件目录直接使用。

其他

作者:icefireelf
出处:http://blog.csdn.net/icefireelf/article/details/6296952

原创粉丝点击