一次查找Windows Live Writer的VSPaste插件丟失RTF格式信息的經歷

来源:https://www.cnblogs.com/yiyan127/archive/2018/02/20/FindVSPasteLoseRTFInformation.html
-Advertisement-
Play Games

記錄了從查找Windows Live Writer上VSPasste插件丟失RTF格式信息問題的原因,到最終解決問題的整個經歷。 ...


背景

我在博客園上寫博客是使用Windows Live Writer,代碼高亮插件是使用Paste from Visual Studio(下文簡稱VSPaste)。

Windows Live Writer更進一步的資料,可參照【超詳細教程】使用Windows Live Writer 2012和Office Word 2013 發佈文章到博客園全面總結,下載地址在此處

VSPaste更進一步的資料,可參照CnBlogs博文排版技巧。由於Windows Live Writer 2012的終止日期是2017年1月10日,並且對應的插件網站也關閉了,所以目前沒有官方下載,有需要的可以聯繫我。

起因

好久沒更新過博客了,一是懶,二是沒什麼值得分享的。恰好手上有了一點可以分享的話題,就開始興高采烈的寫博客了。寫著寫著,發現了VSPaste複製存在丟失格式的情況,於是就來研究這個問題了。

就丟失格式的情況舉一個例子,比如,我複製的是如下代碼:

Snap1

然而,使用VSPaste插入到Windows Live Writer中後,文字全都成了黑色。綠色和藍色呢?

檢查VSPaste是否出錯

由於VSPaste已經很久沒有更新,所以我的第一反應是查看VSPaste是否出錯。為了驗證判斷,我們不妨建立一個工程進行測試。

查找入口

在建立工程之前,需要先瞭解Windows Live Writer調用VSPaste的函數入口。在必應上搜索windows live writer plugin develop,發現有一篇名為Developing Plugins for Windows Live Writer的文章,經過瞭解後發現,插件一定繼承自ContentSource或者SmartContentSource。其中ContentSource是直接插入HTML到Windows Live Writer中,而SmartContentSource功能會更豐富一些,比如可以添加後編譯。

打開ILSpy,將VSPaste的程式集拖入其中。經過簡單查看,發現VSPaste插件的入口類正是繼承自SmartContentSource。而且其中做的事情很簡單,判斷剪貼板中是否存在RTF格式的數據,如果存在,將其轉換為HTML。

Snap16

另外,博客園官方發佈的代碼著色控制項CNBlogs.CodeHighlighter確實如他們所說的,將代碼提交至伺服器處理。如下圖:

Snap14

填充測試工程

通過的分析,我們可以建立一個簡單的測試工程。在分析VSPaste入口時,發現其引用了System.Windows.Forms。所以我們不妨新建一個Windows窗體應用程式來顯示轉換前的RTF和轉換後的HTML。

新建工程後先添加VSPaste的引用,接下來再添加VSPaste所必需的WindowsLive.Writer.Api。這個DLL在哪裡呢?由於是Windows Live Writer插件,所以猜測是在Windows Live Writer安裝目錄下,一查,果然存在這個DLL。可是要是安裝目錄下不存在該怎麼辦呢?我比較喜歡用Everything這個軟體,可以直接輸入文件名稱查找,速度又快。但是使用該軟體的前提是必須要保證對應的盤是NTFS文件系統。

完成主界面,一個主界面由兩個文本框、一個兩行的TableLayoutPanel和一個按鈕組成,如下:

Snap17

測試按鈕的響應如下:

Snap18

運行失敗及解決方案

我們的測試工程已經完成了,接下來我們運行一下試試。編譯成功,運行成功,接下來在VS中複製一段代碼,點擊運行試試。非常不幸,出現了這個錯誤:

Snap19

這個錯誤我有經驗,多出現於P/Invoke場景。也比較好解決,在工程屬性的生成標簽頁中將生成平臺改成x86即可。好了,再來嘗試,依然報錯:

Snap20

不科學啊,平常都行啊,怎麼這次就出問題。再仔細對比一下,還是有區別的,這次是找到的程式集清單定義與程式集引用不匹配。點擊查看詳細信息,如下圖:

Snap24

仔細閱讀FusionLog的信息,發現應該是WindowsLive.Writer.Api的程式集版本不一致。難道是我哪裡疏忽了?

打開ILSpy,查看VSPaste的所引用的WindowsLive.Writer.Api。結果如下圖:

Snap25

再在ILSpy中查看我所安裝的Windows Live Writer 2012目錄下的WindowsLive.Writer.Api的版本信息。結果如下圖:

Snap26

聰明如你,一定已經發現上面的不同了。沒錯,VSPaste所引用的版本是1.0.0.0,而Windows Live Writer所使用的是1.1.0.0,而且是平臺是x86。這也解釋了為什麼第一次運行時提示試圖載入格式不正確的程式。

既然已經知道了問題,那解決起來就簡單了。這個時候需要用到程式集重定向,在應用程式配置文件中指定程式集綁定,如下圖:

Snap28

運行結果

在VS中複製最前面那段代碼,運行程式,點擊測試按鈕:

Snap29

運行結果如上圖。RTF格式我瞭解不是太深入,不過這不影響接下來的操作。新建一個文本文檔,將上部文本框中的文本複製到其中,並將其擴展名改為rtf。打開該文件,效果如下圖:

Snap30

從中可以發現,在生成HTML之前,複製出來的RTF已經不正確了。

再次運行結果

在寫字板中模擬相應代碼,效果如下圖:

Snap31

在RTF中複製代碼,運行程式,點擊測試按鈕:

Snap33

運行結果如上圖。下麵的HTML看起來不是很直觀,新建一個文本文檔,將文本框中的文本複製到其中,並將其擴展名改為html。打開該文件,效果如下圖:

Snap34

從中可以發現,VSPaste並沒有出錯。

進一步查找原因

從前面的實驗可以發現,VSPaste並沒有出錯,從VS中複製出來的代碼已經丟失了RTF格式信息。那麼問題究竟會出現在哪裡?我以前在VS2015中使用VSPaste都沒有問題啊。這時候我有個猜想,如果VS沒有出問題,那麼很大可能就是哪個插件坑爹了。

查找問題插件

在VS中選擇工具—>擴展和更新以打開插件列表,通過二分法來禁用插件以查看問題是否解決(禁用後需要重啟VS)。很快,就找到了罪魁禍首,就是下麵這貨:

Snap35

查找問題功能

我們找到問題插件後可以就此為止了麽?當然可以。但是對於自己來說,總是想打破砂鍋問到底。點擊上圖中右邊的詳細信息,可以瞭解到更多的Productivity Power Tools 2015信息。從中可以瞭解到它的各項功能,也知道了每項功能都可以進行開關。

在VS中選擇工具—>選項,併在窗體左邊的樹狀控制項中選擇Productivity Power Tools。如下圖:

Snap37

在前面瞭解Productivity Power Tools的功能中,我就已經有懷疑的對象了,就是HTML Copy。嘗試將其關閉以查看問題是否解決(禁用後需要重啟VS)。經過試驗,發現果然是該項功能引發的問題。

還能不能進一步查找問題根源?答案是可以。如果多留意一下Productivity Power Tools 2015的詳細信息,就會發現,在該頁面右上部分有一個到GitHub的鏈接。嗯,項目還是微軟的,不知為何為出現這種問題。

Snap38

源碼調試

下載代碼

從GitHub上下載Productivity Power Tools的代碼,由於其是一系列插件的集合,下載後很快就找到了HTML Copy對應的項目。

Snap41

查找問題代碼

代碼不是太多,可以採取逐個文件閱讀的方法。但是我已經知道癥狀了,就是複製出來的RTF數據不對,那麼不妨查找代碼中使用了剪貼板的地方。很快就找到了:

Snap42

該函數只有一個引用,查看引用,可以找到:

Snap43

再查看GenerateClipboardData的定義:

Snap44
再繼續查找,可以發現htmlBuilderService和rtfBuilderService都是通過MEF導入的。

Snap45
在生成html和rtf的代碼後面打一個斷點,開始調試。在新運行的VS實例中打開工程,複製代碼。在斷點處查看,可以發現生成的rtf已經丟失了格式信息,而html仍然保留有格式信息。

那麼這個rtfBuilderService究竟是何方神聖?在監視視窗查看詳細信息:

Snap46

實現自己的RtfBuilderService


前面已經知道了實現rtfBuilderService的類和所在程式集,在ILSpy中打開該程式集並定位到類:

Snap47

在工程中新建一個類,類名不能為RtfBuilderService,將ILSpy中的所有代碼複製出來放到該類中。將該類的導出類型設為對應的類名,同時在導入IRtfBuilderService的地方改為導入對應的類。如下:

Snap49

Snap51

而更改類名的原因是為了避免MEF導入導出失效。如果失效會出現以下情況:

Snap48

分析RtfBuilderService代碼

在我們實現的RtfBuilderService內部代碼中查找GenerateRtf方法,發現其使用瞭如下方法:

Snap52
再查看RtfBuilder內部的GenerateRtf方法

Snap53
先查看GenerateBody方法,發現其主要是通過分析TextRunProperties的屬性來生成rtf的:

Snap54
而文本屬性來源於GetClassificationSpans:

Snap56

調試RtfBuilderService

在GenerateBody方法獲取TextRunProperties後面打一個斷點,開始調試。在新運行的VS實例中打開工程,複製代碼。在斷點處查看文本屬性的顏色,發現只進入一次斷點,且文本前景色為白色,背景色為黑色。

再次複製代碼,在監視視窗處查看current的屬性信息。其只有的ClassificationType和Span屬性。在Span屬性上可以看出它的內容仿佛是我們複製的代碼,嘗試看是否有獲取文本的方法,一查,還真有:

Snap59

對照監視視窗的各項值,可以發現,此時就已經丟失了所有的格式信息。由於我們的ClassificationType為空,所以始終返回的是預設文本屬性。

在GetClassificationSpans第一行打一個斷點,進行單步調試,可以發現一些信息。調用該方法的cancel參數為空,而GetClassificationSpans返回的列表中無條目,所以總會調用ClassificationType參數為空的NullableClassificationSpan的構造函數。接下來根據調用堆棧一層一層的往上查看,發現在最開始在GenerateClipboardData方法調用GenerateRtf時就已經決定了cancel為空。

這可怎麼辦,線索又斷了,真的是無路可走了麽?不,我們還有一條路!這個工程不是有生成HTML的代碼麽,它不是沒丟失格式的嘛,去參考一下唄。

參考生成HTML的代碼

經過一番查找,發現了在生成HTML代碼中與RtfBuilderService中GetClassificationSpans方法功能類似的代碼,連名字都一樣。

Snap60

查看該代碼所調用的GetClassificationSpansSync方法:

Snap61
發現這次調用GetAllClassificationSpans帶了一個CancellationToken的參數,而生成WaitContext的IWaitIndicator來源於MEF導入。

怎麼樣,是不是山重水複疑無路,柳暗花明又一村?

完善RtfBuilderService

依照HTM生成部分依樣畫葫蘆,如下圖,紅色部分表示新增代碼:

Snap62

經過調試測試,發現生成的RTF已經包含正確的格式信息了。

好了,問題已經解決了,我們到此為止了麽?是否還可以做些其它什麼?

另一種解決方案

讓我們再次回到RtfBuilderService,為什麼它會有那麼多的重載?

Snap63
查看實現的介面,發現除了實現IRtfBuilderService外,還實現了IRtfBuilderService2。兩個介面的定義對比:

Snap64
Snap66

不難發現,IRtfBuilderService2是在IRtfBuilderService每個方法後面加上了一個CancellationToken重載。

所以,我們可以不用新增加類,直接將導入的IRtfBuilderService類型改為IRtfBuilderService2,同時在生成rtf的地方傳入CancelToken以調用對應的介面方法。

註意事項

微軟建議的在調試插件時需要Productivity Power Tools卸載了。我在研究問題時是卸載了的,但是在寫這邊博客時沒有卸載,貌似也沒什麼問題。

另外,因為我是先研究的問題,後寫的博客,可能有些細節忘記了或者沒有寫上,有心研究的話可以聯繫我。

結語

因為事情比較多,斷斷續續這麼久,終於把這篇博客寫完了。之所以寫這麼多,主要是想分享下我解決問題的過程和思路。作為程式員,我個人覺得還是有一些探索精神好一些,很多時候,路不是想象的那麼難走。

最後,我給微軟提了個issue……

Snap68


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 以往tp3.2里模型插入資料庫的方法通常使用add tp5後一開始我發現save與create方法都能插入數據 也不知道有什麼分別,後來一般都使用create方法 終於有一次操作關聯表時,由於使用create沒有返回主鍵 導致關聯表的另一個欄位沒有寫入到調用的實例里 通過查看文檔使用save解決 ...
  • 2018-02-19 17:15:14 Python語言相對於其他語言較為簡潔,也相對好入門比如後面不加分號,基本見不著大括弧等優點 第一個程式,也是學每門語言都需要掌握的第一個代碼 print("Hello World") print 語句用法 接下來是輸入用戶名密碼 進行格式化拼接 由以上可以看 ...
  • 一、單例模式 a、單例模式分為四種:文件,類,基於__new__方法實現單例模式,基於metaclass方式實現 b、類實現如下: c、基於__new__方法實現單例模式 d、基於metaclass方式實現單例模式 ...
  • 一、冒泡排序 a、冒泡排序 優化 如果冒泡排序中執行一趟而沒有交換,則列表已經是有序狀態,可以直接結演算法 二、選擇排序 a、一趟遍歷記錄最小的數,放到第一個位置; b、在一趟遍歷記錄剩餘列表中最小的數,繼續放置 三、插入排序 a、列表被分為有序區和無序區兩個部分,最初有序區只有一個元素 b、每次從無 ...
  • 本系列上一篇文章中我們就說到了,這一次我們要說 pop3 收信了。雖然我覺得應該先說完 mime 格式,不過估計大家已經不耐煩了 -- 怎麼老在說發送啊?我們要看收取! 好吧,來啦,來啦!收取郵件現在常用的有 pop3 和 imap 協議,不過從傳統來說 pop3 受眾要廣得多。有了前面的基礎,要實 ...
  • 使用opencv-python一段時間了,因為之前沒有大量接觸過c++下的opencv,在網上看c++的一些程式想改成python遇到了不少坑,正好在這裡總結一下。 1.opencv 中x,y,height, width,rows,cols 的關係(轉自http://blog.csdn.net/ik ...
  • abs(x) 求一個數的絕對值。 all(iterable) 如果迭代器中的所有值都為“真”則返回 True, 否則返回 False 註意: 如果迭代器為空,返回 True any(iterable) 如果迭代器中的任意一個值為“真”則返回 True, 否則返回 False 註意: 如果迭代器為空, ...
  • 格式化代碼: ^I 轉到定義:⌘D 註釋/反註釋: ⌘/ 生成:⌘B 重新生成:^⌘B 調試運行:⌘↩ 不調試運行:⌥⌘↩ 添加/刪除斷點:⌘\ 查看所有斷點:⌥⌘↩ 放大:⌘+ 縮小:⌘- 正常大小:⌘0 全屏/取消全屏:^⌘F 頁內搜索:⌘F 文件/項目內搜索:⇧⌘F 上一步操作游標:⌃⌘← 下 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...