揭开Outlook Express编辑器的奥秘

** 【前言】 **

Outlook Express 是一款大家比较熟悉的邮件工具,其 HTML 编辑器一直是众多程序员竞相模仿的目标。作者最近在一个项目的开发中,开始接触 HTML 编辑器的设计,并遇到了很多的难题。目前网络上关于 IE 编程的文章中,涉及 MSHTML 编辑器的部分,又大多集中在 VC 领域,用 Delphi 作为解决方案的少之又少。在经过一番艰难的摸索之后,作者积累了一些成功的经验。并撰成此文,希望与大家共同探讨。

注: 本文将涉及到 COM 编程,由于 COM 的复杂性,不免会有晦涩难懂之嫌。为了让阅读不至于成为一种折磨,作者将尝试另一种写作模式。文章将随着一个叫做 W 的程序员的编程思路展开,以通俗易懂的叙事方式带领读者一起探讨在 MSHTML 编辑器的开发过程中可能遇到的一些棘手问题。对于某些需要强调的关键术语,文中将适时的给出注解,以便读者更好的领会。

【学习目标】

通过本文的阅读,读者将可以学习到以下内容:

l 掌握 TWebBrowser 控件的用法;

l 理解 IHTMLDocument2 和 IDocHostUIHandle 接口;

l 探讨在 MSHTML 中如何加载字符流;

l 找回在 ** MSHTML ** 编辑器中丢失的回车键; **

**

l 实现工具栏的自动感应;

l 自定义 ** MSHTML ** 编辑器强大的粘贴功能; **

**

本文假定读者已经具备初步的 COM 知识和 Delphi 接口的编程经验,如果您需要对 COM 和接口知识作进一步的深入了解,请参考其它相关文章。

** 【关键字】

**

TWebBrowser 、 MSHTML 、自动化对象、 IHTMLDocument2 、 IDocHostUIHandler 、

FilterDataObject 、 IDataObject 、剪贴板、粘贴

**

**

** 【正文】

**

**

**

一个笑话的启示 **

**

_ _ 在一次程序员大会上,主持人为了活跃气氛,做了个小游戏。他问台下的程序员:如果有谁在小的时候拆过闹钟的请举手。台下的程序员们全都举起了手。主持人又问:那么又有谁后来把闹钟装回去的请举手。举起手来的程序员们又都把手放下了。 _

_

这个笑话从侧面说明了一个问题,追根求源正是大多数程序员的天性。缺少追根求源的精神,软件设计就会缺少创新的动力。也正是由于有了追根求源的精神,越来越多的软件新手跨越了初期的彷徨,走上了软件高手的道路。

未来的一天,程序员 W 所在的软件公司接到一个信息管理系统的设计项目。由于最近 W 刚参加完公司组织的为期一周的 COM 培训,于是项目经理 Y 便把项目中最具挑战的编辑器部分交给他来完成。用户要求实现一个类似 Outlook Express (以下简称 OE )那样所见即所得式的编辑器,并可以支持多种来源的粘贴操作。尽管 W 此前对 OE 编辑器的原理一无所知,但他还是面带微笑并充满自信的接受了挑战。

**揭开 OE ** **编辑器的面纱

**

在以往的使用过程中, W 发现 OE 编辑器确实是一款强大的编辑工具。无论是编辑还是粘贴, OE 编辑器都能完美的实现所见即所得的效果。 OE 编辑器本质上是一款 HTML 编辑器,其中的数据和格式都以 HTML 代码的形式来保存。 W 以前曾研究过网页上的 HTML 编辑器,该编辑器是通过 DHTML 技术来实现的。那么, OE 编辑器和 IE 浏览器之间是否有什么关系呢?

为了搞清楚这个问题, W 调出 VC 的 Spy++ 探个究竟。拖动 Spy++ 那个神奇的雷达指向 OE 编辑窗口, Spy++ 迅速的找到了窗口的类型:“ Internet Explorer_Server ”,这是个 IE 服务器窗口类型,这究竟是什么意思呢?

微软的 IE 浏览器的核心部分是 SHDOCVW.DLL 和 MSHTML.DLL 。从下面的 Internet Explorer 的架构图可以看到, IE 其实只是一个外壳程序,真正的浏览网页、记录历史等工作都是由封装在 SHDOCVW.DLL 中的 WebBrowser Control 来完成的。而 HTML 的解析、脚本引擎、 java 虚拟机、插件宿主等,则由 SHDOCVW.DLL 通过调用 MSHTML.DLL 来完成。通过 SHDOCVW.DLL 提供的丰富接口,网页中的元素可以访问外壳应用程序提供的属性和方法;而通过 MSHTML.DLL 提供的接口,外壳应用程序则反过来可以访问网页中元素的属性、方法、行为、事件等等。

毫无疑问, OE 编辑器正是通过对 WebBrowser 控件和 MSHTML 的封装实现了 HTML 的编辑功能。由于 WebBrowser 属于 ActiveX 控件,所以,利用 Delphi 的 ActiveX 导入向导,可以轻松的实现对 WebBrowser 控件的封装。导入后将在 Delphi 的 Import 文件夹下自动生成两个 TLB 文件: SHDocVw_TLB 和 MSHTML_TLB 。

** Delphi ** 自带的 ** TWebBrowser

**

从 Delphi4 开始, Delphi 就在 Internet 组件面板上提供 TWebBrowser 组件,作为对 WebBrowser 控件的封装。由于 Delphi 的封装并不能保证和最新的 WebBrowser 控件版本相一致,建议 Delphi7 以前的读者先卸载该组件并重新导入 Shdocvw.dll ,以便使用最新的接口功能, **

**

**进入 TWebBrowser ** **的神奇世界

**

感谢 Delphi ,使得一切都变得如此轻松。 W 启动 Delphi ,新建一个项目,在 Internet 组件面板上找到 TWebBrowser 组件,然后拖放到窗体上,并重命名为“ wbEditor ”。由于对 TWebBrowser 组件缺乏了解, W 决定先请教一下公司的 Delphi 高手老 D 。

W :老 D ,你知道 TWebBrowser 组件的用法吗?

D :这个简单。 TWebBrowser 有一个 Document 属性,你看一下,这是个 IDispatch 接口类型的属性。对了, IDispatch 接口你知道吗?

W :(支支吾吾)刚学过,不过没弄懂……

D :简单点说吧。为了给解释型语言——例如 Javascript 脚本语言——提供调用 COM 对象服务的能力,于是出现了 COM 自动化对象。由于解释型语言无法象编译型语言那样实现和 COM 对象的 早期绑定 ,所以, COM 自动化对象便提供了 IDispatch 接口供自动化客户端调用。通过 IDispatch 接口,自动化机制中的客户端就可以动态的调用 COM 自动化对象中的方法了……总之啊, IDispatch 接口是实现 COM 自动化对象 机制的关键。你明白吗?

W :(似懂非懂,不过想想反正以后还可以再学)嗯,知道了。然后呢?

D : OK 。由于我们并不需要自动化机制, IDispatch 接口对我们来说用处不大。但我们可以利用它通过 Delphi 中的 as 运算符查询到其它我们想要的接口。例如, IHTMLDocument2 接口在编程中用的比较多,用它可以实现大多数的 DHTML 功能。

W :哦~ IHTMLDocument2 接口(自言自语)。那如何进入编辑状态呢?

D :答案就在这个 IHTMLDocument2 接口中。这个接口中有一个 disignMode 属性,在运行时置为“ On ”就可以从浏览模式转变为编辑模式了。当然了,前提是必须保证 Document 不能为空。有个简单的办法,在初始化时,通过 TWebBrowser 的 Navigate 方法导航到一个空白页面,有一个 busy 属性可以用来监测是否加载完毕……

W :哦……哦……(一边听一边敲出下面的代码)运行成功!太感谢了。

procedure TForm1.FormCreate(Sender: TObject);

begin

wbEditor.Navigate('about:blank');

while wbEditor.busy do Application.ProcessMessages;

(wbEditor.Document as IHTMLDocument2).designMode := 'On';

end;

在老 D 的帮助下, W 对 TWebBrowser 的用法有了一个初步的了解。很显然,接口在 TWebBrowser 的编程中至关重要。此时,为了加深对接口的了解, W 决定对 IHTMLDocument2 接口做一个深入的了解。

小知识: **

**

在执行 TWebBrowser 的某个方法以进行某些期望的操作如 ExecWB 等时候,可能会碰到如“试图激活未注册的丢失目标”或“ OLE 对象未注册”等错误提示,或者并没有任何出错信息但却得不到希望的结果。这是因为 TWebBrowser 本身是一个 OLE 类型的 COM 组件,你需要在使用 TWebBrowser 前对 OLE 进行一些初始化工作,这个工作可以放到单元的 initialization 和 finalization 段中来完成。

{uses ActiveX}

initialization

OleInitialize(nil);

finalization

try

OleUninitialize;

except

end;

**浅谈 IHTMLDocument2 ** **接口

**

**

**

**** 背景知识: **

**

** ** 为了使用 IHTMLDocument2 接口,你必须包含 MSHTML.pas 单元(如果你采用 ActiveX 导入的方式,这个单元就是 MSHTML_TLB.pas )

**

**

MSHTML 控件的 Document 对象实现了包括 IHTMLDocument2 接口在内的多个接口。其中 Document 对象的常用属性、子集合、方法等都集中在 IHTMLDocument2 接口中。通过 IHTMLDocument2 接口,可以利用 DHTML 的强大功能对网页对象进行各种增删操作和属性的动态改变。

在 IHTMLDocument2 的接口方法中,有一个特殊的方法引起了 W 的注意,这就是 execCommand 方法。很显然,这个方法与命令的调用有关。 execCommand 方法声明如下:

// 对当前文档、选定内容或指定范围执行特定的操作

HRESULT execCommand(
BSTR cmdID,

VARIANT_BOOL showUI,

VARIANT value,

VARIANT_BOOL *pfRet

);

其中, cmdID 参数定义了大多数常用的格式化命令。这样, OE 编辑器工具栏上的大多数编辑功能完全可以通过这个方法来实现。为了验证自己的想法, W 在窗体上新建一个按钮,并写了一些测试代码。运行结果完全符合 W 的猜测。

procedure TForm1.Button1Click(Sender: TObject);

begin

with wbEditor.Document as IHTMLDocument2 do

begin

// 改变字体的前景色

execCommand('ForeColor', False, 'red');

// 改变字体的粗细

execCommand('Bold', False, 1);

// 打开插入图片对话框,插入图片

execCommand('InsertImage', True, '');

// 文本居中

execCommand('JustifyCenter', False, 0);

// 执行撤销上一步操作

execCommand('Undo', False, 0);

end;

end;

注: 为了确保 execCommand 调用成功,你必须保证当前页面已经完全加载。

如果考虑效率问题, IOleCommandTarget::Exec 方法则可以提供更好的性能。事实上, execCommand 命令正是对 IOleCommandTarget::Exec 方法的一个封装,其目的主要是为了给 Script 类型的语言提供一个方便的调用入口。通过以下示例学习如何获得对 IOleCommandTarget 接口的访问并调用 Exec 方法:

** IOleCommandTarget::Exec ** 方法 **

**

procedure TForm1.Button2Click(Sender: TObject);

const

CGID_MSHTML: TGUID = '{DE4BA900-59CA-11CF-9592-444553540000}';

begin

(wbEditor.Document as IOleCommandTarget).Exec(

@CGID_MSHTML,

IDM_BOLD, //Bold 命令的 ID ,请参考 MSDN 有关帮助

OLECMDEXECOPT_DODEFAULT,

0,

POlevariant(nil)^);

end;

**

**

**再谈 Document ** **对象的初始化和赋值

**

在 IHTMLDocument2 接口中, Document 对象是实现 DHTML 模型的核心。要实现对 Document 对象的任何操作,必须要等到 Document 对象的初始化操作结束之后才能进行。通过 Navigate 方法,可以实现对 Document 对象的初始化。 需要注意的是, ** Navigate ** 方法并不能识别常规方式下的相对路径,如果需要导航到某个文件,必须指定绝对路径

procedure TForm1.InitDocument;

begin

wbEditor.Navigate('about:blank');

while wbEditor.ReadyState <> READYSTATE_COMPLETE do

Application.ProcessMessages;

end;

在 Document 文档对象完成初始化之后,就可以对 Document 对象进行赋值。赋值对象既可以是字符串,也可以是内存中的数据流。通过 Document 对象的 IPersistStreamInit 接口,就可以实现数据流的加载。

function TForm1.LoadFromStream(const AStream: TStream): HRESULT;

begin

if not Assigned(wbEditor.Document) then

InitDocument;

AStream.seek(0, 0);

Result := (wbEditor.Document as IPersistStreamInit).Load(TStreamadapter.Create(AStream));

end;

利用数据流的加载方法,可以进一步的实现字符串的加载。

function TForm1.LoadFromStrings(const AStrings: TStrings): HRESULT;

var

M: TMemoryStream;

begin

M := TMemoryStream.Create;

try

AStrings.SaveToStream(M);

Result := LoadFromStream(M);

except

Result := S_FALSE;

end;

M.free;

end;

**

**

** 找回被编辑器吃掉的回车

**

有一个问题从一开始就引起了 W 的注意,那就是 MSHTML 编辑器竟然经常不响应回车事件。也就是说,当在编辑器中按下回车时不能产生一个换行——编辑器面对回车按键毫无反应,就好像回车键被它吃掉一样。类似的,象 TAB 、 Delete 、 BACKSPACE 、→、←等快捷键上也会出现这种情况。这真是一件令人匪夷所思的事。

从本质上说, TWebBrowser 是一个特殊的 OLE 控件。虽然 Delphi 在封装过程中使它继承了 TWinControl ,但它似乎并没有由此取得 TWinControl 的自动获得焦点的能力。看来,极有可能是由于 Delphi 的 VCL 消息处理机制同 OLE 之间存在某种冲突,导致了 OLE 自己吃掉了部分键盘消息。

既然如此, W 实现想不出什么更好的办法。要解决这个问题,一个比较合理的解决方案就是直接捕获并处理 Windows 的消息映射。于是,他尝试写了一个消息处理方法并把这个方法句柄指定给了 Application.OnMessage 事件。这样,丢失的回车键又回来了。

procedure TForm1.IEMessageHandler(var Msg: TMsg; var Handled: Boolean);

const

StdKeys = [VK_TAB, VK_RETURN]; { 标准键 }

ExtKeys = [VK_DELETE, VK_BACK, VK_LEFT, VK_RIGHT]; { 扩展键 }

fExtended = $01000000; { 扩展键标志 }

begin

Handled := False;

with Msg do

if ((Message >= WM_KEYFIRST) and (Message <= WM_KEYLAST)) and

((wParam in StdKeys) or (GetKeyState(VK_CONTROL) < 0) or

(wParam in ExtKeys) and ((lParam and fExtended) = fExtended)) then

try

if IsChild(wbEditor.Handle, hWnd) then

{ 处理所有的浏览器相关消息 }

begin

with wbEditor.Application as IOleInPlaceActiveObject do

Handled := TranslateAccelerator(Msg) = S_OK;

if not Handled then

begin

Handled := True;

TranslateMessage(Msg);

DispatchMessage(Msg);

end;

end;

except

end;

end; // IEMessageHandler

procedure TForm1.FormCreate(Sender: TObject);

begin

……

Application.OnMessage := IEMessageHandler;

end;

** 工具栏的动态感应

**

工具栏已经设计完毕,下一个问题是,如何让工具栏能自动反映编辑器当前选定部分的编辑状态。也就是说,如果当前文本是粗体居中,那么粗体和居中按钮应该处于选中状态。很显然,只要能写出编辑器的 OnDisplayChanged 事件,一切都将迎刃而解。那么必须得有一个接口方法,当编辑器状态发生改变时,在这个方法中调用 OnDisplayChanged 事件即可。在 MSDN 的帮助下, W 找到了 IDocHostUIHandle 接口,并锁定了其中的 UpdateUI 方法。问题思路已经很清晰:当 MSHTML 组件的状态发生改变时,该接口中的 UpdateUI 方法将被调用。

IDocHostUIHandle **工作原理:

**

当MSHTML组件被加载到内存并执行初始化时,MSHTML开始在宿主客户端查询一个叫IDocHostUIHandle的接口实现。如果找到这样的接口实现,MSHTML将在其运行期间,根据需要动态的调用IDocHostUIHandle中的对应方法。

通过对IDocHostUIHandle接口的实现,MSHTML组件将能直接和用户接口界面(UI)进行通信。这样,宿主程序将有机会修改用户界面中的菜单、工具条、以及其它的用户接口元素。

为了实现 UpdateUI 方法, W 决定从 TWebBrowser 继承并产生一个新的组件,新组件将实现对 IDocHostUIHandle 的封装,新组件的名字就叫 TWebEditor 。在 UpdateUI 的实现中将调用 OnDisplayChanged 事件句柄—— Delphi 事件的实现思想实在太妙了。

TWebEditor = class(TWebBrowser, IDocHostUIHandle)

……

private

FOnDisplayChanged: TNotifyEvent; //声明私有事件变量

function UpdateUI: HRESULT; stdcall;

publish

property OnDisplayChanged: TNotifyEvent

read FOnDisplayChanged write FOnDisplayChanged; //声明属性事件

end;

function TEmbeddedED.UpdateUI: HRESULT;

begin

//在编辑器状态改变时,通知宿主程序

if Assigned(FOnDisplayChanged) then

FOnDisplayChanged(self);

Result := S_OK; //表示已经做了处理

end;

编译并安装 TWebEditor 组件到 Internet 面板,然后替换 TWebBrowser 组件。 OK ,双击 OnDisplayChanged 事件并编写代码吧。

** 自定义上下文菜单

**

很多时候,上下文菜单在编辑器的使用中可以为用户提供更方便的功能调用。但 MSHTML 编辑器提供的上下文菜单并不是 W 所希望的。所以,为了让自己的编辑器看起来更专业一点, W 需要一个自己的上下文菜单。有了上面对 IDocHostUIHandle 编程的经历, W 很快发现 ShowContextMenu 接口方法正是自己所需要的。 W 设计了一个 PopupMenu 菜单,重命名为 ppMenu ,然后在新 TWebEditor 组件中添加以下代码:

……

//声明一个显示上下文事件类型

TShowContextMenuEvent = function(const dwID: DWORD; const ppt: PPOINT;

const pcmdtReserved: IUnknown; const pdispReserved: IDispatch): HRESULT of object;

TWebEditor = class(TWebBrowser, IDocHostUIHandle)

……

private

FOnShowContextMenu: TShowContextMenuEvent; //声明响应上下文菜单私有事件变量

……

function ShowContextMenu(const dwID: DWORD; const ppt: PPOINT;

const pcmdtReserved: IUnknown;

const pdispReserved: IDispatch): HRESULT; stdcall;

publish

property OnShowContextMenu: TShowContextMenuEvent

read FOnShowContextmenu write FOnShowContextmenu; //声明属性事件

end;

function TWebEditor.ShowContextMenu(const dwID: DWORD; const ppt: PPOINT;

const pcmdtReserved: IUnknown; const pdispReserved: IDispatch): HRESULT;

begin

//在MSHTML组件企图显示上下文菜单时,通知宿主程序

if Assigned(FOnShowContextMenu) then

RESULT := FOnShowContextMenu (dwID, ppt, pcmdtreserved, pdispreserved)

else

RESULT := S_FALSE;

end;

**

**

** [ ** **提示:组件需要重新编译并安装(下同) ]

**

在响应 wbEditor 的 OnShowContextMenu 事件代码中,返回 S_OK 将告诉 MSHTML 组件你将使用自定义菜单,否则返回 S_FALSE 。

function TForm1.wbEditorShowContextMenu(const dwID: Cardinal;

const ppt: PPoint; const pcmdtReserved: IInterface;

const pdispReserved: IDispatch): HRESULT;

begin

ppMenu.Popup(ppt.X, ppt.Y); //显示自定义菜单

Result := S_OK; //告诉MSHTML组件将显示自定义菜单

end;

**

**

** 剪贴板中的玄机

**

OE 编辑器支持多来源的粘贴功能给 W 留下的印象很深刻,所以, W 对 MSHTML 编辑器的粘贴能力同样寄予了厚望。但不管如何,他需要做一些测试以验证自己的想法。他设想了一些测试来源:网页、 Word 文档、 Excel 表格、图片……

网页的粘贴很顺利,几乎保持原貌; Excel 表格也完全正常;图片的粘贴则完全不被编辑器支持——不过可以利用插入图片功能来替代——然而, Word 的粘贴却遇到了一些麻烦:如果 Word 中包含图片或自定义对象,这部分内容将无法显示。是 Word 的问题吗? W 回到 OE 中,粘贴同样的内容,显示正常。看来,问题还在 MSHTML 编辑器中。

通过上下文菜单, W 打开源文件查看。 Word 粘贴过来的 HTML 代码中,充斥了大量的 XML 代码,图片和对象则被一些奇怪的 XML 标签所包围,而同样的内容在 OE 编辑器中却显示为正常的 HTML 代码。怎么回事? W 脑子第一时间蹦出一个很 COOL 的想法:跟踪剪贴板。

根据剪贴板的原理,在获取剪贴板内容之前,必须指定要获取内容的格式。由于剪贴板中的数据可能存在多种格式,所以有必要对剪贴板的格式类型先做一些了解。 W 写下了以下的测试代码:

procedure TForm1.Button3Click(Sender: TObject);

var

i: integer;

Buffer: PChar;

s: string;

begin

Memo1.Lines.Clear; // 增加了一个 Memo 控件来跟踪数据

with TClipboard.Create do // 利用 TClipboard 追踪剪贴板

begin

GetMem(Buffer, 20);

for i:=0 to FormatCount - 1 do

begin

GetClipboardFormatName(Formats[i], Buffer, 20);

s := StrPas(Buffer);

Memo1.Lines.Add(Format('%s:%d', [s, Formats[i]]));

end;

FreeMem(Buffer);

Free;

end;

end;

点击 Button3 ,在 Memo1 文本框中显示出以下的内容:

DataObject:49161

Object Descriptor:49166

Rich Text Format:49312

** HTML Format:49394

**

HTML Format:14

HTML Format:3

PNG:49672

GIF:49536

JFIF:49538

……

很明显,第 4 行的“ HTML Format: 49394 ” 应该就是 HTML 编辑器真正需要的格式。由于“ HTML Format ”并不是剪贴板默认支持的格式,所以 W 需要使用 API 函数 RegisterClipboardFormat 先进行注册。

procedure TForm1.Button4Click(Sender: TObject);

var

s: string;

hMem: DWORD;

CF_HTML: DWORD; // 声明一个 CF_HTML 剪贴板格式

txtPtr: PChar;

begin

CF_HTML := RegisterClipboardFormat('HTML Format'); // 注册 HTML Format 格式

with TClipboard.Create do

begin

hMem := GetAsHandle(CF_HTML);

txtPtr := GlobalLock(hMem);

s := StrPas(txtPtr);

GlobalUnlock(hMem);

Memo1.Lines.Add(s);

Free;

end;

end;

这回终于水落石出。在 Memo1 中, W 见到了 Word 文档被拷贝以后在剪贴板中以 HTML 格式存在的真实内容:

Version:1.0

StartHTML:0000000105

EndHTML:0000005323

StartFragment:0000003873

EndFragment:0000005283

 1<html xmlns="http://www.w3.org/TR/REC-html40" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word">
 2
 3…… 
 4
 5<body ……="">
 6
 7** <!--StartFragment--> ** <span ……=""><!--[if gte vml 1]>
 8
 9<v:shapetype  ……  >
10
11<v:imagedata src="file:///C:\DOCUME~1\tttk\LOCALS~1\Temp\msohtml1\01\clip_image001.gif" 
12
13o:title="el2"/> …… 
14
15</v:shape><![endif]--><?if !vml?> ** <img **="" **v:shapes="_x0000_i1025" height="128" src="file:///C:\DOCUME~1\tttk\LOCALS~1\Temp\msohtml1\01\clip_image001.gif" width="128"/> ** <?endif?></span> ** <!--EndFragment--> **
16
17…… 
18
19(为了节省篇幅,作者删去了大量无用的信息) 
20
21W  欣喜的发现,  HTML  中熟悉的  <img/> 标签出现在剪贴板中,虽然在最后的粘贴结果中没有看到  img  元素,那也一定是里面的  <?if?> 语句捣的鬼。现在只要把里面的  HTML  部分提取出来不就行了吗?通过规则表达式的帮助,一切都轻松搞定! 
22
23procedure TForm1.FilterData(var S: string); 
24
25var 
26
27isOffice: Boolean; 
28
29begin 
30
31with TRegExpr.Create do 
32
33begin 
34
35isOffice := ExecRegExpr('(?i)xmlns:o="urn:schemas-microsoft-com:office:office"', S); 
36
37Expression := '(?i)<!--StartFragment-->(.*)<!--EndFragment-->'; 
38
39if Exec(S) then S := Match[1]; 
40
41if isOffice then //trip office document 
42
43begin 
44
45S := ReplaceRegExpr('(?i)<!--[^>]+?>.+?<[^>]+?-->', S, ''); 
46
47S := ReplaceRegExpr('(?i)&lt;[^\\]|&gt;]*\\[[if|endif][^&gt;]+&gt;', S, ''); 
48
49S := ReplaceRegExpr('(?i)<!--?[v|o|w]:[^-->]+&gt;', S, ''); 
50
51S := ReplaceRegExpr('(?i)[\r|\n]{2,}', S, ' '); 
52
53end; 
54
55end; 
56
57S := UTF8Decode(S); 
58
59end; 
60
61**注:** ** TRegExpr  ** **类是第三方开源的规则表达式类,感兴趣的读者请到** ** http://regexpstudio.com/  ** **下载试用。** **
62
63**
64
65** 如何更新修改后的数据? 
66
67**
68
69W  没高兴多久。因为他发现,虽然掌握了剪贴板的实际内容,但他依然不知道如何把修改后的数据更新到编辑器中。因为编辑器没有直接的粘贴方法可以调用,  W  首先想到的是把修改后的数据重新赋值给剪贴板。但如果用户希望多次粘贴,这个方法将显然行不通。如果有这样一个粘贴事件,在这个粘贴事件中提供  In  和  Out  两种类型的参数,问题不就解决了吗?那么如何实现这个粘贴事件呢?答案依然在  IDocHostUIHandle  接口中,  W  找到了  FilterDataObject  方法,该方法恰好有两个  In  和  Out  类型的参数: 
70
71HRESULT FilterDataObject(   
72IDataObject *pDO, 
73
74IDataObject **ppDORet 
75
76); 
77
78当粘贴操作发生时,  MSHTML  组件将调用  IDocHostUIHandle  接口的  FilterDataObject  方法,并把内存中的数据对象通过  pDO  参数传入,如果该函数返回  S_OK  并且  ppDORet  参数不为  NULL  ,  MSHTML  组件将尝试从  ppDORet  接口读出修改后的数据。根据这个想法,  W  很快的实现了下面的  OnPaste  事件: 
79
80//声明粘贴事件类型 
81
82TPasteEvent = function(const pDO: IDataObject; 
83
84&amp;nbs</body></html>
Published At
Categories with Web编程
Tagged with
comments powered by Disqus