summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjsdelfino <jsdelfino@13f79535-47bb-0310-9956-ffa450edef68>2010-05-31 02:29:45 +0000
committerjsdelfino <jsdelfino@13f79535-47bb-0310-9956-ffa450edef68>2010-05-31 02:29:45 +0000
commit7808f187b68261a58de9e348a722b6d0e32dcbe9 (patch)
treea1b97c546f15448e46565bc40ff7e20ff5525092
parent90b55b93704adb3e1fac59d58c2d644d37537bc9 (diff)
Add support for RSS feeds and minor fixes to ATOM support.
git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@949652 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--sca-cpp/trunk/configure.ac1
-rw-r--r--sca-cpp/trunk/etc/git-exclude2
-rw-r--r--sca-cpp/trunk/kernel/xml.hpp9
-rw-r--r--sca-cpp/trunk/modules/Makefile.am2
-rw-r--r--sca-cpp/trunk/modules/atom/atom-test.cpp20
-rw-r--r--sca-cpp/trunk/modules/atom/atom.hpp29
-rw-r--r--sca-cpp/trunk/modules/http/Makefile.am5
-rw-r--r--sca-cpp/trunk/modules/http/curl-get.cpp50
-rw-r--r--sca-cpp/trunk/modules/http/curl.hpp14
-rw-r--r--sca-cpp/trunk/modules/rss/Makefile.am22
-rw-r--r--sca-cpp/trunk/modules/rss/rss-test.cpp214
-rw-r--r--sca-cpp/trunk/modules/rss/rss.hpp201
12 files changed, 564 insertions, 5 deletions
diff --git a/sca-cpp/trunk/configure.ac b/sca-cpp/trunk/configure.ac
index b7c9cb418b..25d10efd78 100644
--- a/sca-cpp/trunk/configure.ac
+++ b/sca-cpp/trunk/configure.ac
@@ -770,6 +770,7 @@ AC_CONFIG_FILES([Makefile
modules/http/Makefile
modules/python/Makefile
modules/java/Makefile
+ modules/rss/Makefile
modules/server/Makefile
modules/wsgi/Makefile
components/Makefile
diff --git a/sca-cpp/trunk/etc/git-exclude b/sca-cpp/trunk/etc/git-exclude
index 77694e36a7..15adb84816 100644
--- a/sca-cpp/trunk/etc/git-exclude
+++ b/sca-cpp/trunk/etc/git-exclude
@@ -99,4 +99,6 @@ qpid-test
xmpp-test
pgsql-test
tinycdb-test
+curl-get
+rss-test
diff --git a/sca-cpp/trunk/kernel/xml.hpp b/sca-cpp/trunk/kernel/xml.hpp
index fa1701d83a..895ed7035e 100644
--- a/sca-cpp/trunk/kernel/xml.hpp
+++ b/sca-cpp/trunk/kernel/xml.hpp
@@ -200,6 +200,15 @@ int readCallback(void *context, char* buffer, int len) {
}
/**
+ * Return true if a list of strings contains an XML document.
+ */
+const bool isXML(const list<string>& ls) {
+ if (isNil(ls))
+ return false;
+ return substr(car(ls), 0, 5) == "<?xml";
+}
+
+/**
* Read a list of values from a list of strings representing an XML document.
*/
const list<value> readXML(const list<string>& ilist) {
diff --git a/sca-cpp/trunk/modules/Makefile.am b/sca-cpp/trunk/modules/Makefile.am
index 5ce2787fcf..9c47fd9fd8 100644
--- a/sca-cpp/trunk/modules/Makefile.am
+++ b/sca-cpp/trunk/modules/Makefile.am
@@ -15,7 +15,7 @@
# specific language governing permissions and limitations
# under the License.
-SUBDIRS = scheme atom json scdl http server python wsgi java
+SUBDIRS = scheme atom rss json scdl http rss server python wsgi java
includedir = $(prefix)/include/modules
nobase_include_HEADERS = */*.hpp
diff --git a/sca-cpp/trunk/modules/atom/atom-test.cpp b/sca-cpp/trunk/modules/atom/atom-test.cpp
index 316e8b44db..4acc720816 100644
--- a/sca-cpp/trunk/modules/atom/atom-test.cpp
+++ b/sca-cpp/trunk/modules/atom/atom-test.cpp
@@ -48,6 +48,14 @@ string itemEntry("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<link href=\"cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b\"/>"
"</entry>\n");
+string itemTextEntry("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<entry xmlns=\"http://www.w3.org/2005/Atom\">"
+ "<title type=\"text\">item</title>"
+ "<id>cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b</id>"
+ "<content type=\"text\">Apple</content>"
+ "<link href=\"cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b\"/>"
+ "</entry>\n");
+
string incompleteEntry("<entry xmlns=\"http://www.w3.org/2005/Atom\">"
"<title>item</title><content type=\"text/xml\">"
"<Item xmlns=\"http://services/\">"
@@ -80,12 +88,24 @@ bool testEntry() {
assert(str(os) == itemEntry);
}
{
+ const list<value> a = mklist<value>(string("item"), string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"), string("Apple"));
+ ostringstream os;
+ writeATOMEntry<ostream*>(writer, &os, a);
+ assert(str(os) == itemTextEntry);
+ }
+ {
const list<value> a = content(readATOMEntry(mklist(itemEntry)));
ostringstream os;
writeATOMEntry<ostream*>(writer, &os, a);
assert(str(os) == itemEntry);
}
{
+ const list<value> a = content(readATOMEntry(mklist(itemTextEntry)));
+ ostringstream os;
+ writeATOMEntry<ostream*>(writer, &os, a);
+ assert(str(os) == itemTextEntry);
+ }
+ {
const list<value> a = content(readATOMEntry(mklist(incompleteEntry)));
ostringstream os;
writeATOMEntry<ostream*>(writer, &os, a);
diff --git a/sca-cpp/trunk/modules/atom/atom.hpp b/sca-cpp/trunk/modules/atom/atom.hpp
index fadb482e0d..bb39e7c262 100644
--- a/sca-cpp/trunk/modules/atom/atom.hpp
+++ b/sca-cpp/trunk/modules/atom/atom.hpp
@@ -44,7 +44,7 @@ const list<value> entryElementsToValues(const list<value>& e) {
const list<value> li = filter<value>(selector(mklist<value>(element, "id")), e);
const value i = isNil(li)? value(emptyString) : elementValue(car(li));
const list<value> lc = filter<value>(selector(mklist<value>(element, "content")), e);
- return mklist<value>(t, i, cadr(elementChildren(car(lc))));
+ return mklist<value>(t, i, elementValue(car(lc)));
}
/**
@@ -57,6 +57,15 @@ const list<value> entriesElementsToValues(const list<value>& e) {
}
/**
+ * Return true if a list of strings contains an RSS feed.
+ */
+const bool isATOMFeed(const list<string>& ls) {
+ if (!isXML(ls))
+ return false;
+ return contains(car(ls), "<feed");
+}
+
+/**
* Convert a list of strings to a list of values representing an ATOM entry.
*/
const failable<list<value> > readATOMEntry(const list<string>& ilist) {
@@ -71,7 +80,7 @@ const failable<list<value> > readATOMEntry(const list<string>& ilist) {
*/
const value entryValue(const list<value>& e) {
const list<value> v = elementsToValues(mklist<value>(caddr(e)));
- return cons(car(e), mklist<value>(cadr(e), cdr<value>(car(v))));
+ return cons(car(e), mklist<value>(cadr(e), isList(car(v))? (value)cdr<value>(car(v)) : car(v)));
}
/**
@@ -90,6 +99,19 @@ const failable<list<value> > readATOMFeed(const list<string>& ilist) {
}
/**
+ * Convert an ATOM feed containing elements to an ATOM feed containing values.
+ */
+const list<value> feedValuesLoop(const list<value> e) {
+ if (isNil(e))
+ return e;
+ return cons<value>(entryValue(car(e)), feedValuesLoop(cdr(e)));
+}
+
+const list<value> feedValues(const list<value>& e) {
+ return cons(car<value>(e), cons<value>(cadr<value>(e), feedValuesLoop(cddr<value>(e))));
+}
+
+/**
* Convert a list of values representing an ATOM entry to a list of elements.
* The first two values in the list are the entry title and id.
*/
@@ -98,7 +120,8 @@ const list<value> entryElement(const list<value>& l) {
+ element + "entry" + (list<value>() + attribute + "xmlns" + "http://www.w3.org/2005/Atom")
+ (list<value>() + element + "title" + (list<value>() + attribute + "type" + "text") + car(l))
+ (list<value>() + element + "id" + cadr(l))
- + (list<value>() + element + "content" + (list<value>() + attribute + "type" + "application/xml") + caddr(l))
+ + (list<value>() + element + "content"
+ + (list<value>() + attribute + "type" + (isList(caddr(l))? "application/xml" : "text")) + caddr(l))
+ (list<value>() + element + "link" + (list<value>() + attribute + "href" + cadr(l)));
}
diff --git a/sca-cpp/trunk/modules/http/Makefile.am b/sca-cpp/trunk/modules/http/Makefile.am
index 724d001cb8..224fcc1557 100644
--- a/sca-cpp/trunk/modules/http/Makefile.am
+++ b/sca-cpp/trunk/modules/http/Makefile.am
@@ -23,6 +23,9 @@ moddir=$(prefix)/modules/http
curl_test_SOURCES = curl-test.cpp
curl_test_LDFLAGS = -lxml2 -lcurl -lmozjs
+curl_get_SOURCES = curl-get.cpp
+curl_get_LDFLAGS = -lxml2 -lcurl -lmozjs
+
mod_DATA = httpd.prefix curl.prefix
nobase_dist_mod_DATA = conf/*
@@ -34,5 +37,5 @@ curl.prefix: $(top_builddir)/config.status
echo ${CURL_PREFIX} >curl.prefix
dist_noinst_SCRIPTS = httpd-test http-test
-noinst_PROGRAMS = curl-test
+noinst_PROGRAMS = curl-test curl-get
TESTS = httpd-test http-test
diff --git a/sca-cpp/trunk/modules/http/curl-get.cpp b/sca-cpp/trunk/modules/http/curl-get.cpp
new file mode 100644
index 0000000000..cbd693092a
--- /dev/null
+++ b/sca-cpp/trunk/modules/http/curl-get.cpp
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/* $Rev$ $Date$ */
+
+/**
+ * HTTP client command line test tool.
+ */
+
+#include <assert.h>
+#include "stream.hpp"
+#include "string.hpp"
+#include "perf.hpp"
+#include "curl.hpp"
+
+namespace tuscany {
+namespace http {
+
+const bool testGet(const string& url) {
+ CURLSession ch;
+ const failable<value> val = get(url, ch);
+ assert(hasContent(val));
+ cout << val << endl;
+ return true;
+}
+
+}
+}
+
+int main(unused const int argc, const char** argv) {
+ tuscany::http::testGet(tuscany::string(argv[1]));
+ return 0;
+}
+
diff --git a/sca-cpp/trunk/modules/http/curl.hpp b/sca-cpp/trunk/modules/http/curl.hpp
index 95c81d9b94..3d11ac56b1 100644
--- a/sca-cpp/trunk/modules/http/curl.hpp
+++ b/sca-cpp/trunk/modules/http/curl.hpp
@@ -37,6 +37,7 @@
#include "monad.hpp"
#include "parallel.hpp"
#include "../atom/atom.hpp"
+#include "../rss/rss.hpp"
#include "../json/json.hpp"
namespace tuscany {
@@ -330,10 +331,23 @@ const failable<value> get(const string& url, const CURLSession& ch) {
const string ct(content(contentType(car(content(res)))));
if (ct == "application/atom+xml;type=entry") {
+ // Read an ATOM entry
const value val(atom::entryValue(content(atom::readATOMEntry(ls))));
debug(val, "http::get::result");
return val;
}
+ if (ct == "application/atom+xml;type=feed" || atom::isATOMFeed(ls)) {
+ // Read an ATOM feed
+ const value val(atom::feedValues(content(atom::readATOMFeed(ls))));
+ debug(val, "http::get::result");
+ return val;
+ }
+ if (ct == "application/rss+xml" || rss::isRSSFeed(ls)) {
+ // Read an RSS feed
+ const value val(rss::feedValues(content(rss::readRSSFeed(ls))));
+ debug(val, "http::get::result");
+ return val;
+ }
// Return the content as a list of values
const value val(mkvalues(ls));
diff --git a/sca-cpp/trunk/modules/rss/Makefile.am b/sca-cpp/trunk/modules/rss/Makefile.am
new file mode 100644
index 0000000000..5254eacb25
--- /dev/null
+++ b/sca-cpp/trunk/modules/rss/Makefile.am
@@ -0,0 +1,22 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+rss_test_SOURCES = rss-test.cpp
+rss_test_LDFLAGS = -lxml2
+
+noinst_PROGRAMS = rss-test
+TESTS = rss-test
diff --git a/sca-cpp/trunk/modules/rss/rss-test.cpp b/sca-cpp/trunk/modules/rss/rss-test.cpp
new file mode 100644
index 0000000000..3168e1f474
--- /dev/null
+++ b/sca-cpp/trunk/modules/rss/rss-test.cpp
@@ -0,0 +1,214 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/* $Rev$ $Date$ */
+
+/**
+ * Test RSS data conversion functions.
+ */
+
+#include <assert.h>
+#include "stream.hpp"
+#include "string.hpp"
+#include "rss.hpp"
+
+namespace tuscany {
+namespace rss {
+
+ostream* writer(const string& s, ostream* os) {
+ (*os) << s;
+ return os;
+}
+
+string itemEntry("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<item>"
+ "<title>fruit</title>"
+ "<link>cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b</link>"
+ "<description>"
+ "<fruit>"
+ "<name>Apple</name><price>$2.99</price>"
+ "</fruit>"
+ "</description>"
+ "</item>\n");
+
+string itemTextEntry("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<item>"
+ "<title>fruit</title>"
+ "<link>cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b</link>"
+ "<description>Apple</description>"
+ "</item>\n");
+
+string incompleteEntry("<item>"
+ "<title>fruit</title><description>"
+ "<fruit xmlns=\"http://services/\">"
+ "<name xmlns=\"\">Orange</name>"
+ "<price xmlns=\"\">3.55</price>"
+ "</fruit>"
+ "</description>"
+ "</item>");
+
+string completedEntry("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<item>"
+ "<title>fruit</title>"
+ "<link></link>"
+ "<description>"
+ "<fruit xmlns=\"http://services/\">"
+ "<name xmlns=\"\">Orange</name>"
+ "<price xmlns=\"\">3.55</price>"
+ "</fruit>"
+ "</description>"
+ "</item>\n");
+
+bool testEntry() {
+ {
+ const list<value> i = list<value>() + element + value("fruit")
+ + value(list<value>() + element + value("name") + value(string("Apple")))
+ + value(list<value>() + element + value("price") + value(string("$2.99")));
+ const list<value> a = mklist<value>(string("fruit"), string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"), i);
+ ostringstream os;
+ writeRSSEntry<ostream*>(writer, &os, a);
+ assert(str(os) == itemEntry);
+ }
+ {
+ const list<value> a = mklist<value>(string("fruit"), string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"), "Apple");
+ ostringstream os;
+ writeRSSEntry<ostream*>(writer, &os, a);
+ assert(str(os) == itemTextEntry);
+ }
+ {
+ const list<value> a = content(readRSSEntry(mklist(itemEntry)));
+ ostringstream os;
+ writeRSSEntry<ostream*>(writer, &os, a);
+ assert(str(os) == itemEntry);
+ }
+ {
+ const list<value> a = content(readRSSEntry(mklist(itemTextEntry)));
+ ostringstream os;
+ writeRSSEntry<ostream*>(writer, &os, a);
+ assert(str(os) == itemTextEntry);
+ }
+ {
+ const list<value> a = content(readRSSEntry(mklist(incompleteEntry)));
+ ostringstream os;
+ writeRSSEntry<ostream*>(writer, &os, a);
+ assert(str(os) == completedEntry);
+ }
+ return true;
+}
+
+string emptyFeed("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<rss version=\"2.0\">"
+ "<channel>"
+ "<title>Feed</title>"
+ "<link>1234</link>"
+ "<description>Feed</description>"
+ "</channel>"
+ "</rss>\n");
+
+string itemFeed("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<rss version=\"2.0\">"
+ "<channel>"
+ "<title>Feed</title>"
+ "<link>1234</link>"
+ "<description>Feed</description>"
+ "<item>"
+ "<title>fruit</title>"
+ "<link>cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b</link>"
+ "<description>"
+ "<fruit>"
+ "<name>Apple</name><price>$2.99</price>"
+ "</fruit>"
+ "</description>"
+ "</item>"
+ "<item>"
+ "<title>fruit</title>"
+ "<link>cart-53d67a61-aa5e-4e5e-8401-39edeba8b83c</link>"
+ "<description>"
+ "<fruit>"
+ "<name>Orange</name><price>$3.55</price>"
+ "</fruit>"
+ "</description>"
+ "</item>"
+ "</channel>"
+ "</rss>\n");
+
+bool testFeed() {
+ {
+ ostringstream os;
+ writeRSSFeed<ostream*>(writer, &os, mklist<value>("Feed", "1234"));
+ assert(str(os) == emptyFeed);
+ }
+ {
+ const list<value> a = content(readRSSFeed(mklist(emptyFeed)));
+ ostringstream os;
+ writeRSSFeed<ostream*>(writer, &os, a);
+ assert(str(os) == emptyFeed);
+ }
+ {
+ const list<value> i = list<value>()
+ + (list<value>() + "fruit" + "cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"
+ + (list<value>() + element + "fruit"
+ + (list<value>() + element + "name" + "Apple")
+ + (list<value>() + element + "price" + "$2.99")))
+ + (list<value>() + "fruit" + "cart-53d67a61-aa5e-4e5e-8401-39edeba8b83c"
+ + (list<value>() + element + "fruit"
+ + (list<value>() + element + "name" + "Orange")
+ + (list<value>() + element + "price" + "$3.55")));
+ const list<value> a = cons<value>("Feed", cons<value>("1234", i));
+ ostringstream os;
+ writeRSSFeed<ostream*>(writer, &os, a);
+ assert(str(os) == itemFeed);
+ }
+ {
+ const list<value> i = list<value>()
+ + (list<value>() + "fruit" + "cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"
+ + valueToElement(list<value>() + "fruit"
+ + (list<value>() + "name" + "Apple")
+ + (list<value>() + "price" + "$2.99")))
+ + (list<value>() + "fruit" + "cart-53d67a61-aa5e-4e5e-8401-39edeba8b83c"
+ + valueToElement(list<value>() + "fruit"
+ + (list<value>() + "name" + "Orange")
+ + (list<value>() + "price" + "$3.55")));
+ const list<value> a = cons<value>("Feed", cons<value>("1234", i));
+ ostringstream os;
+ writeRSSFeed<ostream*>(writer, &os, a);
+ assert(str(os) == itemFeed);
+ }
+ {
+ const list<value> a = content(readRSSFeed(mklist(itemFeed)));
+ ostringstream os;
+ writeRSSFeed<ostream*>(writer, &os, a);
+ assert(str(os) == itemFeed);
+ }
+ return true;
+}
+
+}
+}
+
+int main() {
+ tuscany::cout << "Testing..." << tuscany::endl;
+
+ tuscany::rss::testEntry();
+ tuscany::rss::testFeed();
+
+ tuscany::cout << "OK" << tuscany::endl;
+
+ return 0;
+}
diff --git a/sca-cpp/trunk/modules/rss/rss.hpp b/sca-cpp/trunk/modules/rss/rss.hpp
new file mode 100644
index 0000000000..506d1f4a6d
--- /dev/null
+++ b/sca-cpp/trunk/modules/rss/rss.hpp
@@ -0,0 +1,201 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/* $Rev$ $Date$ */
+
+#ifndef tuscany_rss_hpp
+#define tuscany_rss_hpp
+
+/**
+ * RSS data conversion functions.
+ */
+
+#include "string.hpp"
+#include "list.hpp"
+#include "value.hpp"
+#include "monad.hpp"
+#include "xml.hpp"
+
+namespace tuscany {
+namespace rss {
+
+/**
+ * Convert a list of elements to a list of values representing an RSS entry.
+ */
+const list<value> entryElementsToValues(const list<value>& e) {
+ const list<value> lt = filter<value>(selector(mklist<value>(element, "title")), e);
+ const value t = isNil(lt)? value(emptyString) : elementValue(car(lt));
+ const list<value> li = filter<value>(selector(mklist<value>(element, "link")), e);
+ const value i = isNil(li)? value(emptyString) : elementValue(car(li));
+ const list<value> ld = filter<value>(selector(mklist<value>(element, "description")), e);
+ return mklist<value>(t, i, elementValue(car(ld)));
+}
+
+/**
+ * Convert a list of elements to a list of values representing RSS entries.
+ */
+const list<value> entriesElementsToValues(const list<value>& e) {
+ if (isNil(e))
+ return e;
+ return cons<value>(entryElementsToValues(car(e)), entriesElementsToValues(cdr(e)));
+}
+
+/**
+ * Return true if a list of strings contains an RSS feed.
+ */
+const bool isRSSFeed(const list<string>& ls) {
+ if (!isXML(ls))
+ return false;
+ return contains(car(ls), "<rss");
+}
+
+/**
+ * Convert a list of strings to a list of values representing an RSS entry.
+ */
+const failable<list<value> > readRSSEntry(const list<string>& ilist) {
+ const list<value> e = readXML(ilist);
+ if (isNil(e))
+ return mkfailure<list<value> >("Empty entry");
+ return entryElementsToValues(car(e));
+}
+
+/**
+ * Convert a list of values representing an RSS entry to a value.
+ */
+const value entryValue(const list<value>& e) {
+ const list<value> v = elementsToValues(mklist<value>(caddr(e)));
+ return cons(car(e), mklist<value>(cadr(e), isList(car(v))? (value)cdr<value>(car(v)) : car(v)));
+}
+
+/**
+ * Convert a list of strings to a list of values representing an RSS feed.
+ */
+const failable<list<value> > readRSSFeed(const list<string>& ilist) {
+ const list<value> f = readXML(ilist);
+ if (isNil(f))
+ return mkfailure<list<value> >("Empty feed");
+ const list<value> c = filter<value>(selector(mklist<value>(element, "channel")), car(f));
+ const list<value> t = filter<value>(selector(mklist<value>(element, "title")), car(c));
+ const list<value> i = filter<value>(selector(mklist<value>(element, "link")), car(c));
+ const list<value> e = filter<value>(selector(mklist<value>(element, "item")), car(c));
+ if (isNil(e))
+ return mklist<value>(elementValue(car(t)), elementValue(car(i)));
+ return cons<value>(elementValue(car(t)), cons(elementValue(car(i)), entriesElementsToValues(e)));
+}
+
+/**
+ * Convert an RSS feed containing elements to an RSS feed containing values.
+ */
+const list<value> feedValuesLoop(const list<value> e) {
+ if (isNil(e))
+ return e;
+ return cons<value>(entryValue(car(e)), feedValuesLoop(cdr(e)));
+}
+
+const list<value> feedValues(const list<value>& e) {
+ return cons(car<value>(e), cons<value>(cadr<value>(e), feedValuesLoop(cddr<value>(e))));
+}
+
+/**
+ * Convert a list of values representing an RSS entry to a list of elements.
+ * The first two values in the list are the entry title and id.
+ */
+const list<value> entryElement(const list<value>& l) {
+ return list<value>()
+ + element + "item"
+ + (list<value>() + element + "title" + car(l))
+ + (list<value>() + element + "link" + cadr(l))
+ + (list<value>() + element + "description" + caddr(l));
+}
+
+/**
+ * Convert a list of values representing RSS entries to a list of elements.
+ */
+const list<value> entriesElements(const list<value>& l) {
+ if (isNil(l))
+ return l;
+ return cons<value>(entryElement(car(l)), entriesElements(cdr(l)));
+}
+
+/**
+ * Convert a list of values representing an RSS entry to an RSS entry.
+ * The first two values in the list are the entry id and title.
+ */
+template<typename R> const failable<R> writeRSSEntry(const lambda<R(const string&, const R)>& reduce, const R& initial, const list<value>& l) {
+ return writeXML<R>(reduce, initial, mklist<value>(entryElement(l)));
+}
+
+const failable<list<string> > writeRSSEntry(const list<value>& l) {
+ const failable<list<string> > ls = writeRSSEntry<list<string> >(rcons<string>, list<string>(), l);
+ if (!hasContent(ls))
+ return ls;
+ return reverse(list<string>(content(ls)));
+}
+
+/**
+ * Convert a list of values representing an RSS feed to an RSS feed.
+ * The first two values in the list are the feed id and title.
+ */
+template<typename R> const failable<R> writeRSSFeed(const lambda<R(const string&, const R)>& reduce, const R& initial, const list<value>& l) {
+ const list<value> c = list<value>()
+ + (list<value>() + element + "title" + car(l))
+ + (list<value>() + element + "link" + cadr(l))
+ + (list<value>() + element + "description" + car(l));
+ const list<value> ce = isNil(cddr(l))? c : append(c, entriesElements(cddr(l)));
+ const list<value> fe = list<value>()
+ + element + "rss" + (list<value>() + attribute + "version" + "2.0")
+ + append(list<value>() + element + "channel", ce);
+ return writeXML<R>(reduce, initial, mklist<value>(fe));
+}
+
+/**
+ * Convert a list of values representing an RSS feed to a list of strings.
+ * The first two values in the list are the feed id and title.
+ */
+const failable<list<string> > writeRSSFeed(const list<value>& l) {
+ const failable<list<string> > ls = writeRSSFeed<list<string>>(rcons<string>, list<string>(), l);
+ if (!hasContent(ls))
+ return ls;
+ return reverse(list<string>(content(ls)));
+}
+
+/**
+ * Convert an RSS entry containing a value to an RSS entry containing an item element.
+ */
+const list<value> entryValuesToElements(const list<value> val) {
+ return cons(car(val), cons(cadr(val), valuesToElements(mklist<value>(cons<value>("item", (list<value>)caddr(val))))));
+}
+
+/**
+ * Convert an RSS feed containing values to an RSS feed containing elements.
+ */
+const list<value> feedValuesToElementsLoop(const list<value> val) {
+ if (isNil(val))
+ return val;
+ return cons<value>(entryValuesToElements(car(val)), feedValuesToElementsLoop(cdr(val)));
+}
+
+const list<value> feedValuesToElements(const list<value>& val) {
+ return cons(car<value>(val), cons<value>(cadr<value>(val), feedValuesToElementsLoop(cddr<value>(val))));
+}
+
+}
+}
+
+#endif /* tuscany_rss_hpp */