mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-17 06:06:00 -06:00
382 lines
9.7 KiB
C++
382 lines
9.7 KiB
C++
//**********************************************************************************************
|
|
//* Rss Class
|
|
//* ---------
|
|
//* RSS 2.0 Web content syndication format encapsulation for U++
|
|
//* For more information on RSS format,
|
|
//* See:
|
|
//* http://www.rss-specifications.com/
|
|
//* http://www.rssboard.org/rss-specification
|
|
//*
|
|
#include "Rss.h"
|
|
|
|
RssNode RssItem(const char* title, const char* link, const char* desc, const char* author,
|
|
const char* comments, const char* pubdate, RssTag* category, RssTag* guid, RssTag* source, RssTag* enclosure)
|
|
{
|
|
return RssNode(RSS_ITEM)
|
|
<< RssTag(RSS_TITLE, title)
|
|
<< RssTag(RSS_LINK, link)
|
|
<< RssTag(RSS_DESCRIPTION, desc)
|
|
<< (author ? RssTag(RSS_AUTHOR, author) : Null)
|
|
<< (comments ? RssTag(RSS_COMMENTS, comments) : Null)
|
|
<< (pubdate ? RssTag(RSS_PUBDATE, pubdate) : Null)
|
|
<< (category ? *category : NULL)
|
|
<< (guid ? *guid : NULL)
|
|
<< (source ? *source : NULL)
|
|
<< (enclosure ? *enclosure : NULL);
|
|
}
|
|
|
|
RssNode RssImage(const char* title, const char* link, const char* url, const char* desc, Size sz)
|
|
{
|
|
return RssNode(RSS_IMAGE)
|
|
<< RssTag(RSS_TITLE, title)
|
|
<< RssTag(RSS_LINK, link)
|
|
<< RssTag(RSS_URL, url)
|
|
<< (desc ? RssTag(RSS_DESCRIPTION, desc) : Null)
|
|
<< (sz.cx > 0 ? RssTag("width", AsString(sz.cx)) : Null)
|
|
<< (sz.cy > 0 ? RssTag("height", AsString(sz.cy)) : Null);
|
|
}
|
|
|
|
RssNode RssTextInput(const char* title, const char* link, const char* desc, const char* name)
|
|
{
|
|
return RssNode(RSS_TEXTINPUT)
|
|
<< RssTag(RSS_TITLE, title)
|
|
<< RssTag(RSS_LINK, link)
|
|
<< RssTag(RSS_DESCRIPTION, desc)
|
|
<< RssTag(RSS_NAME, name);
|
|
}
|
|
|
|
RssTag RssCloud(const char* domain, int port, const char* path, const char* registerProcedure, const char* protocol)
|
|
{
|
|
return RssTag(RSS_CLOUD)
|
|
("domain", domain)
|
|
("port", port)
|
|
("path", path)
|
|
("registerProcedure", registerProcedure)
|
|
("protocol", protocol);
|
|
}
|
|
|
|
RssTag RssCategory(const char* category, const char* domain)
|
|
{
|
|
return RssTag(RSS_CATEGORY, category)
|
|
("domain", domain);
|
|
}
|
|
|
|
RssTag RssGuid(const char *id, bool islink)
|
|
{
|
|
return RssTag(RSS_GUID, id)
|
|
("isPermaLink", (islink ? "true" : "false"));
|
|
}
|
|
|
|
RssTag RssSource(const char* url, const char* src)
|
|
{
|
|
return RssTag(RSS_SOURCE, src)
|
|
(RSS_URL, url);
|
|
}
|
|
|
|
RssTag RssEnclosure(const char* url, double length, const char* type)
|
|
{
|
|
return RssTag(RSS_ENCLOSURE)
|
|
("url", url)
|
|
("length", length)
|
|
("type", type);
|
|
}
|
|
|
|
void RssTag::Copy(const RssTag& src)
|
|
{
|
|
tag0 = src.tag0;
|
|
txt0 = src.txt0;
|
|
attrs <<= src.attrs;
|
|
}
|
|
|
|
void RssNode::Copy(const RssNode& src)
|
|
{
|
|
RssTag::operator=(src);
|
|
tags <<= src.tags;
|
|
nodes <<= src.nodes;
|
|
}
|
|
|
|
void RssChannel::Copy(const RssChannel& src)
|
|
{
|
|
tags <<= src.tags;
|
|
nodes <<= src.nodes;
|
|
skipdays <<= src.skipdays;
|
|
skiphours <<= src.skiphours;
|
|
}
|
|
|
|
void Rss::Copy(const Rss& src)
|
|
{
|
|
channels <<= src.channels;
|
|
charset = src.charset;
|
|
version = src.version;
|
|
error = src.error;
|
|
}
|
|
|
|
RssTag RssChannel::GetTag(VectorMap<String, RssTag>& t, const char* tag)
|
|
{
|
|
RssTag tg;
|
|
return tg = t.Get(tag, Null);
|
|
}
|
|
|
|
RssNode RssChannel::GetNode(VectorMap<String, RssNode>& n, const char* tag)
|
|
{
|
|
RssNode nd;
|
|
return nd = n.Get(tag, Null);
|
|
}
|
|
|
|
RssChannel& RssChannel::Tag(const RssTag& t)
|
|
{
|
|
|
|
int n = tags.Find(t.Tag());
|
|
if(n >= 0 &&
|
|
t.Tag() == RSS_SKIPDAYS ||
|
|
t.Tag() == RSS_SKIPHOURS)
|
|
tags.Get(t.Tag()) = t;
|
|
else
|
|
tags.Add(t.Tag(), t);
|
|
return *this;
|
|
}
|
|
|
|
RssChannel& RssChannel::Node(const RssNode& t)
|
|
{
|
|
int n = nodes.Find(t.Tag());
|
|
if(n >= 0 &&
|
|
t.Tag() == RSS_SKIPDAYS ||
|
|
t.Tag() == RSS_SKIPHOURS)
|
|
nodes.Get(t.Tag()) = t;
|
|
else
|
|
nodes.Add(t.Tag(), t);
|
|
return *this;
|
|
}
|
|
|
|
RssChannel& RssChannel::SkipDays(const char* d)
|
|
{
|
|
int n = skipdays.GetCount();
|
|
if(n < 7) {
|
|
if(n > 0) {
|
|
for(int i = 0; i < n; i++)
|
|
if(skipdays.At(i) == d)
|
|
return *this;
|
|
}
|
|
skipdays.Add(d);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
RssChannel& RssChannel::SkipHours(int h)
|
|
{
|
|
int n = skiphours.GetCount();
|
|
if(n < 24 && (h < 25 && h > 0)) {
|
|
if(n > 0) {
|
|
for(int i = 0; i < n; i++)
|
|
if(skiphours.At(i) == h)
|
|
return *this;
|
|
}
|
|
skiphours.Add(h);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
//**********************************************************************************************
|
|
//* Rss class implementation
|
|
//*
|
|
|
|
bool Rss::Compose(String& out)
|
|
{
|
|
try {
|
|
String feed, channels, nodes, items;
|
|
if(IsNull(Version()))
|
|
Version("2.0");
|
|
if(IsNull(Encoding()))
|
|
Encoding(CHARSET_DEFAULT);
|
|
if(!HasChannel())
|
|
throw XmlError(t_("No channel node."));
|
|
RssChannels &chs = Channels();
|
|
for(int i = 0; i < chs.GetCount(); i++) {
|
|
if(!chs[i].HasItem())
|
|
throw XmlError(Format(t_("Channel '%d' has not items"), i));
|
|
items = MakeTags(chs[i].Tags());
|
|
nodes = MakeNodes(chs[i].Nodes());
|
|
Vector<String>& skipdays = chs[i].SkipDays();
|
|
Vector<int>& skiphours = chs[i].SkipHours();
|
|
for(int sdc = 0; sdc < skipdays.GetCount(); sdc++)
|
|
items << XmlTag(RSS_SKIPDAYS)
|
|
.Text(skipdays[sdc],
|
|
CharsetByName(Encoding()));
|
|
for(int shc = 0; shc < skiphours.GetCount(); shc++)
|
|
items << XmlTag(RSS_SKIPHOURS)
|
|
.Text(AsString(skiphours[shc]),
|
|
CharsetByName(Encoding()));
|
|
XmlTag ch = XmlTag();
|
|
channels << ch.Tag(RSS_CHANNEL)(items + nodes);
|
|
}
|
|
out << XmlHeader(Encoding());
|
|
out << XmlTag(RSS_HEADER)(RSS_VERSION, Version())(channels);
|
|
|
|
}
|
|
catch(XmlError e) {
|
|
error = e;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Rss::Parse(String& in)
|
|
{
|
|
|
|
Clear();
|
|
try {
|
|
XmlNode n = ParseXML(in);
|
|
if(n.GetCount() == 0 || n[0].GetTag() != RSS_HEADER)
|
|
throw XmlError(t_("<Rss> node not found."));
|
|
|
|
const XmlNode& rssnode = n[0];
|
|
const char* ver = "<?xml version=\""; const char* enc = "encoding=\"";
|
|
const char* buf = in;
|
|
String xmlver, encoding;
|
|
int ver0 = in.Find(ver);
|
|
int enc0 = in.Find(enc);
|
|
if(ver0 >= 0)
|
|
for(int i = ver0 + strlen(ver); i; i++) {
|
|
if(buf[i] == '\"' || buf[i] == '>')
|
|
break;
|
|
else
|
|
xmlver.Cat(buf[i]); // TODO: add Xml version var.
|
|
}
|
|
if(enc0 >= 0)
|
|
for(int i = enc0 + strlen(enc); i; i++) {
|
|
if(buf[i] == '\"' || buf[i] == '>')
|
|
break;
|
|
else
|
|
encoding.Cat(buf[i]);
|
|
}
|
|
if(!IsNull(encoding))
|
|
charset = encoding;
|
|
version = rssnode.Attr(RSS_VERSION);
|
|
|
|
for(int i = 0; i < rssnode.GetCount(); i++) {
|
|
const XmlNode& channelnode = rssnode[i];
|
|
if(channelnode.IsTag(RSS_CHANNEL)) {
|
|
RssChannel& channel = channels.Add();
|
|
for(int cc = 0; cc < channelnode.GetCount(); cc++) {
|
|
const XmlNode& ch = channelnode[cc];
|
|
if(ch.IsTag(RSS_SKIPDAYS))
|
|
channel.SkipDays(ch[0].GetText());
|
|
else
|
|
if(ch.IsTag(RSS_SKIPHOURS))
|
|
channel.SkipHours(StrInt(ch[0].GetText()));
|
|
else
|
|
if(ch.IsTag(RSS_CATEGORY))
|
|
channel.Category(RssCategory(ch[0].GetText(), ch.Attr("domain")));
|
|
else
|
|
if(ch.IsTag(RSS_CLOUD))
|
|
channel.Cloud(RssCloud(ch.Attr("domain"),
|
|
StrInt(ch.Attr("port")),
|
|
ch.Attr("path"),
|
|
ch.Attr("registerProcedure"),
|
|
ch.Attr("protocol"))
|
|
);
|
|
else
|
|
if(ch.IsTag(RSS_IMAGE)) {
|
|
RssNode imgnode;
|
|
imgnode.Tag(RSS_IMAGE);
|
|
for(int iic = 0; iic < ch.GetCount(); iic++) {
|
|
const XmlNode& img = ch[iic];
|
|
imgnode << RssTag(img.GetTag(), img[0].GetText());
|
|
}
|
|
channel.Image(imgnode);
|
|
}
|
|
else
|
|
if(ch.IsTag(RSS_TEXTINPUT)) {
|
|
RssNode tinode;
|
|
tinode.Tag(RSS_TEXTINPUT);
|
|
for(int tic = 0; tic < ch.GetCount(); tic++) {
|
|
const XmlNode& tex = ch[tic];
|
|
tinode << RssTag(tex.GetTag(), tex[0].GetText());
|
|
}
|
|
channel.TextInput(tinode);
|
|
}
|
|
else
|
|
if(ch.IsTag(RSS_ITEM)) {
|
|
RssNode inode;
|
|
inode.Tag(RSS_ITEM);
|
|
for(int itc = 0; itc < ch.GetCount(); itc++) {
|
|
const XmlNode& itm = ch[itc];
|
|
if(itm.IsTag(RSS_GUID))
|
|
inode << RssTag(itm.GetTag(), itm[0].GetText())
|
|
("isPermaLink", itm.Attr("isPermaLink"));
|
|
else
|
|
if(itm.IsTag(RSS_CATEGORY))
|
|
inode << RssCategory(itm[0].GetText(), itm.Attr("domain"));
|
|
else
|
|
if(itm.IsTag(RSS_SOURCE))
|
|
inode << RssSource(itm.Attr("url"), itm[0].GetText());
|
|
else
|
|
if(itm.IsTag(RSS_ENCLOSURE))
|
|
inode << RssEnclosure(itm.Attr("url"), ::StrDbl(itm.Attr("length")), itm.Attr("type"));
|
|
else
|
|
inode << RssTag(itm.GetTag(), itm[0].GetText());
|
|
}
|
|
channel.Item(inode);
|
|
}
|
|
else
|
|
channel.Tag(RssTag(ch.GetTag(), ch[0].GetText()));
|
|
}
|
|
}
|
|
else
|
|
throw XmlError(t_("<Channel> node not found. Faulty Rss structure."));
|
|
}
|
|
}
|
|
catch(XmlError e) {
|
|
error = e;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
String Rss::MakeTags(RssTags& tags)
|
|
{
|
|
String out;
|
|
|
|
for(int tc = 0; tc < tags.GetCount(); tc++) {
|
|
RssTag& t = tags[tc];
|
|
XmlTag x;
|
|
if(t.HasAttr()) {
|
|
RssAttributes& attrs = t.Attrs();
|
|
for(int ac = 0; ac < attrs.GetCount(); ac++) {
|
|
const char* attr = attrs.GetKey(ac);
|
|
const char *val = attrs[ac];
|
|
x(attr, val);
|
|
}
|
|
}
|
|
out << x.Tag(t.Tag()).Text(t.Text(), CharsetByName(Encoding()));
|
|
}
|
|
return out;
|
|
}
|
|
|
|
String Rss::MakeNodes(RssNodes& nodes)
|
|
{
|
|
String out, items;
|
|
for(int nc = 0; nc < nodes.GetCount(); nc++) {
|
|
RssNode& n = nodes[nc];
|
|
XmlTag x;
|
|
items = Null;
|
|
if(n.HasTag())
|
|
items << MakeTags(n.Tags());
|
|
if(n.HasNode())
|
|
items << MakeNodes(n.Nodes());
|
|
out << x.Tag(n.Tag())(items);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
Rss& Rss::Channel(const RssChannel& channel)
|
|
{
|
|
for(int i = 0; i < ChannelCount(); i++) {
|
|
RssChannel& ch = channels.At(i);
|
|
if(&ch == &channel)
|
|
return *this;
|
|
}
|
|
channels.Add(channel);
|
|
return *this;
|
|
}
|