1、使用tinyXml2解析RSS文件,并生成一个网页库pagelib.dat。
tinyXml2 -- https://github.com/leethomason/tinyxml2
rss -- https://coolshell.cn/feed
-- http://www.runoob.com/rss/rss-tutorial.html
正则表达式 进行过滤
参考接口:
struct RssItem
{
string title;
string link;
string description;
string content;
};
class RssReader
{
public:
RssReader();
void parseRss();//解析
void dump(const string & filename);//输出
private:
vector<RssItem> _rss;
};
要求:最后生成一个 pagelib.txt, 其格式:
<doc>
<docid>1</docid>
<title>...</title>
<link>...</link>
<description>...</description>
<content>...</content>
</doc>
<doc>
<docid>2</docid>
<title>...</title>
<link>...</link>
<description>...</description>
<content>...</content>
</doc>
<doc>
...
</doc>
RSS文件解析作业思路:xml -->rss-->tinyxml2(使用该库对rss文件进行解析)--> boost::regex/std::regex(使用正则表达式去除html标签)
提示:首先去读coolshell.xml文件,因为是一个rss文件,而我们需要找到rss的channel节点下面的item节点的title节点、link节点中间的文本,至于这些文本可以使用tinyxml2这个第三方库进行解析,所以这里需要看看timyxml2如何解析第三方库(可以看看timyxml2的源码),解析完成一个item后,可以将其存到vector中(也可以将这些原始信息经过后面正则表达式去除标签后再存起来),然后再去读第二个item(其实就是一个循环操作),其实第二个item是第一个item的兄弟节点(可以使用tinyxml2里面的函数进行跳转到第二个item),这样就可以解析coolshell.xml文档了。接着,因为description信息中包含html的标签,所以需要去除这样的html标签,如<p></p>,这个可以使用正则表达式,也就是std::regex进行去除,这个可以在cppreference.html中进行查找使用方法。
最后就是遍历vector,讲读取到的信息存到另外一个文件,格式需要自己定义,使用我们自定义的<doc> </doc>格式
date_11/13作业总结:
wd老师已把作业实现思路讲的很清楚了,陌生的只是对RSS/HTML不了解,对第三方库tinyxml2的使用不了解,对正则表达式不了解。一开始觉得难,但是去了解了RSS/tinyxml2/正则表达式知识后,才发现,咦,原来不难啊,找到了处理html标签的正则表达式之后,作业基本完成-end.
1.了解RSS语言基本组成:RSS 教程 | 菜鸟教程
2.简单学习tinyxml2库,用于读取xml文件,用XMLElement类里面定义的函数可以获取RSS结点指针,tinyxml2库的安装和学习:参考:https://www.codenong.com/cs106726476/
1.复习:
git clone https://github.com/leethomason/tinyxml2.git
cd tinyxml2/
make
./xmltest
sudo make install
2.把tinyxml2.cpp从github下载,放到自己的项目文件夹3.简单学习XMLDocument类:
1.XMLDocument xml;
xml.LoadFile("coolshell.xml");//1.从当前文件夹读取xml文件2.XMLElement *category = xml.RootElement();//2.拿到根结点rss
3.XMLElement *channel = category->FirstChildElement("channel");//根节点的第一个孩子就是channel
4.XMLElement *title = channel->FirstChildElement("title");//拿到channel的孩子结点title
5.XMLElement *item = channel->FirstChildElement("item");//channel结点有很多个item子结点
//作业的要求就是拿到item结点下的title,link,description,content,存入到结构体中,再用vector保存
item = item->NextSiblingElement();//找下一个兄弟结点item
3.正则表达式处理网页html标签,C++Primer第五版简单了解了下语法,网上找去除html标签的正则表达式,拿来用即可。
1.正则表达式:regex reg("<[^>]*>");
2.regex_replace(str,reg,"")的使用。
代码实现:
01_RssHeader.h
#include <iostream>
#include <regex>
#include <fstream>
#include "tinyxml2.h"
#include <vector>
using namespace std;
using namespace tinyxml2;
#include <string>
struct RssItem
{
string title;
string link;
string description;
string content;
};
class RssReader
{
public:
RssReader(){};
void parseRss(const char *filename);//1.读取xml文件,解析相应内容,把每个结构体存入vector
void read(); //2.把解析的内容输出到控制台
void dump(const string & filename);//3.输出到文件
private:
vector<RssItem> _rss;
};
02_Rss_main.cc
#include "01_RssHeader.h"
int main()
{
RssReader s1;
s1.parseRss("coolshell.xml");
s1.dump("pagelib.dat");
return 0;
}
03_parseData.cc
#include "01_RssHeader.h"
//获取Rss文件中channel结点下item结点的有用内容:title/link/descirption/content,保存到结构体vector
void RssReader::parseRss(const char *filename)//解析
{
XMLDocument xml; //1.声明xml类,用来读取xml文件
xml.LoadFile("coolshell.xml");//2.读取xml文件
XMLElement *category = xml.RootElement();//3.拿到xml文件的根节点Rss
XMLElement *channel = category->FirstChildElement("channel");//4.拿到channel结点指针
XMLElement *title = channel->FirstChildElement("title");
XMLElement *item = channel->FirstChildElement("item");//5.拿到item结点
while(item)//6.设置循环,遍历channel下的每个item结点
{
RssItem rss_item;//7.把数据保存到结构体
regex reg("<[^>]*>"); //8.正则表达式去除网页html标签
//9.获取title结点指针,把内容保存到结构体相应的变量
XMLElement *item_title = item->FirstChildElement("title");//title结点
rss_item.title = item_title->GetText(); //保存结点的内容
//10.获取link结点指针,把内容保存到结构体相应的变量
XMLElement *item_link = item->FirstChildElement("link");
rss_item.link= item_link->GetText();
//cout<<Rss.link<<endl;
//11.获取desc结点指针,数据用正则去除html标签后,把内容保存到结构体相应的变量
XMLElement *item_description = item->FirstChildElement("description");
rss_item.description= item_description->GetText();
rss_item.description = regex_replace(rss_item.description,reg,"");//去除html标签
//11.获取content结点指针,数据用正则去除html标签后,把内容保存到结构体相应的变量
XMLElement *item_content = item->FirstChildElement("content:encoded");
rss_item.content= item_content->GetText();
rss_item.content = regex_replace(rss_item.content,reg,"");//去除网页标签
//12.把每个结构体保存进 vector,每个结构体都是一篇文章了
_rss.push_back(rss_item);
item = item->NextSiblingElement();//找下一个item结点
}
}
void RssReader::read()//内容输出到控制台
{
for(int i=0;i<_rss.size();i++)
{
cout<<_rss[i].title<<endl;
cout<<_rss[i].link<<endl;
cout<<_rss[i].description<<endl;
cout<<_rss[i].content<<endl;
}
}
//把内容按指定格式输出到指定文件
void RssReader::dump(const string & filename)
{
ofstream ofs(filename);
if(!ofs.good())
{
cerr<<"ofstream is not good!"<<endl;
return;
}
for(int i=0;i<_rss.size();i++)
{
ofs<<"<doc>"<<"\n"
<<"\t"<<"<docid>"<<i+1<<"</docid>"<<"\n"
<<"\t"<<"<title>"<<_rss[i].title<<"</title>"<<"\n"
<<"\t"<<"<link>"<<_rss[i].link<<"</link>"<<"\n"
<<"\t"<<"<description>"<<_rss[i].description<<"</description>"<<"\n"
<<"\t"<<"<content>"<<_rss[i].content<<"</content>"<<endl;
}
ofs.close();
}
Ubuntu下对代码进行编译:
g++ tinyxml2.cpp 01_RssHeader.h 02_Rss_main.cc 03_parseData.cc -o demo1 -std=c++11
部分输出结果:RSS文件来自Coolshell:Go编程模式 : 泛型编程 | 酷 壳 - CoolShell
<doc>
<docid>1</docid>
<title>Go编程模式 : 泛型编程</title>
<link>https://coolshell.cn/articles/21615.html</link>
<description>Go语言的1.17版本发布了,其中开始正式支持泛型了。虽然还有一些限制(比如,不能把泛型函数export),但是,可以体验了。我的这个《Go编程模式》的系列终于...
Read More Read More
The post Go编程模式 : 泛型编程 first appeared on 酷 壳 - CoolShell.</description>
<content>Go语言的1.17版本发布了,其中开始正式支持泛型了。虽然还有一些限制(比如,不能把泛型函数export),但是,可以体验了。我的这个《Go编程模式》的系列终于有了真正的泛型编程了,再也不需要使用反射或是go generation这些难用的技术了。周末的时候,我把Go 1.17下载下来,然后,体验了一下泛型编程,还是很不错的。下面,就让我们来看一下Go的泛型编程。(注:不过,如果你对泛型编程的重要性还不是很了解的话,你可以先看一下之前的这篇文章《Go编程模式:Go Generation》,然后再读一下《Go编程模式:MapReduce》)
本文是全系列中第10 / 10篇:Go编程模式Go编程模式:切片,接口,时间和性能Go 编程模式:错误处理Go 编程模式:Functional OptionsGo编程模式:委托和反转控制Go编程模式:Map-ReduceGo 编程模式:Go GenerationGo编程模式:修饰器Go编程模式:PipelineGo 编程模式:k8s Visitor 模式Go编程模式 : 泛型编程« 上一篇文章
初探
我们先来看一个简单的示例:
package main
import "fmt"
func print[T any] (arr []T) {
for _, v := range arr {
fmt.Print(v)
fmt.Print(" ")
}
fmt.Println("")
}
func main() {
strs := []string{"Hello", "World", "Generics"}
decs := []float64{3.14, 1.14, 1.618, 2.718 }
nums := []int{2,4,6,8}
print(strs)
print(decs)
print(nums)
}
上面这个例子中,有一个 print() 函数,这个函数就是想输出数组的值,如果没有泛型的话,这个函数需要写出 int 版,float版,string 版,以及我们的自定义类型(struct)的版本。现在好了,有了泛型的支持后,我们可以使用 [T any] 这样的方式来声明一个泛型类型(有点像C++的 typename T),然后面都使用 T 来声明变量就好。
上面这个示例中,我们泛型的 print() 支持了三种类型的适配—— int型,float64型,和 string型。要让这段程序跑起来需要在编译行上加上 -gcflags=-G=3编译参数(这个编译参数会在1.18版上成为默认参数),如下所示:
$ go run -gcflags=-G=3 ./main.go
有了个操作以后,我们就可以写一些标准的算法了,比如,一个查找的算法
func find[T comparable] (arr []T, elem T) int {
for i, v := range arr {
if v == elem {
return i
}
}
return -1
}
我们注意到,我们没有使用 [T any]的形式,而是使用 [T comparable]的形式,comparable是一个接口类型,其约束了我们的类型需要支持 == 的操作, 不然就会有类型不对的编译错误。上面的这个 find() 函数同样可以使用于 int, float64或是string类型。
从上面的这两个小程序来看,Go语言的泛型已基本可用了,只不过,还有三个问题:
一个是 fmt.Printf()中的泛型类型是 %v 还不够好,不能像c++ iostream重载 >> 来获得程序自定义的输出。
另外一个是,go不支持操作符重载,所以,你也很难在泛型算法中使用“泛型操作符”如:== 等
最后一个是,上面的 find() 算法依赖于“数组”,对于hash-table、tree、graph、link等数据结构还要重写。也就是说,没有一个像C++ STL那样的一个泛型迭代器(这其中的一部分工作当然也需要通过重载操作符(如:++ 来实现)
不过,这个已经很好了,让我们来看一下,可以干哪些事了。
数据结构
Stack 栈
编程支持泛型最大的优势就是可以实现类型无关的数据结构了。下面,我们用Slices这个结构体来实现一个Stack的数结构。
首先,我们可以定义一个泛型的Stack
type stack [T any] []T
看上去很简单,还是 [T any] ,然后 []T 就是一个数组,接下来就是实现这个数据结构的各种方法了。下面的代码实现了 push() ,pop(),top(),len(),print()这几个方法,这几个方法和 C++的STL中的 Stack很类似。(注:目前Go的泛型函数不支持 export,所以只能使用第一个字符是小写的函数名)
func (s *stack[T]) push(elem T) {
*s = append(*s, elem)
}
func (s *stack[T]) pop() {
if len(*s) > 0 {
*s = (*s)[:len(*s)-1]
}
}
......