zoukankan      html  css  js  c++  java
  • 一次查找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复制存在丢失格式的情况,于是就来研究这个问题了。

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

    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

  • 相关阅读:
    Mysql 关于 FOUND_ROWS() 和 ROW_COUNT() 函数
    MySQL数据库远程连接
    MySQL数据库远程连接
    Linux 下不得不说的那些快捷键
    Linux 下不得不说的那些快捷键
    linux实时查看更新日志命令
    linux实时查看更新日志命令
    the current differences between MyISAM and InnoDB storage engines
    Difference Between InnoDb and MyISAM(个人觉着是好文章,简单易懂,推荐看)
    MyISAM to InnoDB: Why and How(MYSQL官方译文)
  • 原文地址:https://www.cnblogs.com/yiyan127/p/FindVSPasteLoseRTFInformation.html
Copyright © 2011-2022 走看看