Index: modules/aggregator/aggregator.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.module,v
retrieving revision 1.382
diff -u -r1.382 aggregator.module
--- modules/aggregator/aggregator.module	5 Jul 2008 05:57:00 -0000	1.382
+++ modules/aggregator/aggregator.module	25 Jul 2008 20:19:41 -0000
@@ -443,192 +443,50 @@
 }
 
 /**
- * Callback function used by the XML parser.
- */
-function aggregator_element_start($parser, $name, $attributes) {
-  global $item, $element, $tag, $items, $channel;
-
-  switch ($name) {
-    case 'IMAGE':
-    case 'TEXTINPUT':
-    case 'CONTENT':
-    case 'SUMMARY':
-    case 'TAGLINE':
-    case 'SUBTITLE':
-    case 'LOGO':
-    case 'INFO':
-      $element = $name;
-      break;
-    case 'ID':
-      if ($element != 'ITEM') {
-        $element = $name;
-      }
-    case 'LINK':
-      if (!empty($attributes['REL']) && $attributes['REL'] == 'alternate') {
-        if ($element == 'ITEM') {
-          $items[$item]['LINK'] = $attributes['HREF'];
-        }
-        else {
-          $channel['LINK'] = $attributes['HREF'];
-        }
-      }
-      break;
-    case 'ITEM':
-      $element = $name;
-      $item += 1;
-      break;
-    case 'ENTRY':
-      $element = 'ITEM';
-      $item += 1;
-      break;
-  }
-
-  $tag = $name;
-}
-
-/**
- * Call-back function used by the XML parser.
- */
-function aggregator_element_end($parser, $name) {
-  global $element;
-
-  switch ($name) {
-    case 'IMAGE':
-    case 'TEXTINPUT':
-    case 'ITEM':
-    case 'ENTRY':
-    case 'CONTENT':
-    case 'INFO':
-      $element = '';
-      break;
-    case 'ID':
-      if ($element == 'ID') {
-        $element = '';
-      }
-  }
-}
-
-/**
- * Callback function used by the XML parser.
- */
-function aggregator_element_data($parser, $data) {
-  global $channel, $element, $items, $item, $image, $tag;
-  $items += array($item => array());
-  switch ($element) {
-    case 'ITEM':
-      $items[$item] += array($tag => '');
-      $items[$item][$tag] .= $data;
-      break;
-    case 'IMAGE':
-    case 'LOGO':
-      $image += array($tag => '');
-      $image[$tag] .= $data;
-      break;
-    case 'LINK':
-      if ($data) {
-        $items[$item] += array($tag => '');
-        $items[$item][$tag] .= $data;
-      }
-      break;
-    case 'CONTENT':
-      $items[$item] += array('CONTENT' => '');
-      $items[$item]['CONTENT'] .= $data;
-      break;
-    case 'SUMMARY':
-      $items[$item] += array('SUMMARY' => '');
-      $items[$item]['SUMMARY'] .= $data;
-      break;
-    case 'TAGLINE':
-    case 'SUBTITLE':
-      $channel += array('DESCRIPTION' => '');
-      $channel['DESCRIPTION'] .= $data;
-      break;
-    case 'INFO':
-    case 'ID':
-    case 'TEXTINPUT':
-      // The sub-element is not supported. However, we must recognize
-      // it or its contents will end up in the item array.
-      break;
-    default:
-      $channel += array($tag => '');
-      $channel[$tag] .= $data;
-  }
-}
-
-/**
  * Checks a news feed for new items.
  *
  * @param $feed
  *   An associative array describing the feed to be refreshed.
  */
 function aggregator_refresh($feed) {
-  global $channel, $image;
+  // Retrieve feed.
+  $result = drupal_retrieve_feed($feed['url'], $feed['etag'], $feed['modified']);
+
+  if (count($result->items) == 0) {
+    db_query('UPDATE {aggregator_feed} SET checked = %d WHERE fid = %d', time(), $feed['fid']);
+    drupal_set_message(t('There is no new syndicated content from %site.', array('%site' => $feed['title'])));
+    return;
+  }
+  
+  if (isset($result->redirect)) {
+    $feed['url'] = $result->redirect_url;
+    watchdog('aggregator', 'Updated URL for feed %title to %url.', array('%title' => $feed['title'], '%url' => $feed['url']));
+  }
+
+  // Filter the items data.
+  if (aggregator_save_items($result->items, $feed)) {
+    if (!empty($result->image['LINK']) && !empty($result->image['URL']) && !empty($result->image['TITLE'])) {
+      // TODO: we should really use theme_image() here, but that only works with
+      // local images. It won't work with images fetched with a URL unless PHP version > 5.
+      $image = '<a href="' . check_url($result->image['LINK']) . '" class="feed-image"><img src="' . check_url($result->image['URL']) . '" alt="' . check_plain($result->image['TITLE']) . '" /></a>';
+    }
+    else {
+      $image = NULL;
+    }
 
-  // Generate conditional GET headers.
-  $headers = array();
-  if ($feed['etag']) {
-    $headers['If-None-Match'] = $feed['etag'];
-  }
-  if ($feed['modified']) {
-    $headers['If-Modified-Since'] = gmdate('D, d M Y H:i:s', $feed['modified']) . ' GMT';
-  }
-
-  // Request feed.
-  $result = drupal_http_request($feed['url'], $headers);
-
-  // Process HTTP response code.
-  switch ($result->code) {
-    case 304:
-      db_query('UPDATE {aggregator_feed} SET checked = %d WHERE fid = %d', time(), $feed['fid']);
-      drupal_set_message(t('There is no new syndicated content from %site.', array('%site' => $feed['title'])));
-      break;
-    case 301:
-      $feed['url'] = $result->redirect_url;
-      watchdog('aggregator', 'Updated URL for feed %title to %url.', array('%title' => $feed['title'], '%url' => $feed['url']));
-      // Do not break here.
-    case 200:
-    case 302:
-    case 307:
-      // Filter the input data.
-      if (aggregator_parse_feed($result->data, $feed)) {
-        $modified = empty($result->headers['Last-Modified']) ? 0 : strtotime($result->headers['Last-Modified']);
-
-        // Prepare the channel data.
-        foreach ($channel as $key => $value) {
-          $channel[$key] = trim($value);
-        }
-
-        // Prepare the image data (if any).
-        foreach ($image as $key => $value) {
-          $image[$key] = trim($value);
-        }
-
-        if (!empty($image['LINK']) && !empty($image['URL']) && !empty($image['TITLE'])) {
-          // TODO: we should really use theme_image() here, but that only works with
-          // local images. It won't work with images fetched with a URL unless PHP version > 5.
-          $image = '<a href="' . check_url($image['LINK']) . '" class="feed-image"><img src="' . check_url($image['URL']) . '" alt="' . check_plain($image['TITLE']) . '" /></a>';
-        }
-        else {
-          $image = NULL;
-        }
-
-        $etag = empty($result->headers['ETag']) ? '' : $result->headers['ETag'];
-        // Update the feed data.
-        db_query("UPDATE {aggregator_feed} SET url = '%s', checked = %d, link = '%s', description = '%s', image = '%s', etag = '%s', modified = %d WHERE fid = %d", $feed['url'], time(), $channel['LINK'], $channel['DESCRIPTION'], $image, $etag, $modified, $feed['fid']);
+    // Update the feed data.
+    db_query("UPDATE {aggregator_feed} SET url = '%s', checked = %d, link = '%s', description = '%s', image = '%s', etag = '%s', modified = %d WHERE fid = %d", $feed['url'], time(), $result->channel['LINK'], $result->channel['DESCRIPTION'], $image, $result->etag, $result->modified, $feed['fid']);
 
-        // Clear the cache.
-        cache_clear_all();
+    // Clear the cache.
+    cache_clear_all();
 
-        watchdog('aggregator', 'There is new syndicated content from %site.', array('%site' => $feed['title']));
-        drupal_set_message(t('There is new syndicated content from %site.', array('%site' => $feed['title'])));
-        break;
-      }
-      $result->error = t('feed not parseable');
-      // Do not break here..
-    default:
-      watchdog('aggregator', 'The feed from %site seems to be broken, due to "%error".', array('%site' => $feed['title'], '%error' => $result->code . ' ' . $result->error), WATCHDOG_WARNING);
-      drupal_set_message(t('The feed from %site seems to be broken, because of error "%error".', array('%site' => $feed['title'], '%error' => $result->code . ' ' . $result->error)));
-      module_invoke('system', 'check_http_request');
+    watchdog('aggregator', 'There is new syndicated content from %site.', array('%site' => $feed['title']));
+    drupal_set_message(t('There is new syndicated content from %site.', array('%site' => $feed['title'])));
+  }
+  else {
+    $result->error = t('feed not parseable');
+    watchdog('aggregator', 'The feed from %site seems to be broken, due to "%error".', array('%site' => $feed['title'], '%error' => $result->code . ' ' . $result->error), WATCHDOG_WARNING);
+    drupal_set_message(t('The feed from %site seems to be broken, because of error "%error".', array('%site' => $feed['title'], '%error' => $result->code . ' ' . $result->error)));
   }
 }
 
@@ -673,7 +531,7 @@
 }
 
 /**
- * Parse a feed and store its items.
+ * Store a feed's items.
  *
  * @param $data
  *   The feed data.
@@ -682,27 +540,7 @@
  * @return
  *   FALSE on error, TRUE otherwise.
  */
-function aggregator_parse_feed(&$data, $feed) {
-  global $items, $image, $channel;
-
-  // Unset the global variables before we use them.
-  unset($GLOBALS['element'], $GLOBALS['item'], $GLOBALS['tag']);
-  $items = array();
-  $image = array();
-  $channel = array();
-
-  // Parse the data.
-  $xml_parser = drupal_xml_parser_create($data);
-  xml_set_element_handler($xml_parser, 'aggregator_element_start', 'aggregator_element_end');
-  xml_set_character_data_handler($xml_parser, 'aggregator_element_data');
-
-  if (!xml_parse($xml_parser, $data, 1)) {
-    watchdog('aggregator', 'The feed from %site seems to be broken, due to an error "%error" on line %line.', array('%site' => $feed['title'], '%error' => xml_error_string(xml_get_error_code($xml_parser)), '%line' => xml_get_current_line_number($xml_parser)), WATCHDOG_WARNING);
-    drupal_set_message(t('The feed from %site seems to be broken, because of error "%error" on line %line.', array('%site' => $feed['title'], '%error' => xml_error_string(xml_get_error_code($xml_parser)), '%line' => xml_get_current_line_number($xml_parser))), 'error');
-    return FALSE;
-  }
-  xml_parser_free($xml_parser);
-
+function aggregator_save_items(&$items, $feed) {
   // We reverse the array such that we store the first item last, and the last
   // item first. In the database, the newest item should be at the top.
   $items = array_reverse($items);
@@ -712,11 +550,6 @@
   foreach ($items as $item) {
     unset($title, $link, $author, $description, $guid);
 
-    // Prepare the item:
-    foreach ($item as $key => $value) {
-      $item[$key] = trim($value);
-    }
-
     // Resolve the item's title. If no title is found, we use up to 40
     // characters of the description ending at a word boundary, but not
     // splitting potential entities.
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.779
diff -u -r1.779 common.inc
--- includes/common.inc	19 Jul 2008 10:38:13 -0000	1.779
+++ includes/common.inc	25 Jul 2008 20:19:41 -0000
@@ -3577,3 +3577,53 @@
   }
   variable_set('css_js_query_string', $new_character . substr($string_history, 0, 19));
 }
+
+/**
+ * Fetch a feed from URL.
+ *
+ * @param $url
+ *   A string containing a fully qualified URI.
+ * @param $etag
+ *   Optional Etag for the header checks.
+ * @param $modified
+ *   Optional timestamp of last check. Will return an empty array if unmodified.
+ * @return
+ *   An object containing the feed data and headers.
+ */
+function drupal_retrieve_feed($url, $etag = NULL, $modified = NULL) {
+  // Generate conditional GET headers.
+  $headers = array();
+  if (isset($etag)) {
+    $headers['If-None-Match'] = $etag;
+  }
+  if (isset($modified)) {
+    $headers['If-Modified-Since'] = gmdate('D, d M Y H:i:s', $modified) . ' GMT';
+  }
+
+  // Request feed.
+  $result = drupal_http_request($url, $headers);
+
+  // Process HTTP response code.
+  switch ($result->code) {
+    case 304:
+      return array();
+      break;
+    case 301:
+      $feed->redirect = $result->redirect_url;
+      $url = $result->redirect_url;
+      // Do not break here.
+    case 200:
+    case 302:
+    case 307:
+      require_once './includes/feed.inc';
+      $feed = feed_parse($result->data, $url);
+      if ($feed) {
+        $feed->etag = empty($result->headers['ETag']) ? '' : $result->headers['ETag'];
+        $feed->modified = empty($result->headers['Last-Modified']) ? 0 : strtotime($result->headers['Last-Modified']);
+        return $feed;
+      }
+      // Do not break here...
+    default:
+      module_invoke('system', 'check_http_request');
+  }
+}
Index: includes/feed.inc
===================================================================
RCS file: includes/feed.inc
diff -N includes/feed.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/feed.inc	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,176 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * API for parsing RSS, RDF and Atom formatted feeds.
+ */
+
+/**
+ * Callback function used by the XML parser.
+ */
+function _feed_element_start($parser, $name, $attributes) {
+  global $item, $element, $tag, $items, $channel;
+
+  switch ($name) {
+    case 'IMAGE':
+    case 'TEXTINPUT':
+    case 'CONTENT':
+    case 'SUMMARY':
+    case 'TAGLINE':
+    case 'SUBTITLE':
+    case 'LOGO':
+    case 'INFO':
+      $element = $name;
+      break;
+    case 'ID':
+      if ($element != 'ITEM') {
+        $element = $name;
+      }
+    case 'LINK':
+      if (!empty($attributes['REL']) && $attributes['REL'] == 'alternate') {
+        if ($element == 'ITEM') {
+          $items[$item]['LINK'] = $attributes['HREF'];
+        }
+        else {
+          $channel['LINK'] = $attributes['HREF'];
+        }
+      }
+      break;
+    case 'ITEM':
+      $element = $name;
+      $item += 1;
+      break;
+    case 'ENTRY':
+      $element = 'ITEM';
+      $item += 1;
+      break;
+  }
+
+  $tag = $name;
+}
+
+/**
+ * Call-back function used by the XML parser.
+ */
+function _feed_element_end($parser, $name) {
+  global $element;
+
+  switch ($name) {
+    case 'IMAGE':
+    case 'TEXTINPUT':
+    case 'ITEM':
+    case 'ENTRY':
+    case 'CONTENT':
+    case 'INFO':
+      $element = '';
+      break;
+    case 'ID':
+      if ($element == 'ID') {
+        $element = '';
+      }
+  }
+}
+
+/**
+ * Callback function used by the XML parser.
+ */
+function _feed_element_data($parser, $data) {
+  global $channel, $element, $items, $item, $image, $tag;
+  $items += array($item => array());
+  switch ($element) {
+    case 'ITEM':
+      $items[$item] += array($tag => '');
+      $items[$item][$tag] .= $data;
+      break;
+    case 'IMAGE':
+    case 'LOGO':
+      $image += array($tag => '');
+      $image[$tag] .= $data;
+      break;
+    case 'LINK':
+      if ($data) {
+        $items[$item] += array($tag => '');
+        $items[$item][$tag] .= $data;
+      }
+      break;
+    case 'CONTENT':
+      $items[$item] += array('CONTENT' => '');
+      $items[$item]['CONTENT'] .= $data;
+      break;
+    case 'SUMMARY':
+      $items[$item] += array('SUMMARY' => '');
+      $items[$item]['SUMMARY'] .= $data;
+      break;
+    case 'TAGLINE':
+    case 'SUBTITLE':
+      $channel += array('DESCRIPTION' => '');
+      $channel['DESCRIPTION'] .= $data;
+      break;
+    case 'INFO':
+    case 'ID':
+    case 'TEXTINPUT':
+      // The sub-element is not supported. However, we must recognize
+      // it or its contents will end up in the item array.
+      break;
+    default:
+      $channel += array($tag => '');
+      $channel[$tag] .= $data;
+  }
+}
+
+/**
+ * Parse a feed.
+ *
+ * @param $data
+ *   The feed data.
+ * @param $url
+ *   The URL to the feed.
+ * @return
+ *   FALSE on error, an object on success.
+ */
+function feed_parse(&$data, $url) {
+  global $items, $image, $channel;
+
+  // Unset the global variables before we use them.
+  unset($GLOBALS['element'], $GLOBALS['item'], $GLOBALS['tag']);
+  $items = array();
+  $image = array();
+  $channel = array();
+
+  // Parse the data.
+  $xml_parser = drupal_xml_parser_create($data);
+  xml_set_element_handler($xml_parser, '_feed_element_start', '_feed_element_end');
+  xml_set_character_data_handler($xml_parser, '_feed_element_data');
+
+  if (!xml_parse($xml_parser, $data, 1)) {
+    watchdog('feed parser', 'The feed from %url seems to be broken, due to an error "%error" on line %line.', array('%url' => $url, '%error' => xml_error_string(xml_get_error_code($xml_parser)), '%line' => xml_get_current_line_number($xml_parser)), WATCHDOG_WARNING);
+    return FALSE;
+  }
+  xml_parser_free($xml_parser);
+
+  // Prepare the channel data.
+  foreach ($channel as $key => $value) {
+    $channel[$key] = trim($value);
+  }
+
+  // Prepare the image data (if any).
+  foreach ($image as $key => $value) {
+    $image[$key] = trim($value);
+  }
+
+  $result = new stdClass();
+  $result->channel = $channel;
+  $result->image = $image;
+  $result->items = array();
+
+  foreach ($items as $item) {
+    // Prepare the item.
+    foreach ($item as $key => $value) {
+      $item[$key] = trim($value);
+    }
+    $result->items[] = $item;
+  }
+
+  return $result;
+}
Index: includes/tests/feed.test
===================================================================
RCS file: includes/tests/feed.test
diff -N includes/tests/feed.test
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/tests/feed.test	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,302 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Unit tests for the feed parsing API.
+ */
+
+/**
+ * Enter description here...
+ */
+class FeedRSS091ParsingTestCase extends DrupalWebTestCase {
+  /**
+   * Implementation of getInfo().
+   */
+  function getInfo() {
+    return array(
+      'name' => t('RSS 0.91 format parsing'),
+      'description' => t('Test feed parsing with RSS 0.91 formatted sample feeds.'),
+      'group' => t('System'),
+    );
+  }
+  
+  function getRSS091Sample() {
+    $data = <<<EOT
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<rss version="0.91">
+  <channel>
+    <title>WriteTheWeb</title> 
+    <link>http://writetheweb.com</link> 
+    <description>News for web users that write back</description> 
+    <language>en-us</language> 
+    <copyright>Copyright 2000, WriteTheWeb team.</copyright> 
+    <managingEditor>editor@writetheweb.com</managingEditor> 
+    <webMaster>webmaster@writetheweb.com</webMaster> 
+    <image>
+      <title>WriteTheWeb</title> 
+      <url>http://writetheweb.com/images/mynetscape88.gif</url> 
+      <link>http://writetheweb.com</link> 
+      <width>88</width> 
+      <height>31</height> 
+      <description>News for web users that write back</description> 
+      </image>
+    <item>
+      <title>Giving the world a pluggable Gnutella</title> 
+      <link>http://writetheweb.com/read.php?item=24</link> 
+      <description>WorldOS is a framework on which to build programs that work like Freenet or Gnutella -allowing distributed applications using peer-to-peer routing.</description> 
+      </item>
+    </channel>
+  </rss>
+EOT;
+    return $data;
+  }
+  
+  function testRSS091Sample() {
+    if (!drupal_function_exists('feed_parse')) {
+      return;
+    }
+
+    $feed = feed_parse($this->getRSS091Sample(), '');
+
+    $this->assertEqual($feed->channel['TITLE'], 'WriteTheWeb', t('Channel title retrieved from RSS 0.91 formatted sample.'));
+    $this->assertEqual($feed->channel['LINK'], 'http://writetheweb.com', t('Channel link retrieved from RSS 0.91 formatted sample.'));
+    $this->assertEqual($feed->channel['DESCRIPTION'], 'News for web users that write back', t('Channel description retrieved from RSS 0.91 formatted sample.'));
+
+    $this->assertEqual($feed->image['TITLE'], 'WriteTheWeb', t('Image title retrieved from RSS 0.91 formatted sample.'));
+    $this->assertEqual($feed->image['LINK'], 'http://writetheweb.com', t('Image link retrieved from RSS 0.91 formatted sample.'));
+    $this->assertEqual($feed->image['URL'], 'http://writetheweb.com/images/mynetscape88.gif', t('Image URL retrieved from RSS 0.91 formatted sample.'));
+
+    $test_item = array();
+    foreach ($feed->items as $item) {
+      $test_item = $item;
+    }
+    $this->assertEqual($test_item['TITLE'], 'Giving the world a pluggable Gnutella', t('Item title retrieved from RSS 0.91 formatted sample.'));
+    $this->assertEqual($test_item['LINK'], 'http://writetheweb.com/read.php?item=24', t('Item link retrieved from RSS 0.91 formatted sample.'));
+    $this->assertEqual($test_item['DESCRIPTION'], 'WorldOS is a framework on which to build programs that work like Freenet or Gnutella -allowing distributed applications using peer-to-peer routing.', t('Item description retrieved from RSS 0.91 formatted sample.'));
+  }
+}
+
+/**
+ * Enter description here...
+ */
+class FeedRSS092ParsingTestCase extends DrupalWebTestCase {
+  /**
+   * Implementation of getInfo().
+   */
+  function getInfo() {
+    return array(
+      'name' => t('RSS 0.92 format parsing'),
+      'description' => t('Test feed parsing with RSS 0.92 formatted sample feeds.'),
+      'group' => t('System'),
+    );
+  }
+  
+  function getRSS092Sample() {
+    $data = <<<EOT
+<?xml version="1.0"?>
+<!-- RSS generation done by 'Radio UserLand' on Fri, 13 Apr 2001 19:23:02 GMT -->
+<rss version="0.92">
+  <channel>
+    <title>Dave Winer: Grateful Dead</title>
+    <link>http://www.scripting.com/blog/categories/gratefulDead.html</link>
+    <description>A high-fidelity Grateful Dead song every day. This is where we're experimenting with enclosures on RSS news items that download when you're not using your computer. If it works (it will) it will be the end of the Click-And-Wait multimedia experience on the Internet. </description>
+    <lastBuildDate>Fri, 13 Apr 2001 19:23:02 GMT</lastBuildDate>
+    <docs>http://backend.userland.com/rss092</docs>
+    <managingEditor>dave@userland.com (Dave Winer)</managingEditor>
+    <webMaster>dave@userland.com (Dave Winer)</webMaster>
+    <cloud domain="data.ourfavoritesongs.com" port="80" path="/RPC2" registerProcedure="ourFavoriteSongs.rssPleaseNotify" protocol="xml-rpc"/>
+    <item>
+      <description>It's been a few days since I added a song to the Grateful Dead channel. Now that there are all these new Radio users, many of whom are tuned into this channel (it's #16 on the hotlist of upstreaming Radio users, there's no way of knowing how many non-upstreaming users are subscribing, have to do something about this..). Anyway, tonight's song is a live version of Weather Report Suite from Dick's Picks Volume 7. It's wistful music. Of course a beautiful song, oft-quoted here on Scripting News. &lt;i&gt;A little change, the wind and rain.&lt;/i&gt;
+</description>
+      <enclosure url="http://www.scripting.com/mp3s/weatherReportDicksPicsVol7.mp3" length="6182912" type="audio/mpeg"/>
+      </item>
+    </channel>
+  </rss>
+EOT;
+    return $data;
+  }
+  
+  function testRSS092Sample() {
+    if (!drupal_function_exists('feed_parse')) {
+      return;
+    }
+
+    $feed = feed_parse($this->getRSS092Sample(), '');
+
+    $this->assertEqual($feed->channel['TITLE'], 'Dave Winer: Grateful Dead', t('Channel title retrieved from RSS 0.92 formatted sample.'));
+    $this->assertEqual($feed->channel['LINK'], 'http://www.scripting.com/blog/categories/gratefulDead.html', t('Channel link retrieved from RSS 0.92 formatted sample.'));
+    $this->assertEqual($feed->channel['DESCRIPTION'], "A high-fidelity Grateful Dead song every day. This is where we're experimenting with enclosures on RSS news items that download when you're not using your computer. If it works (it will) it will be the end of the Click-And-Wait multimedia experience on the Internet.", t('Channel description retrieved from RSS 0.92 formatted sample.'));
+
+    $test_item = array();
+    foreach ($feed->items as $item) {
+      $test_item = $item;
+    }
+
+    $this->assertEqual($test_item['DESCRIPTION'], "It's been a few days since I added a song to the Grateful Dead channel. Now that there are all these new Radio users, many of whom are tuned into this channel (it's #16 on the hotlist of upstreaming Radio users, there's no way of knowing how many non-upstreaming users are subscribing, have to do something about this..). Anyway, tonight's song is a live version of Weather Report Suite from Dick's Picks Volume 7. It's wistful music. Of course a beautiful song, oft-quoted here on Scripting News. <i>A little change, the wind and rain.</i>", t('Item description retrieved from RSS 0.92 formatted sample.'));
+  }
+}
+
+/**
+ * Enter description here...
+ */
+class FeedRSS2ParsingTestCase extends DrupalWebTestCase {
+  /**
+   * Implementation of getInfo().
+   */
+  function getInfo() {
+    return array(
+      'name' => t('RSS 2.0 format parsing'),
+      'description' => t('Test feed parsing with RSS 2.0 formatted sample feeds.'),
+      'group' => t('System'),
+    );
+  }
+  
+  function getRSS2Sample() {
+    $data = <<<EOT
+<?xml version="1.0"?>
+<rss version="2.0">
+   <channel>
+      <title>Liftoff News</title>
+      <link>http://liftoff.msfc.nasa.gov/</link>
+      <description>Liftoff to Space Exploration.</description>
+      <language>en-us</language>
+      <pubDate>Tue, 10 Jun 2003 04:00:00 GMT</pubDate>
+      <lastBuildDate>Tue, 10 Jun 2003 09:41:01 GMT</lastBuildDate>
+      <docs>http://blogs.law.harvard.edu/tech/rss</docs>
+      <generator>Weblog Editor 2.0</generator>
+      <managingEditor>editor@example.com</managingEditor>
+      <webMaster>webmaster@example.com</webMaster>
+      <item>
+         <title>Star City</title>
+         <link>http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp</link>
+         <description>How do Americans get ready to work with Russians aboard the International Space Station? They take a crash course in culture, language and protocol at Russia's &lt;a href="http://howe.iki.rssi.ru/GCTC/gctc_e.htm"&gt;Star City&lt;/a&gt;.</description>
+         <pubDate>Tue, 03 Jun 2003 09:39:21 GMT</pubDate>
+         <guid>http://liftoff.msfc.nasa.gov/2003/06/03.html#item573</guid>
+      </item>
+   </channel>
+</rss>
+EOT;
+    return $data;
+  }
+  
+  function testRSS2Sample() {
+    if (!drupal_function_exists('feed_parse')) {
+      return;
+    }
+
+    $feed = feed_parse($this->getRSS2Sample(), '');
+
+    $this->assertEqual($feed->channel['TITLE'], 'Liftoff News', t('Channel title retrieved from RSS 2.0 formatted sample.'));
+    $this->assertEqual($feed->channel['LINK'], 'http://liftoff.msfc.nasa.gov/', t('Channel link retrieved from RSS 2.0 formatted sample.'));
+    $this->assertEqual($feed->channel['DESCRIPTION'], "Liftoff to Space Exploration.", t('Channel description retrieved from RSS 2.0 formatted sample.'));
+
+    $test_item = array();
+    foreach ($feed->items as $item) {
+      $test_item = $item;
+    }
+
+    $this->assertEqual($test_item['TITLE'], 'Star City', t('Item title retrieved from RSS 2.0 formatted sample.'));
+    $this->assertEqual($test_item['LINK'], 'http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp', t('Item link retrieved from RSS 2.0 formatted sample.'));
+    $this->assertEqual($test_item['DESCRIPTION'], "How do Americans get ready to work with Russians aboard the International Space Station? They take a crash course in culture, language and protocol at Russia's <a href=\"http://howe.iki.rssi.ru/GCTC/gctc_e.htm\">Star City</a>.", t('Item description retrieved from RSS 2.0 formatted sample.'));
+  }
+}
+
+/**
+ * Enter description here...
+ */
+class FeedRDFParsingTestCase extends DrupalWebTestCase {
+  /**
+   * Implementation of getInfo().
+   */
+  function getInfo() {
+    return array(
+      'name' => t('RDF format parsing'),
+      'description' => t('Test feed parsing with RDF formatted sample feeds.'),
+      'group' => t('System'),
+    );
+  }
+  
+  function getRDFSample() {
+    $data = <<<EOT
+<?xml version="1.0"?>
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+  xmlns:dc="http://purl.org/dc/elements/1.1/">
+  <rdf:Description rdf:about="http://www.w3.org/">
+    <dc:title>World Wide Web Consortium</dc:title> 
+  </rdf:Description>
+</rdf:RDF>
+EOT;
+    return $data;
+  }
+  
+  function testRDFSample() {
+    if (!drupal_function_exists('feed_parse')) {
+      return;
+    }
+
+    $feed = feed_parse($this->getRDFSample(), '');
+
+    $this->assertEqual($feed->channel['DC:TITLE'], 'World Wide Web Consortium', t('Channel title retrieved from RDF formatted sample.'));
+  }
+}
+
+/**
+ * Enter description here...
+ */
+class FeedAtomParsingTestCase extends DrupalWebTestCase {
+  /**
+   * Implementation of getInfo().
+   */
+  function getInfo() {
+    return array(
+      'name' => t('Atom format parsing'),
+      'description' => t('Test feed parsing with Atom formatted sample feeds.'),
+      'group' => t('System'),
+    );
+  }
+  
+  function getAtomSample() {
+    $data = <<<EOT
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+  <title>Example Feed</title>
+  <link href="http://example.org/"/>
+  <updated>2003-12-13T18:30:02Z</updated>
+  <author>
+    <name>John Doe</name>
+  </author>
+  <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>
+  <entry>
+    <title>Atom-Powered Robots Run Amok</title>
+    <link href="http://example.org/2003/12/13/atom03"/>
+    <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
+    <updated>2003-12-13T18:30:02Z</updated>
+    <summary>Some text.</summary>
+  </entry>
+</feed>
+EOT;
+    return $data;
+  }
+  
+  function testAtomSample() {
+    if (!drupal_function_exists('feed_parse')) {
+      return;
+    }
+
+    $feed = feed_parse($this->getAtomSample(), '');
+
+    drupal_set_message(print_r($feed));
+    $this->assertEqual($feed->channel['TITLE'], 'Example Feed', t('Channel title retrieved from Atom formatted sample.'));
+    $this->assertEqual($feed->channel['LINK'], 'http://example.org/', t('Channel link retrieved from Atom formatted sample.'));
+
+    $test_item = array();
+    foreach ($feed->items as $item) {
+      $test_item = $item;
+    }
+
+    $this->assertEqual($test_item['TITLE'], 'Atom-Powered Robots Run Amok', t('Item title retrieved from Atom formatted sample.'));
+    $this->assertEqual($test_item['LINK'], 'http://example.org/2003/12/13/atom03', t('Item link retrieved from Atom formatted sample.'));
+    $this->assertEqual($test_item['SUMMARY'], "Some text.", t('Item description retrieved from Atom formatted sample.'));
+  }
+}
