一次查找Windows Live Writer的VSPaste插件丢失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复制存在丢失格式的情况,于是就来研究这个问题了。
就丢失格式的情况举一个例子,比如,我复制的是如下代码:

然而,使用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。

另外,博客园官方发布的代码着色控件CNBlogs.CodeHighlighter确实如他们所说的,将代码提交至服务器处理。如下图:

填充测试工程
通过的分析,我们可以建立一个简单的测试工程。在分析VSPaste入口时,发现其引用了System.Windows.Forms。所以我们不妨新建一个Windows窗体应用程序来显示转换前的RTF和转换后的HTML。
新建工程后先添加VSPaste的引用,接下来再添加VSPaste所必需的WindowsLive.Writer.Api。这个DLL在哪里呢?由于是Windows Live Writer插件,所以猜测是在Windows Live Writer安装目录下,一查,果然存在这个DLL。可是要是安装目录下不存在该怎么办呢?我比较喜欢用Everything这个软件,可以直接输入文件名称查找,速度又快。但是使用该软件的前提是必须要保证对应的盘是NTFS文件系统。
完成主界面,一个主界面由两个文本框、一个两行的TableLayoutPanel和一个按钮组成,如下:

测试按钮的响应如下:

运行失败及解决方案
我们的测试工程已经完成了,接下来我们运行一下试试。编译成功,运行成功,接下来在VS中复制一段代码,点击运行试试。非常不幸,出现了这个错误:

这个错误我有经验,多出现于P/Invoke场景。也比较好解决,在工程属性的生成标签页中将生成平台改成x86即可。好了,再来尝试,依然报错:

不科学啊,平常都行啊,怎么这次就出问题。再仔细对比一下,还是有区别的,这次是找到的程序集清单定义与程序集引用不匹配。点击查看详细信息,如下图:

仔细阅读FusionLog的信息,发现应该是WindowsLive.Writer.Api的程序集版本不一致。难道是我哪里疏忽了?
打开ILSpy,查看VSPaste的所引用的WindowsLive.Writer.Api。结果如下图:

再在ILSpy中查看我所安装的Windows Live Writer 2012目录下的WindowsLive.Writer.Api的版本信息。结果如下图:

聪明如你,一定已经发现上面的不同了。没错,VSPaste所引用的版本是1.0.0.0,而Windows Live Writer所使用的是1.1.0.0,而且是平台是x86。这也解释了为什么第一次运行时提示试图加载格式不正确的程序。
既然已经知道了问题,那解决起来就简单了。这个时候需要用到程序集重定向,在应用程序配置文件中指定程序集绑定,如下图:

运行结果
在VS中复制最前面那段代码,运行程序,点击测试按钮:

运行结果如上图。RTF格式我了解不是太深入,不过这不影响接下来的操作。新建一个文本文档,将上部文本框中的文本复制到其中,并将其扩展名改为rtf。打开该文件,效果如下图:

从中可以发现,在生成HTML之前,复制出来的RTF已经不正确了。
再次运行结果
在写字板中模拟相应代码,效果如下图:

在RTF中复制代码,运行程序,点击测试按钮:

运行结果如上图。下面的HTML看起来不是很直观,新建一个文本文档,将文本框中的文本复制到其中,并将其扩展名改为html。打开该文件,效果如下图:

从中可以发现,VSPaste并没有出错。
进一步查找原因
从前面的实验可以发现,VSPaste并没有出错,从VS中复制出来的代码已经丢失了RTF格式信息。那么问题究竟会出现在哪里?我以前在VS2015中使用VSPaste都没有问题啊。这时候我有个猜想,如果VS没有出问题,那么很大可能就是哪个插件坑爹了。
查找问题插件
在VS中选择工具—>扩展和更新以打开插件列表,通过二分法来禁用插件以查看问题是否解决(禁用后需要重启VS)。很快,就找到了罪魁祸首,就是下面这货:

查找问题功能
我们找到问题插件后可以就此为止了么?当然可以。但是对于自己来说,总是想打破砂锅问到底。点击上图中右边的详细信息,可以了解到更多的Productivity Power Tools 2015信息。从中可以了解到它的各项功能,也知道了每项功能都可以进行开关。
在VS中选择工具—>选项,并在窗体左边的树状控件中选择Productivity Power Tools。如下图:

在前面了解Productivity Power Tools的功能中,我就已经有怀疑的对象了,就是HTML Copy。尝试将其关闭以查看问题是否解决(禁用后需要重启VS)。经过试验,发现果然是该项功能引发的问题。
还能不能进一步查找问题根源?答案是可以。如果多留意一下Productivity Power Tools 2015的详细信息,就会发现,在该页面右上部分有一个到GitHub的链接。嗯,项目还是微软的,不知为何为出现这种问题。

源码调试
下载代码
从GitHub上下载Productivity Power Tools的代码,由于其是一系列插件的集合,下载后很快就找到了HTML Copy对应的项目。

查找问题代码
代码不是太多,可以采取逐个文件阅读的方法。但是我已经知道症状了,就是复制出来的RTF数据不对,那么不妨查找代码中使用了剪贴板的地方。很快就找到了:

该函数只有一个引用,查看引用,可以找到:

再查看GenerateClipboardData的定义:

再继续查找,可以发现htmlBuilderService和rtfBuilderService都是通过MEF导入的。

在生成html和rtf的代码后面打一个断点,开始调试。在新运行的VS实例中打开工程,复制代码。在断点处查看,可以发现生成的rtf已经丢失了格式信息,而html仍然保留有格式信息。
那么这个rtfBuilderService究竟是何方神圣?在监视窗口查看详细信息:

实现自己的RtfBuilderService
前面已经知道了实现rtfBuilderService的类和所在程序集,在ILSpy中打开该程序集并定位到类:

在工程中新建一个类,类名不能为RtfBuilderService,将ILSpy中的所有代码复制出来放到该类中。将该类的导出类型设为对应的类名,同时在导入IRtfBuilderService的地方改为导入对应的类。如下:


而更改类名的原因是为了避免MEF导入导出失效。如果失效会出现以下情况:

分析RtfBuilderService代码
在我们实现的RtfBuilderService内部代码中查找GenerateRtf方法,发现其使用了如下方法:

再查看RtfBuilder内部的GenerateRtf方法

先查看GenerateBody方法,发现其主要是通过分析TextRunProperties的属性来生成rtf的:

而文本属性来源于GetClassificationSpans:

调试RtfBuilderService
在GenerateBody方法获取TextRunProperties后面打一个断点,开始调试。在新运行的VS实例中打开工程,复制代码。在断点处查看文本属性的颜色,发现只进入一次断点,且文本前景色为白色,背景色为黑色。
再次复制代码,在监视窗口处查看current的属性信息。其只有的ClassificationType和Span属性。在Span属性上可以看出它的内容仿佛是我们复制的代码,尝试看是否有获取文本的方法,一查,还真有:

对照监视窗口的各项值,可以发现,此时就已经丢失了所有的格式信息。由于我们的ClassificationType为空,所以始终返回的是默认文本属性。
在GetClassificationSpans第一行打一个断点,进行单步调试,可以发现一些信息。调用该方法的cancel参数为空,而GetClassificationSpans返回的列表中无条目,所以总会调用ClassificationType参数为空的NullableClassificationSpan的构造函数。接下来根据调用堆栈一层一层的往上查看,发现在最开始在GenerateClipboardData方法调用GenerateRtf时就已经决定了cancel为空。
这可怎么办,线索又断了,真的是无路可走了么?不,我们还有一条路!这个工程不是有生成HTML的代码么,它不是没丢失格式的嘛,去参考一下呗。
参考生成HTML的代码
经过一番查找,发现了在生成HTML代码中与RtfBuilderService中GetClassificationSpans方法功能类似的代码,连名字都一样。

查看该代码所调用的GetClassificationSpansSync方法:

发现这次调用GetAllClassificationSpans带了一个CancellationToken的参数,而生成WaitContext的IWaitIndicator来源于MEF导入。
怎么样,是不是山重水复疑无路,柳暗花明又一村?
完善RtfBuilderService
依照HTM生成部分依样画葫芦,如下图,红色部分表示新增代码:

经过调试测试,发现生成的RTF已经包含正确的格式信息了。
好了,问题已经解决了,我们到此为止了么?是否还可以做些其它什么?
另一种解决方案
让我们再次回到RtfBuilderService,为什么它会有那么多的重载?

查看实现的接口,发现除了实现IRtfBuilderService外,还实现了IRtfBuilderService2。两个接口的定义对比:


不难发现,IRtfBuilderService2是在IRtfBuilderService每个方法后面加上了一个CancellationToken重载。
所以,我们可以不用新增加类,直接将导入的IRtfBuilderService类型改为IRtfBuilderService2,同时在生成rtf的地方传入CancelToken以调用对应的接口方法。
注意事项
微软建议的在调试插件时需要Productivity Power Tools卸载了。我在研究问题时是卸载了的,但是在写这边博客时没有卸载,貌似也没什么问题。
另外,因为我是先研究的问题,后写的博客,可能有些细节忘记了或者没有写上,有心研究的话可以联系我。
结语
因为事情比较多,断断续续这么久,终于把这篇博客写完了。之所以写这么多,主要是想分享下我解决问题的过程和思路。作为程序员,我个人觉得还是有一些探索精神好一些,很多时候,路不是想象的那么难走。
最后,我给微软提了个issue……

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历的更多相关文章
- Windows Live Writer的Markdown插件
我新写了一个Windows Live Writer的Markdown插件,代码放在了github上. 介绍 这个项目是一个Windows Live Writer的Markdown插件.有了这个插件,你 ...
- 为Windows Live Writer添加Code插件
1.插件效果展示 1: void CDemoDlg::OnBnClickedNmdlg() 2: { 3: CNonModeDlg *pDlg = new CNonModeDlg();// 创建一个C ...
- 用 Windows Live Writer 和 SyntaxHighlighter 插件写高亮代码
博客园内置支持SyntaxHighlighter代码着色,代码着色语法:<pre class='brush:编程语言'>代码</pre>. 需要注意的是:如何你使用Syntax ...
- Windows Live Writer的Markdown插件MarkdownInLiveWriter支持语法高亮了
我前几天开发的Windows Live Writer的Markdown的插件MarkdownInLiveWriter支持语法高亮了.参见下图: 基本上就是把我的另一个插件CodeInLiveWrite ...
- Windows Live Writer代码高亮插件对比
一.Paste ASVisual Studio Code 参考:http://www.cnblogs.com/mikelij/archive/2010/11/13/1876199.html 插件下载: ...
- 新语言代码高亮及Windows Live Writer插件开发
最近在博客园做一些学习笔记.一个是看apple的swift官方书,另外一个是随学校课堂(SICP)学习scheme. 这两种语言都谈不上普及(或者说swift太新).博客园原来的windows liv ...
- 使用Windows Live Writer发布日志
前言 Windows Live Writer是非常不错的一个日志发布工具,支持本地写文章,然后通过点击一个按钮就发布到网站上,如果借助插件,还可以同时发布到多个博客网站,功能非常强大,很多博友认识她之 ...
- 在Windows Live Writer中插入C# code
平时都是用Windows Live Writer写博客,发布博客.遇到需要插入代码都是先在notepad中写好,或者是拷贝到notepad,再从notepad中拷到Windows Live Write ...
- Windows Live Writer介绍及相关问题解决
今天本来想说更新一篇我的文章,更新的过程中添加了很多的内容,里面的图片太多了,导致我浏览器占用的内存不断增大,浏览器变得很卡,最后过了好久我终于更新完文章打算保存的时候居然卡住,然后所有我更新的文字和 ...
随机推荐
- [.net]线程基础
关于线程的诞生 早期的16位Windows只有一个执行线程,在执行各种程序时,如果这个线程运行出现了问题,就会“冻结”整个系统,使得系统处于未响应状态.这是一件多么尴尬的事儿,无论是用户还是微软自己, ...
- Kotlin 初体验
本文由作者邹丽萍授权网易云社区发布. 背景 Kotlin 是 JetBrains 公司(著名的 IntelliJ IDEA 正是由这家公司开发的,Android Studio 也是基于 IDEA 的) ...
- spring jdbc批量插入
http://blog.csdn.net/fyqcdbdx/article/details/7366439
- 程序猿的日常——JVM内存模型与垃圾回收
Java开发有个很基础的问题,虽然我们平时接触的不多,但是了解它却成为Java开发的必备基础--这就是JVM.在C++中我们需要手动申请内存然后释放内存,否则就会出现对象已经不再使用内存却仍被占用的情 ...
- es6中箭头函数 注意点
var aaabbb = 'kkkooo' setTimeout(()=>{ var aaaa = 'kkkk'; console.log(this) },1000); 因为据我了解,箭头函数指 ...
- iOS---UICollectlionView 的使用
UICollectlionView继承自UIScrollerview,跟tableview的使用很相似. 下面是UIcollectionView的一些属性和代理方法. #import "Vi ...
- iOS完全自学手册——[三]Objective-C语言速成,利用Objective-C创建自己的对象
1.前言 上一篇已经介绍了App Delegate.View Controller的基本概念,除此之外,分别利用storyboard和纯代码创建了第一个Xcode的工程,并对不同方式搭建项目进行了比较 ...
- SPSS学习系列之SPSS Modeler Server是什么?
不多说,直接上干货! SPSS Modeler 使用客户端/服务器体系结构将资源集约型操作的请求分发给功能强大的服务器软件,因而使大数据集的传输速度大大加快.除了此处所列的产品和更新,也可能还有其他可 ...
- PMP备考指南之第一章:引论
本文已同步至 GitHub/Gitee/公众号,感兴趣的同学帮忙点波关注~ 第一章 引论 1."项目管理知识体系":应该包含所有行业.应用领域项目管理的具体知识.技能.方法和实践 ...
- centos7设置SSH安全策略–指定IP登陆
之前自己搭建了个博客网站(理想三旬),写了些文章,但是由于一些原因慢慢将文章放在博客园了.所以这里将一些文章复制过来.便于以后自己查询. 为了服务器的安全性,我们在日常使用需要授予权限和指定ip登陆来 ...