Windows Live Writer插件開發經驗

題注:在我寫這篇文章的時候,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(),根據源內容的輸入方式的不同,後續的步驟有一定的不同:

重載方法後,編譯成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

發佈了24 篇原創文章 · 獲贊 2 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章