c修正不规范的html,C/C++编写的一个非常不错的Html解析器

众所周知,HTML是结构化文档(Structured Document),由诸多标签(

等)嵌套形成的著名的文档对象模型(DOM, Document Object Model),是显而易见的树形多层次结构。如果带着这种思路看待HTML、编写HTML解析器,无疑将导致问题复杂化。不妨从另一视角俯视HTML文本,视其为一维线状结构:诸多单一节点的顺序排列。仔细审视任何一段HTML文本,以左右尖括号()为边界,会发现HTML文本被天然地分割为:一个标签(Tag),接一段普通文字,再一个标签,再一段普通文字……

在正式编码之前,先确定好“节点”的数据结构。作为“普通文字”节点,需要记录一个文本(text);作为“标签”节点,需要记录标签名称(tagName)、标签类型(tagType)、所有属性值(props);另外还要有个类型(type)以便区分该节点是普通文字、开始标签还是结束标签。这其中固然有些冗余信息,比如对标签来说不需要记录文本,对普通文字来说又不需要记录标签名称、属性值等,不过无伤大雅,简洁的编程模型是最大的诱惑。用C/C++语言语法表示如下:enum HtmlNodeType

{

NODE_UNKNOWN = 0,

NODE_START_TAG,

NODE_CLOSE_TAG,

NODE_CONTENT,

};

enum HtmlTagType

{

TAG_UNKNOWN = 0,

TAG_A, TAG_DIV, TAG_FONT, TAG_IMG, TAG_P, TAG_SPAN, TAG_BR, TAG_B, TAG_I, TAG_HR,

};

struct HtmlNodeProp

{

WCHAR* szName;

WCHAR* szValue;

};

#define MAX_HTML_TAG_LENGTH (15)

struct HtmlNode

{

HtmlNodeType type;

HtmlTagType tagType;

WCHAR tagName[MAX_HTML_TAG_LENGTH+1];

WCHAR* text;

int propCount;

HtmlNodeProp* props;

};具体到编写程序代码,要比想象中容易的多。编码的核心要点是,以左右尖括号()为边界自然分割标签和普通文字。左右尖括号之间的当然是标签节点(开始标签或结束标签),左尖括号()之后(直到后一个左尖括号或结尾)的显然是普通文字节点。区分开始标签或结束标签的关键点是,看左尖括号(下面就是负责把HTML文本解析为一个个节点(HtmlNode)的核心代码(不足百行,够精简吧):void HtmlParser::ParseHtml(const WCHAR* szHtml)

{

m_html = szHtml ? szHtml : L"";

freeHtmlNodes();

if(szHtml == NULL || *szHtml == L'/0') return;

WCHAR* p = (WCHAR*) szHtml;

WCHAR* s = (WCHAR*) szHtml;

HtmlNode* pNode = NULL;

WCHAR c;

bool bInQuotes = false;

while( c = *p )

{

if(c == L'/"')

{

bInQuotes = !bInQuotes;

p++; continue;

}

if(bInQuotes)

{

p++; continue;

}

if(c == L' s)

{

//Add Text Node

pNode = NewHtmlNode();

pNode->type = NODE_CONTENT;

pNode->text = duplicateStrUtill(s, L'')

{

if(p > s)

{

//Add HtmlTag Node

pNode = NewHtmlNode();

while(isspace(*s)) s++;

pNode->type = (*s != L'/' ? NODE_START_TAG : NODE_CLOSE_TAG);

if(*s == L'/') s++;

copyStrUtill(pNode->tagName, MAX_HTML_TAG_LENGTH, s, L'>', true);

//处理自封闭的结点, 如

, 删除tagName中可能会有的'/'字符

//自封闭的结点的type设置为NODE_START_TAG应该可以接受(否则要引入新的NODE_STARTCLOSE_TAG)

int tagNamelen = wcslen(pNode->tagName);

if(pNode->tagName[tagNamelen-1] == L'/')

pNode->tagName[tagNamelen-1] = L'/0';

//处理结点属性

for(int i = 0; i < tagNamelen; i++)

{

if(pNode->tagName[i] == L' ' //第一个空格后面跟的是属性列表

|| pNode->tagName[i] == L'=') //扩展支持这种格式: , 等效于{

WCHAR* props = (pNode->tagName[i] == L' ' ? s + i + 1 : s);

pNode->text = duplicateStrUtill(props, L'>', true);

int nodeTextLen = wcslen(pNode->text);

if(pNode->text[nodeTextLen-1] == L'/') //去掉最后可能会有的'/'字符, 如这种情况:

...

pNode->text[nodeTextLen-1] = L'/0';

pNode->tagName[i] = L'/0';

parseNodeProps(pNode); //parse props

break;

}

}

pNode->tagType = getHtmlTagTypeFromName(pNode->tagName);

}

s = p + 1;

}

p++;

}

if(p > s)

{

//Add Text Node

pNode = NewHtmlNode();

pNode->type = NODE_CONTENT;

pNode->text = duplicateStr(s, -1);

}

#ifdef _DEBUG

dumpHtmlNodes(); //just for test

#endif

}下面是负责解析“开始标签”属性表文本(形如“key1=value1 key2=value2 key3”)的代码,parseNodeProps(),核心思路是按空格和等号字符进行分割属性名和属性值,由于想兼容HTML4.01及以前的不标准的属性表写法(如没有=号也没有属性值),颇费周折://[virtual]

void HtmlParser::parseNodeProps(HtmlNode* pNode)

{

if(pNode == NULL || pNode->propCount > 0 || pNode->text == NULL)

return;

WCHAR* p = pNode->text;

WCHAR *ps = NULL;

CMem mem;

bool inQuote1 = false, inQuote2 = false;

WCHAR c;

while(c = *p)

{

if(c == L'/"')

{

inQuote1 = !inQuote1;

}

else if(c == L'/'')

{

inQuote2 = !inQuote2;

}

if((!inQuote1 && !inQuote2) && (c == L' ' || c == L'/t' || c == L'='))

{

if(ps)

{

mem.AddPointer(duplicateStrAndUnquote(ps, p - ps));

ps = NULL;

}

if(c == L'=')

mem.AddPointer(NULL);

}

else

{

if(ps == NULL)

ps = p;

}

p++;

}

if(ps)

mem.AddPointer(duplicateStrAndUnquote(ps, p - ps));

mem.AddPointer(NULL);

mem.AddPointer(NULL);

WCHAR** pp = (WCHAR**) mem.GetPtr();

CMem props;

for(int i = 0, n = mem.GetSize() / sizeof(WCHAR*) - 2; i < n; i++)

{

props.AddPointer(pp[i]); //prop name

if(pp[i+1] == NULL)

{

props.AddPointer(pp[i+2]); //prop value

i += 2;

}

else

props.AddPointer(NULL); //prop vlalue

}

pNode->propCount = props.GetSize() / sizeof(WCHAR*) / 2;

pNode->props = (HtmlNodeProp*) props.Detach();

}根据标签名称取标签类型的getHtmlTagTypeFromName()方法,就非常直白了,查表,逐一识别://[virtual]

HtmlTagType HtmlParser::getHtmlTagTypeFromName(const WCHAR* szTagName)

{

//todo: uses hashmap

struct N2T { const WCHAR* name; HtmlTagType type; };

static N2T n2tTable[] =

{

{ L"A", TAG_A },

{ L"FONT", TAG_FONT },

{ L"IMG", TAG_IMG },

{ L"P", TAG_P },

{ L"DIV", TAG_DIV },

{ L"SPAN", TAG_SPAN },

{ L"BR", TAG_BR },

{ L"B", TAG_B },

{ L"I", TAG_I },

{ L"HR", TAG_HR },

};

for(int i = 0, count = sizeof(n2tTable)/sizeof(n2tTable[0]); i < count; i++)

{

N2T* p = &n2tTable[i];

if(wcsicmp(p->name, szTagName) == 0)

return p->type;

}

return TAG_UNKNOWN;

}使用也非常之简单:Html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值