65.9K
CodeProject 正在变化。 阅读更多。
Home

使用MSHTML加载和解析HTML。第三种方法。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.74/5 (17投票s)

2002年2月1日

CPOL

4分钟阅读

viewsIcon

1017802

downloadIcon

5818

讲解如何从内存中加载HTML代码并使用Microsoft技术进行解析

引言

首先,我来解释一下为什么我将这篇文章命名为“第三种方法”。我已经在CodeGuru上看到过类似的文章,介绍了如何从内存中加载和解析HTML文件。你可能会问,那为什么我还要写另一篇指南呢?嗯,下面我将展示我在这些方法中发现的优点和缺点。

第一种方法,也是在MSDN上展示的方法,是使用IStream接口加载HTML代码。你可以点击这里阅读相关文章。如果你只想将新代码插入到你的文档中,那么绝对应该使用这种方法。但是,如果你尝试在加载HTML后从文档中获取标签,你会一无所获。仅仅因为它们仍在解析中,你必须创建一个OnDocumentComplete处理程序,然后才能开始查看你的文档。

当我意识到这一点后,我开始寻找另一种方法,该方法可以在提交代码后立即给我文档。是的,我找到了!你可以在CodeGuru上的Asher Kobin一篇精彩的文章中找到它。它使用了一个名为IMarkupServices的新接口,该接口随MS Internet Explorer 5.0一起引入。我选择了这段代码,并在此基础上进行了自己的修改并开始使用它……但突然我发现,当我将文档保存到磁盘时,BODY标签没有属性!我花了一整天的时间来解决这个问题,试图让它正常工作,但是……徒劳无功。当你从内存中加载HTML代码到文档时,BODY标签的所有属性都会丢失。仍然不知道为什么会发生这种情况,如果有人能告诉我,我将非常感激。

因此,我又回到了MSDN,并找到了另一种(第三种)加载和解析HTML的方法。我太高兴了,所以我决定将我的第一篇CodeProject文章写在这个上面,就是你现在正在阅读的这篇:)

代码

对于那些不想阅读整篇文章的高级程序员,我将给出一个提示:加载HTML代码是通过IHTMLDocument2接口的write()方法实现的。

现在我将从头开始解释如何做到这一点。

头文件和导入

在此,我将假设你有一个标准的MFC应用程序(例如对话框、SDI或MDI应用程序)。首先,你必须初始化COM,因为我们将使用MSHTML COM接口。这可以在你的应用程序的InitInstance()函数中完成。另外,请记住在你的ExitInstance()中取消初始化COM。

BOOL CYourApp::InitInstance()
{
	CoInitialize(NULL);
	...
}
int CYourApp::ExitInstance() 
{
	...
	CoUninitialize();
	return CWinApp::ExitInstance();
}

现在,在你将要使用MSHTML接口的文件中,包含mshtml.hcomdef.h(用于智能指针)并导入mshtml.tlb

#include <comdef.h>
#include <mshtml.h>
#pragma warning(disable : 4146)	//see Q231931 for explaintation
#import <mshtml.tlb> no_auto_exclude

我从哪里获取文档?

现在让我们获取一个指向IHTMLDocument接口的指针。你将如何获得它?这取决于你已经拥有什么:)如果你在应用程序中托管了一个WebBrowser控件或使用了CHtmlView,你可以调用GetDocument()函数并将返回值存储在你的指针中,但我将解释如何获得一个“独立”的文档,该文档未附加到任何控件或视图。这可以通过简单地调用CoCreateInstance()函数来完成。

MSHTML::IHTMLDocument2Ptr pDoc;
HRESULT hr = CoCreateInstance(CLSID_HTMLDocument, NULL, CLSCTX_INPROC_SERVER, 
                              IID_IHTMLDocument2, (void**)&pDoc);

验证你是否有一个有效的指针(不是NULL),然后继续。

转换你的HTML代码

我将假设你想要加载的所有HTML代码都存在于一个名为lpszHTMLCode的变量中。这可以是一个CString或任何其他缓冲区,例如从磁盘文件加载的。在将其传递给MSHTML之前,我们需要对其进行准备。问题在于,我们将使用的MSHTML函数只接受SAFEARRAY作为参数。所以,让我们将我们的字符串转换为SAFEARRAY

SAFEARRAY* psa = SafeArrayCreateVector(VT_VARIANT, 0, 1);
VARIANT *param;
bstr_t bsData = (LPCTSTR)lpszHTMLCode;
hr =  SafeArrayAccessData(psa, (LPVOID*)&param);
param->vt = VT_BSTR;
param->bstrVal = (BSTR)bsData;

最后一步

现在我们准备好将我们的SAFEARRAY传递给write()函数。这两行代码将为你完成所有脏乱的解析工作。

hr = pDoc->write(psa);	//write your buffer
hr = pDoc->close();	//and closes the document, "applying" your code  

//Don't forget to free the SAFEARRAY!
SafeArrayDestroy(psa);

当然,请记住检查每一步,以确保你的程序永远不会崩溃,我省略了这一点是为了保持代码的简洁。

现在,经过所有这些工作,你将获得一个指向IHTMLDocument2接口的指针,该接口提供了许多功能,例如获取特定标签、搜索、插入、替换、删除标签,就像你在JavaScript中所做的那样。

请记住,如果你使用智能指针(就像我在这里所做的那样),则无需调用Release()函数,对象将自动释放。

"about:blank" bug 变通方法

嗯,因为我们的文档接口没有附加网站,所以所有相对文档的链接(href、src)在尝试使用IHTMLAnchorElement::href属性时都将以“about:blank”开头。要获取与HTML源文件完全相同的链接,可以使用IHTMLElement接口和一个名为getAttribute的有用函数。只需记住,该函数的第二个参数应为2,这将告诉解析器按原样返回文本。

当然,你应该以相同的方式处理IMG、LINK和其他标签。示例项目也已更新以包含此修复。你可以下载它并查看我是如何做到的。

参考文献

Ahser Kobin关于使用IMarkupServices进行解析的文章(CodeGuru)
从流加载HTML(MSDN)
MSHTML参考(MSDN)
IHTMLDocument2参考(MSDN)

© . All rights reserved.