针对特定XML的解析器XMLParser
一、建立网页库和偏移文件
为文本搜索引擎建立网页库,首先要把所有的网页(这里是文章)格式化,并保存到指定的格式中。如以下格式:
|
| <doc>
| <docid>...</docid>
| <url>...</url>
| <title>...</title>
| <content>...</content>
| </doc>
| <doc>
| ...
| </doc>
| ...
其中<doc>……</doc>保存着一篇文章;<docid>...</docid> 保存文档id;<url>...</url>保存文本路径;<content>...</content> 保存文章内容。
如何生成这样的格式呢?对每一篇文章使用以下方法:
首先从文章中提取标题 title;以及获得文章的路径,然后用字符串拼接,拼接成上述格式。
std::ofstream ofs("pages.lib",std::ios::out);//打开网页库
std::ofstream out("offset.lib",std::ios::out); //打开偏移文件
string txt = "<doc><docid>" + docid + "</docid>" + "<url>" + url + ... + "</doc>"; ofs << txt; //写到文件流 int offset = ofs.tellg();// offset 获得当前指针的位置 ,tellg()和tellp()是C++文件流操作中获得流指针的函数 int length = txt.size();//length out<<docid<<" "<<offset<<""<<length<<endl;//将这片文章的起始位置和偏移量写到偏移文件中,便于后面从网页库中读出一篇网页
循环对所有文章执行上面的操作,就得到一个网页库和一个偏移文件。
二、解析网页
根据偏移文件,从网页库中读出一篇网页,接着对网页库中的docid,title,url进行解析。
以下是解析器的代码:
1 #ifndef _XMLPARSER_HPP 2 #define _XMLPARSER_HPP 3 #include <stdio.h> 4 #include <utility> 5 #include <string> 6 #include <stdlib.h> 7 using namespace std; 8 class XMLParser 9 { 10 public: 11 XMLParser(string & page) 12 :page_(page) 13 {} 14 string parser(string tag) 15 { 16 string ltag,rtag; 17 ltag = "<"+tag+">"; //拼接左标签 18 rtag = "</"+tag+">"; //拼接右标签 19 string str; 20 //pair<string,string> pr; 21 string::size_type bpos; 22 string::size_type epos; 23 string::size_type len; 24 //确定字符串范围 25 bpos = page_.find(ltag);//查找作标签下标 26 epos = page_.find(rtag);//查找右标签下标 27 bpos+=ltag.size(); 28 len = epos - bpos; //计算标签包含内容长度 29 if(bpos==page_.npos||epos==page_.npos) 30 { 31 printf("No such tag: %s \n",tag.c_str()); 32 exit(-1); 33 } 34 if(bpos==epos) 35 { 36 printf("标签内容不存在\n"); 37 exit(-1); 38 } 39 40 string content = page_.substr(bpos+1,len-2);//截取标签包含内容 41 #if 0 42 if(content[content.size()-1]=='\n') 43 content[content.size()-1]=='\0'; 44 if(content[content.size()-2]=='\n') 45 content[content.size()-2]=='\0'; 46 if(content[content.size()-3]=='\n') 47 content[content.size()-3]=='\0'; 48 #endif 49 //pr.first = tag; 50 //pr.second = content; 51 //return pr; //最好使用std::move,减少复制 52 return content; 53 } 54 private: 55 string &page_; //使用引用,避免大复制 56 }; 57 #endif
解析器每次只能传入一个标签,并且返回标签对应的内容,要对所有标签进行解析,那么就要多次调用parser()。当然,对于一篇文章,就对应一个解析器对象,parser()是对外的一个函数,接收标签,并从对象中解析出便签包含的内容。也就是说,创建一个对象可以对这个对象调用多次parser(string tag)对文章进行解析。
三、解析器的使用
首先要有一个加载偏移文件的工具,这里写了一个工具PageOffset.hpp
(代码折叠)代码如下:
#ifndef _PAGEOFFSET_HPP #define _PAGEOFFSET_HPP #include <fstream> #include <sstream> #include <vector> #include <string> #include <utility> #include <stdlib.h> #include <stdio.h> using namespace std; class PageOffset { public: PageOffset(string &path) { ifstream ifs(path.c_str(),ios::in); string line; string str; pair<int,int> p1; pair<int ,pair<int ,int > > p2; while(getline(ifs,line),!ifs.eof()) { istringstream iss(line); int pos = 0; while(iss>>str) { if(pos == 0) { p2.first = atoi(str.c_str()); } else if(pos == 1) { p1.first = atoi(str.c_str()); } else if(pos == 2) { p1.second = atoi(str.c_str()); p2.second = p1; } pos++; } offset.insert(p2); } ifs.close(); } pair<int ,int > &operator[](int docid) { return offset[docid]; } size_t size() const { return offset.size(); } private: map<int,pair<int,int> > offset; }; #endif
测试程序代码如下:
1 #include "XMLParser.hpp" 2 #include "PageOffset.hpp" 3 #include <fstream> 4 #include <cstring> 5 #include <string> 6 #include <utility> 7 using namespace std; 8 int main() 9 { 10 ifstream ifs("pages.lib",ios::in); 11 PageOffset pageoffset("offset.lib"); 12 char *buf = new char[1024*1024]; //开缓存空间 13 pair<int,int> page =(pageOffset)[vec[i]]; //从偏移文件中提取文档相应的offset与size,读出一篇文章 14 ifs.seekg(page.first,ios::beg); //定位文件起始位置 15 memset(buf,0,1024*1024); 16 ifs.read(buf,page.second); //读取一篇文档 17 string str(buf); //C风格的字符串转化为C++风格的字符串 18 XMLParser xmlparser(str); //将文章加入解析器,初始化一个解析器对象 19 string title = xmlparser.parser("title"); //解析标题 20 string url = xmlparser.parser("url"); //解析url 21 string content = xmlparser.parser("content"); //解析内容 22 delete []buf; //释放缓存空间,防止内存泄露 23 return 0; 24 }
以上是针对特定格式的文本库进行解析。因此不能给适用于多种场合。