欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

程序员文章站 2022-07-01 08:49:39
记录了从查找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复制存在丢失格式的情况,于是就来研究这个问题了。

就丢失格式的情况举一个例子,比如,我复制的是如下代码:

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

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

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

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

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

填充测试工程

通过的分析,我们可以建立一个简单的测试工程。在分析VSPaste入口时,发现其引用了System.Windows.Forms。所以我们不妨新建一个Windows窗体应用程序来显示转换前的RTF和转换后的HTML。

新建工程后先添加VSPaste的引用,接下来再添加VSPaste所必需的WindowsLive.Writer.Api。这个DLL在哪里呢?由于是Windows Live Writer插件,所以猜测是在Windows Live Writer安装目录下,一查,果然存在这个DLL。可是要是安装目录下不存在该怎么办呢?我比较喜欢用Everything这个软件,可以直接输入文件名称查找,速度又快。但是使用该软件的前提是必须要保证对应的盘是NTFS文件系统。

完成主界面,一个主界面由两个文本框、一个两行的TableLayoutPanel和一个按钮组成,如下:

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

测试按钮的响应如下:

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

运行失败及解决方案

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

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

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

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

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

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

仔细阅读FusionLog的信息,发现应该是WindowsLive.Writer.Api的程序集版本不一致。难道是我哪里疏忽了?

打开ILSpy,查看VSPaste的所引用的WindowsLive.Writer.Api。结果如下图:

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

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

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

聪明如你,一定已经发现上面的不同了。没错,VSPaste所引用的版本是1.0.0.0,而Windows Live Writer所使用的是1.1.0.0,而且是平台是x86。这也解释了为什么第一次运行时提示试图加载格式不正确的程序。

既然已经知道了问题,那解决起来就简单了。这个时候需要用到程序集重定向,在应用程序配置文件中指定程序集绑定,如下图:

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

运行结果

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

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

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

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

从中可以发现,在生成HTML之前,复制出来的RTF已经不正确了。

再次运行结果

在写字板中模拟相应代码,效果如下图:

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

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

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

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

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

从中可以发现,VSPaste并没有出错。

进一步查找原因

从前面的实验可以发现,VSPaste并没有出错,从VS中复制出来的代码已经丢失了RTF格式信息。那么问题究竟会出现在哪里?我以前在VS2015中使用VSPaste都没有问题啊。这时候我有个猜想,如果VS没有出问题,那么很大可能就是哪个插件坑爹了。

查找问题插件

在VS中选择工具—>扩展和更新以打开插件列表,通过二分法来禁用插件以查看问题是否解决(禁用后需要重启VS)。很快,就找到了罪魁祸首,就是下面这货:

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

查找问题功能

我们找到问题插件后可以就此为止了么?当然可以。但是对于自己来说,总是想打破砂锅问到底。点击上图中右边的详细信息,可以了解到更多的Productivity Power Tools 2015信息。从中可以了解到它的各项功能,也知道了每项功能都可以进行开关。

在VS中选择工具—>选项,并在窗体左边的树状控件中选择Productivity Power Tools。如下图:

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

在前面了解Productivity Power Tools的功能中,我就已经有怀疑的对象了,就是HTML Copy。尝试将其关闭以查看问题是否解决(禁用后需要重启VS)。经过试验,发现果然是该项功能引发的问题。

还能不能进一步查找问题根源?答案是可以。如果多留意一下Productivity Power Tools 2015的详细信息,就会发现,在该页面右上部分有一个到GitHub的链接。嗯,项目还是微软的,不知为何为出现这种问题。

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

源码调试

下载代码

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

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

查找问题代码

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

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

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

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

再查看GenerateClipboardData的定义:

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历
再继续查找,可以发现htmlBuilderService和rtfBuilderService都是通过MEF导入的。

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历
在生成html和rtf的代码后面打一个断点,开始调试。在新运行的VS实例中打开工程,复制代码。在断点处查看,可以发现生成的rtf已经丢失了格式信息,而html仍然保留有格式信息。

那么这个rtfBuilderService究竟是何方神圣?在监视窗口查看详细信息:

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

实现自己的RtfBuilderService


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

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

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

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

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

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

分析RtfBuilderService代码

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

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历
再查看RtfBuilder内部的GenerateRtf方法

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历
先查看GenerateBody方法,发现其主要是通过分析TextRunProperties的属性来生成rtf的:

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历
而文本属性来源于GetClassificationSpans:

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

调试RtfBuilderService

在GenerateBody方法获取TextRunProperties后面打一个断点,开始调试。在新运行的VS实例中打开工程,复制代码。在断点处查看文本属性的颜色,发现只进入一次断点,且文本前景色为白色,背景色为黑色。

再次复制代码,在监视窗口处查看current的属性信息。其只有的ClassificationType和Span属性。在Span属性上可以看出它的内容仿佛是我们复制的代码,尝试看是否有获取文本的方法,一查,还真有:

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

对照监视窗口的各项值,可以发现,此时就已经丢失了所有的格式信息。由于我们的ClassificationType为空,所以始终返回的是默认文本属性。

在GetClassificationSpans第一行打一个断点,进行单步调试,可以发现一些信息。调用该方法的cancel参数为空,而GetClassificationSpans返回的列表中无条目,所以总会调用ClassificationType参数为空的NullableClassificationSpan的构造函数。接下来根据调用堆栈一层一层的往上查看,发现在最开始在GenerateClipboardData方法调用GenerateRtf时就已经决定了cancel为空。

这可怎么办,线索又断了,真的是无路可走了么?不,我们还有一条路!这个工程不是有生成HTML的代码么,它不是没丢失格式的嘛,去参考一下呗。

参考生成HTML的代码

经过一番查找,发现了在生成HTML代码中与RtfBuilderService中GetClassificationSpans方法功能类似的代码,连名字都一样。

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

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

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历
发现这次调用GetAllClassificationSpans带了一个CancellationToken的参数,而生成WaitContext的IWaitIndicator来源于MEF导入。

怎么样,是不是山重水复疑无路,柳暗花明又一村?

完善RtfBuilderService

依照HTM生成部分依样画葫芦,如下图,红色部分表示新增代码:

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

经过调试测试,发现生成的RTF已经包含正确的格式信息了。

好了,问题已经解决了,我们到此为止了么?是否还可以做些其它什么?

另一种解决方案

让我们再次回到RtfBuilderService,为什么它会有那么多的重载?

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历
查看实现的接口,发现除了实现IRtfBuilderService外,还实现了IRtfBuilderService2。两个接口的定义对比:

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历
一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历

不难发现,IRtfBuilderService2是在IRtfBuilderService每个方法后面加上了一个CancellationToken重载。

所以,我们可以不用新增加类,直接将导入的IRtfBuilderService类型改为IRtfBuilderService2,同时在生成rtf的地方传入CancelToken以调用对应的接口方法。

注意事项

微软建议的在调试插件时需要Productivity Power Tools卸载了。我在研究问题时是卸载了的,但是在写这边博客时没有卸载,貌似也没什么问题。

另外,因为我是先研究的问题,后写的博客,可能有些细节忘记了或者没有写上,有心研究的话可以联系我。

结语

因为事情比较多,断断续续这么久,终于把这篇博客写完了。之所以写这么多,主要是想分享下我解决问题的过程和思路。作为程序员,我个人觉得还是有一些探索精神好一些,很多时候,路不是想象的那么难走。

最后,我给微软提了个issue……

一次查找Windows Live Writer的VSPaste插件丢失RTF格式信息的经历