背景:
基于某些不着边际想法,只为取得HTML页面上的所有“URL”和“文本”,其它的内容都不在关心之列。
问题:
对于“文本”搜索,如果搜索了除英文以外的语言还好说些,如果要搜索的内容是英文本,
那么就难以区分是“标记”还是“本文”了。对于“URL”的搜索,因为“标记”就是英文,
这样就绕回到“对于‘文本’搜索”。另外字母的大小写,被转义的字符,引号,尖括号,都得处理。
例如:
<a href="http://www.csdn.net" >csdn</a>
<script src="http://csdnimg.cn/xxxxxxxx.js" type="text/javascript"></script>
想要搜索“csdn”这个字符串,直接以字符串遍历的法能搜索到3个,其实呢只希望搜索到1个。
例如:
<a href="http://bbs.csdn.net" >论坛</a>
<a href="http://bbs.csdn.net" >论 坛</a>
<a href="http://bbs.csdn.net" >论 坛</a>
想要搜索“论坛”这个字符串,按语义上讲,希望在搜索时能搜到3个。
但直接以字符串遍历的法能搜到1个,原因在于加了“空格”后的字符串,
计算机不知道对于人来讲意思并没有变。
总结:
1:直接搜索特定字符串,不多了就是少了。
2:尝试过MS的COM库,功能强大且齐全,但耗费的资源也相当多。
3:耳熟能详的搜索引擎也跑过几个回合,因没有耐心翻遍所有网页只好放弃。
结论:
只能把HTML页面完整的解析完毕才能达找到想到的东西,尽管不是全部,但情况要好很多。
方法:
HTML语句结构是:<a href="http://www.csdn.net" >aaaa</a> 或 <link href="/favicon.ico" />
等等一连串类似的语句组成,并且只有嵌套没有循环(脚本只能算上面提到的“文本”)。
分界符(这个词本人自己的称呼)使用的是“ <>""''=空格 ”,把两个分界符之间的内容看作一个链表节点,
“标记”a与“标记”/a是“父”节点与“子”节点的关系,“标记”a与“标记”href是“兄弟”节点的关系。
这样的好处是不用关心“标记”含义,就可以把整个页面解析成一个二维链表。
纵向可以遍历“标记”和“文本”,横向可以找到“文本”对应用“URL”。
当然实际情况要复杂的多,种种异常情况都要考虑。如:转意字符,脚本中的括号对称验证等等,
最糟糕是碰到错误的语法,或者根本就不是HTML页面(这个就不属性本文说明范围了)。
//以上内容于 2011-12-17 18:01 添加/
1:较“HTML解析-第一版(C/C++)” 减少了内存拷贝,速度相对提高很多。
2:代码在VS2008下测试通过。#define _UNICODE #define _WIN32_WINNT 0x0600
3:解析方法:类似于构建一个map表(STL模板库里的map不利于阅读,可以参考MFC类库的CMap),最终组成一个二维的单向链表。
4:CHtmlObject 类负责解析HTML“标记”和“属性”。
//CHtmlObject.h//
#pragma once
/*****************************************************************************************************************
created: 2011/12/03
author: hmm7e (hmm7e_z@126.com)
*****************************************************************************************************************/
class CHtmlObject
{
public:
//
static BOOL IsSpace(TCHAR tcLetter);
protected:
struct tagNode
{
LPCTSTR s_pszKey;
LPCTSTR s_pszValue;
struct tagNode * s_pstRight; //attribute of tag
struct tagNode * s_pstNext; //next tag
};
public:
CHtmlObject(void);
virtual ~CHtmlObject(void);
//
enum {CHARSET_UTF8,CHARSET_UNICODE,CHARSET_MULTIBYTE}TextCharset;
protected:
//
tagNode * InnerAllocNode();
void InnerFreeNode(tagNode * lpstNode);
void InnerLinkNextNode(tagNode * lpstNode);
void InnerLinkRightNode(tagNode * lpstTagNode,tagNode * lpstNode);
void InnerCleanupNode();
void InnerCleanupRightNode(tagNode * lpstNode);
public:
//
void AutoTakeSnapshot(PBYTE lpszString,UINT nStringLen);
void TakeSnapshot(PBYTE lpszString,UINT nStringLen,UINT nFromCharset );
void DeleteSnapshot();
//
void Parse();
private:
//
void InnerParse();
LPTSTR InnerSplitComment(tagNode * lpstNode,LPTSTR lpszTagString);
LPTSTR InnerSplitTag(tagNode * lpstNode,LPTSTR lpszTagString);
LPTSTR InnerSplitContent(tagNode * lpstNode,LPTSTR lpszTagString);
LPTSTR InnerSplitText(tagNode * lpstNode,LPTSTR lpszTagString);
LPTSTR InnerSplitScript(tagNode * lpstNode,LPTSTR lpszTagString);
LPTSTR InnerSplitStyle(tagNode * lpstNode,LPTSTR lpszTagString);
protected:
//
LPTSTR m_pszSnapshotBuffer;
UINT m_nSnapshotBufferLen;
UINT m_nSnapshotStringLen;
//
tagNode * m_pstHead;
tagNode * m_pstTail;
};
//CHtmlObject.h//
//CHtmlObject.cpp//
#pragma once
/*****************************************************************************************************************
created: 2011/12/03
author: hmm7e (hmm7e_z@126.com)
*****************************************************************************************************************/
#include "HtmlObject.h"
//
BOOL CHtmlObject::IsSpace(TCHAR tcLetter)
{
//以下字符在HTML标记里都算是空格。
return (tcLetter == _T(' ') || tcLetter == _T('\r') ||tcLetter == _T('\n') ||tcLetter == _T('\t') );
}
CHtmlObject::CHtmlObject(void)
{
m_pszSnapshotBuffer = NULL;
m_nSnapshotBufferLen = 0;
m_nSnapshotStringLen = 0;
m_pstHead = NULL;
m_pstTail = NULL;
}
CHtmlObject::~CHtmlObject(void)
{
DeleteSnapshot();
}
//
CHtmlObject::tagNode * CHtmlObject::InnerAllocNode()
{
CHtmlObject::tagNode * pstResult = new CHtmlObject::tagNode;
if( pstResult )
{
::ZeroMemory((LPVOID)pstResult,sizeof(CHtmlObject::tagNode));
}
return pstResult;
}
void CHtmlObject::InnerFreeNode(CHtmlObject::tagNode * lpstNode)
{
if( lpstNode )
delete lpstNode;
}
void CHtmlObject::InnerLinkNextNode(tagNode * lpstNode)
{
//链接到“尾”结点。
//1:如果没有“头”节点,那么表示链表是“空”的。
//2:如果已经存“头”节点,那么就链接新节点到“尾”节点,并重新记录“尾”节点指针。
if( m_pstHead == NULL )
{
m_pstHead = lpstNode;
m_pstTail = lpstNode;
}
else
{
m_pstTail->s_pstNext = lpstNode;
m_pstTail = lpstNode;
}
#ifdef _DEBUG
if( lpstNode->s_pszKey )
{
::OutputDebugString(_T("--"));
::OutputDebugString(lpstNode->s_pszKey);
::OutputDebugString(_T("--\r\n"));
}
if( lpstNode->s_pszValue )
{
::OutputDebugString(_T("--"));
::OutputDebugString(lpstNode->s_pszValue);
::OutputDebugString(_T("--\r\n"));
}
#endif //_DEBUG
}
void CHtmlObject::InnerLinkRightNode(tagNode * lpstTagNode,tagNode * lpstNode)
{
//链接到“属性”的“头”节点。
//1:把现有的“属性”链表,链接到当前新节点的下。
//2:把当前节点做为“头”节点保存。
lpstNode->s_pstRight = lpstTagNode->s_pstRight;
lpstTagNode->s_pstRight