Monday, November 08, 2010 at 12:07 AM.
system.verbs.builtins.xml.rss.compileService
on compileService (adrservice, flSaveData=false, adrStoryArrivedCallback=nil) {
<<Changes
<<11/30/08; 6:53:57 AM by DW
<<Look for a Media-RSS thumbnail each item and add it to the display of the item if present.
<<12/16/03; 2:52:12 AM by JES
<<Added support for the RSS 2.0 pubDate element, whose value is stored in the data sub-table of each item. Also store the value of author and comments if present in the data table for RSS 2.0 feeds.
<<9/5/03; 9:18:11 PM by DW
<<Eric Soroos pointed out that item-level categories weren't making it through to the items in the database. A simple fix made it work, per his suggestion.
<<http://groups.yahoo.com/group/radio-dev/message/7954
<<3/5/03; 3:31:32 PM by JES
<<When doing RSS auto-discovery, look also for the newer style <link> element with type="application/xml+rss" and title="RSS" attributes as well as the older style <link> with type="text/xml" and title="xml". Fixes a bug which would cause RSS autodiscovery not to work.
<<See: http://radio.userland.com/radioMacrosHeadLinks#rss
<<1/16/03; 5:38:55 AM by DW
<<Per Sam Ruby's report [1], changed the code in processing categories on items to only record the category if the domain is not specified.
<<[1] http://www.intertwingly.net/blog/1125.html
<<11/16/02; 11:05:27 AM by DW
<<Cleaned up implementation of guids and permalinks. If the feed specifies the isPermaLink attribute, our code would get confused. That confusion has been eliminated. But it's even worse than it appears. If isPermaLink is false, we wouldn't use the guid at all. Added a new element to sub-elements of the items array, guid, which holds the value of the guid element if isPermaLink is false. Modified addToHistory to respect either guid or permalink element. Thanks to Roger Turner for the excellent report.
<<11/8/02; 10:59:32 AM by DW
<<For 0.91 and greater feeds, if it has a skipHours element, create a skipHoursList element in the compilation table, containing the hours we can skip, in the local timezone.
<<10/30/02; 9:35:20 AM by DW
<<Get the changes URL from the channel level <category> element, per this comment on SN:
<<http://scriptingnews.userland.com/backissues/2002/10/29#weblogscomForRssProgress
<<It's stored in the compilation table for each service at changesUrl.
<<10/18/02; 11:00:20 AM by DW
<<If RSS 2.0 set format to RSS2, but do it after all processing so there's no chance of breakage. We want Morton Fredricksen to be able to tell the diff betw 0.92 and 2.0.
<<10/17/02; 5:11:29 PM by JES
<<Don't add the comment link to the story text. Instead, add it to the item sub-table of the compilation table. It will be picked up by xml.aggregator.storyArrivedCallback.
<<10/17/02; 12:48:09 AM by JES
<<Add support for RSS 2.0 item-level comments element.
<<10/10/02; 7:32:53 AM by DW
<<Add support for RSS 2.0 item-level <author> element.
<<9/29/02; 7:59:37 PM by JES
<<Added support for moduleDrivers, which are used to implement the aggregator-side of modules in RSS 2.0.
<<For RSS 2.0 feeds, skip <item>s and sub-elements thereof, which aren't in the default namespace.
<<9/22/02; 9:51:55 AM by DW
<<Release the guid-aware version.
<<9/18/02; 1:01:07 PM by DW
<<See addToHistory. As we transition to RSS 2.0 feeds, we have to check the history table for items that are indexed by the title and by the permalink. If we don't as soon as a feed switches over, all the items become "new" and the aggregator shows them all. Even though it's only transitional people will report it as a bug. So we check both.
<<9/16/02; 11:05:14 AM by DW
<<First feature derived from RSS 2.0. If a channel is RSS 2.0, for each item that has a guid that is a permalink, add it to the history table indexed by the guid, not the mashing of the title and the link. This means that items will not reappear just because the text changed.
<<6/1/02; 3:12:26 PM by DW
<<Sjoerd Visscher reports: "It's a nice start, but it doesn't work on my site. Why? Because Radio tries to parse the page as xml, and if it fails, it looks for a link element. But this page is valid XML, so that doesn't work. But I'm sure Dave is already working on that."
<<http://w3future.com/weblog/2002/06/02.html#a108
<<5/31/02; 4:58:09 PM by DW
<<If the XML fails to compile, look for a <link> element, following very rigid syntax rules, and try to compile it. If it works, set adrservice^.urlChanged to the new URL. Code that calls this must watch out for it. For an example, see xml.aggregator.subscribeService.
<<http://frontier.userland.com/aggregatorAutoDiscovery
<<3/17/02; 4:16:05 PM by DW
<<In looking for the name of the top-level element of an unknown type feed, don't stop on DOCTYPE.
<<http://radio.userland.com/discuss/msgReader$12010
<<3/15/02; 2:20:50 PM by DW
<<Add a driver architecture to the aggregator so that new formats can be added without changing the core of the aggregator.
<<http://radio.userland.com/aggregatorDriverArchitecture
<<12/12/01; 5:21:54 PM by JES
<<Pass in true for the value of flSkipMalformedEntities when calling xml.entitydecode. When adding an item to the history if sizeOf (title) > 0, don't add the item. (See locally declared addToHistory.)
<<2/22/01; 9:29:50 PM by JES
<<If this is a Mac, convert to Mac text when decoding strings in the service XML.
<<2/16/01; 12:21:35 PM by PBS
<<Some elements weren't being decoded; now they all are.
<<1/12/01; 6:06:29 AM by DW
<<An important fix. In 0.92 the <source> sub-element of <item> is optional, but we didn't treat it that way, the compilation would fail if any item didn't have a <source> sub-element.
<<1/11/01; 6:32:59 PM by DW
<<Add support for <enclosure>s.
<<1/5/01; 5:20:16 PM by DW
<<Add support for publish-and-subscribe notification.
<<1/1/01; 11:54:32 AM by DW
<<Hit the limit on the size of addresses that can be processed by xml.getAddressList.
<<I could re-code all the loops over the items to work around this limit or I could do something more clever, work with a local copy of the xmlstruct table. This would probably be faster too.
<<In fact in earlier versions of this script it worked this way, explaining why I'm hitting the problem on some channels now. Interesting.
<<Thursday, December 28, 2000 at 7:26:03 AM by DW
<<Added adrStoryArrivedCallback, it's called when a new story has arrived. You can store it in a database, or whatever else you might want to do.
<<Wednesday, December 27, 2000 at 2:27:50 PM by DW
<<Added flSaveData boolean, if true, we remember the data behind the title in the item table. This is needed for processing RSS channels in Radio's outliner, we need the text and the link separated, unmashed together.
<<Wednesday, December 27, 2000 at 12:17:26 PM by DW
<<Moved into Radio.root and Frontier.root so it can be accessed by Manila and the new nodetype code in 7.0.
<<Sunday, December 17, 2000 at 10:49:04 AM by DW
<<Radically simplified for Radio use. Lots of ancient stuff swept away.
<<6/19/99; 9:34:46 PM by DW
<<Created.
on decode (s) {
return (xml.rss.decodeString (s))};
xml.rss.init (); //set up user.xml.rss
local (adrcompilation = @adrservice^.compilation);
if not defined (adrcompilation^) {
new (tabletype, adrcompilation)};
local (format = "unknown");
local (xstruct, adrxstruct = @xstruct);
bundle { //comple the xml text, set the format
try { //compile the xmltext
xml.compile (string (adrservice^.xmltext), adrxstruct);
bundle { //if top level element is <html> throw an error, 6/1/02 by DW
local (htmllist = xml.getaddresslist (adrxstruct, "html"));
if sizeof (htmllist) > 0 {
scriptError ("rssAutoDetect")}}}
else { //failed, look for a suitable <link> element
<<5/31/02; 4:38:51 PM by DW
<<See if we can find a suitable <link> element, fumble around with the HTML.
<<<link rel="alternate" type="text/xml" title="XML" href="http://radio.weblogs.com/0001015/rss.xml">
<<The pattern-match is very crude. If the attributes aren't all present, it fails. If they aren't in the correct order, it fails. Perhaps we'll get more sophisticated later, but I really don't want to add an HTML parser here. ;->
<<If we can, then try compiling that, and inform the upper level code of what happened.
local (errorstring = tryError);
local (sourcetext = string (adrservice^.xmltext), s = string.lower (sourcetext));
local (pattern = "<link rel=\"alternate\" type=\"text/xml\" title=\"xml\" href=\"");
local (ix = string.patternmatch (pattern, s));
if ix == 0 { //look for the newer type="application/rss+xml" title="RSS" attribute
<<JES 3/5/03: see: http://radio.userland.com/radioMacrosHeadLinks#rss
<<The older format is documented here: http://radio.userland.com/aggregatorAutoDiscovery#caveatForWeblogEditors
pattern = "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"RSS\" href=\"";
ix = string.patternmatch (pattern, s)};
if ix == 0 { //re-throw the error
scriptError (errorstring)};
local (url = string.delete (sourcetext, 1, ix + sizeof (pattern) - 1));
url = string.nthfield (url, "\"", 1);
adrservice^.xmltext = tcp.httpReadUrl (url);
xml.compile (string (adrservice^.xmltext), adrxstruct);
adrservice^.urlChanged = url}; //signal to upper levels
adrservice^.xmlstruct = adrxstruct^;
try { //check for RSS 0.90
xml.getAddress (adrxstruct, "RDF");
format = "RSS1"}
else {
try {
xml.getAddress (adrxstruct, "scriptingNews");
format = "scriptingNews"}
else {
try {
xml.getAddress (adrxstruct, "rss");
format = "RSS"}}}};
local (adrhistory = @adrcompilation^.itemHistory);
bundle { //initialize itemHistory table
local (i);
if not defined (adrhistory^) {
new (tabletype, adrhistory)};
for i = 1 to sizeof (adrhistory^) {
adrhistory^ [i] = false}};
<<on addToHistory (adritem, categorylist={})
<<bundle //if the item has a permalink, use that
<<if defined (adritem^.permalink)
<<local (adrinhistory = @adrhistory^.[adritem^.permalink])
<<if not defined (adrinhistory^) //it's a new story
<<local (flskipnew = false)
<<if sizeof (adritem^.title) > 0
<<try {flskipnew = defined (adrhistory^.[adritem^.title])} //a newly 2.0'd feed
<<if not flskipnew
<<if adrStoryArrivedCallback != nil
<<adrStoryArrivedCallback^ (adrservice, adritem)
<<adrinhistory^ = true //it's current
<<return
<<
<<local (title = adritem^.title)
<<if sizeOf (title) > 0
<<local (adrinhistory = @adrhistory^.[title])
<<if not defined (adrinhistory^) //it's a new story
<<if adrStoryArrivedCallback != nil
<<adrStoryArrivedCallback^ (adrservice, adritem)
<<adrinhistory^ = true //it's current
on addToHistory (adritem, categorylist={}) {
bundle { //if the item has a permalink, use that
if defined (adritem^.permalink) or defined (adritem^.guid) {
local (guidval);
bundle { //11/16/02 by DW
if defined (adritem^.permalink) {
guidval = adritem^.permalink}
else {
guidval = adritem^.guid}};
local (adrinhistory = @adrhistory^.[guidval]);
if not defined (adrinhistory^) { //it's a new story
local (flskipnew = false);
if sizeof (adritem^.title) > 0 {
try {flskipnew = defined (adrhistory^.[adritem^.title])}}; //a newly 2.0'd feed
if not flskipnew {
if adrStoryArrivedCallback != nil {
adrStoryArrivedCallback^ (adrservice, adritem)}}};
adrinhistory^ = true; //it's current
return}};
local (title = adritem^.title);
if sizeOf (title) > 0 {
local (adrinhistory = @adrhistory^.[title]);
if not defined (adrinhistory^) { //it's a new story
if adrStoryArrivedCallback != nil {
adrStoryArrivedCallback^ (adrservice, adritem)}};
adrinhistory^ = true}}; //it's current
on getElementValueWithMatchingNamespace (adrtable, adrroot, name, adrvalue, nsuri="") {
<<9/29/02; 8:22:06 PM by JES
<<Get the value of the first sub-element of adrtable (an xml table) whose namespace URI matches an expected value.
<<Parameters:
<<adrtable -- the address of an xml table
<<adrroot -- the address of the root-level of the compiled xml document
<<name -- the name of the element we want the value of
<<nsuri -- the namespace we're interested in
<<docnsuri -- the default namespace URI for this XML document
<<adrvalue -- the address of a variable in which to store the value of the found element (pcdata)
local (items = xml.getAddressList (adrtable, name));
local (adr);
for adr in items {
if xml.getNamespaceUriForElement (adr, adrroot, docnsuri) == nsuri {
if typeOf (adr^) == tableType {
adrvalue^ = decode (adr^.["/pcdata"])}
else {
adrvalue^ = decode (adr^)};
return (true)}};
return (false)};
on initModulesForService (adrservice) {
on initModules (adrtable) {
local (adr);
for adr in adrtable {
try {
while typeOf (adr^) == addressType {
adr = adr^};
if defined (adr^.init) {
adr^.init (adrservice)}}}};
initModules (@xml.rss.moduleDrivers);
initModules (@user.xml.rss.moduleDrivers)};
on runModules (adrtable, adrdoc, docnsuri, adritem=nil) {
<<10/3/02; 10:19:44 PM by JES
<<Run scripts in the module drivers for any sub-elements of adrtable, whose namespace URI is different from docnsuri.
<<Module drivers have the following table-structure -- all sub-elements are optional.
<<["http://moduledriver.com/uriOfModuleDriver/"] -- a table in user.xml.rss.moduleDrivers or xml.rss.moduleDrivers.
<<init -- a script that's called before the channel is complied -- use this script to clear stale data
<<any -- a script which handles any elements which aren't handled by a more specific script
<<subElementOfItem -- a table of scripts called for sub-elements of an <item>
<<any -- a script called for any sub-element of <item>
<<[elementName] -- a script called for a specific sub-element of <item>, whose name is elementName
<<Note: Scripts in this table take three parameters, adrelement, adrservice and adritem
<<adrelement is the address of the XML table for the element
<<adrservice is the address of the service table
<<adritem is the address of a sub-table of adrservice^.compilation.items
<<subElementOfChannel -- a table of scripts called for sub-elements of <channel>
<<Same as subElementOfItem, but for sub-elements of <channel> instead.
<<Note: Scripts in this table take two parameters: adrelement and adrservice
<<adrelement is the address of the XML table for the element
<<adrservice is the address of the service table
<<subElementOfRss -- a table of scripts called for sub-elements of <rss>
<<Same as subElementOfChannel, but for sub-elements of <rss> instead.
<<Note: Scripts in this table take two parameters: adrelement and adrservice.
<<If this script is called with an address specified for adritem, then adritem is passed as the third parameter to the moduleDriver script. If not, then the moduleDriver script is called with only two parameters.
<<Note: If/when module support is added for <rdf> (RSS 1.0), drivers which have subElementOfRss scripts will need to also have subElementOfRdf scripts. They can just call the corresponding scripts in the subElementOfRss table.
local (adrelement);
for adrelement in adrtable {
local (name = xml.convertToDisplayName (nameOf (adrelement^)) );
if name beginsWith "/" { //skip /atts and /pcdata
continue};
local (nsuri = xml.getNamespaceUriForElement (adrelement, adrdoc, docnsuri));
if nsuri != docnsuri { //found an element in another namespace -- try to process it
local (adrdriver);
try {
if xml.rss.findModuleDriver (nsuri, @adrdriver) {
local (parentElementName = xml.convertToDisplayName (nameOf (adrtable^)));
local (tablename = "subElementOf" + string.upper (parentElementName[1]) + string.delete (parentElementName, 1, 1));
local (adrscripts = @adrdriver^.[tablename] );
local (adrscript);
if defined (adrscripts^) { //found a set of scripts specific to sub-elements of this element
if defined (adrscripts^.[name]) {
adrscript = @adrscripts^.[name]}
else {
if defined (adrscripts^.any) {
adrscript = @adrscripts^.any}}}
else { //look for a top-level any script
if defined (adrdriver^.[name]) {
adrscript = @adrdriver^.[name]}
else {
if defined (adrdriver^.any) {
adrscript = @adrdriver^.any}}};
if adrscript != nil {
if adritem == nil { //call without the adritem parameter
adrscript^ (adrelement, adrservice)}
else { //call with the adritem parameter
adrscript^ (adrelement, adrservice, adritem)}}}}}}};
case format {
"RSS1" {
local (adrrdf = xml.getAddress (adrxstruct, "RDF"));
local (adrchannel = decode (xml.getAddress (adrrdf, "channel")));
bundle { //set channeltitle, channellink, channeldescription
adrcompilation^.channeltitle = decode (xml.getValue (adrchannel, "title"));
adrcompilation^.channellink = decode (xml.getValue (adrchannel, "link"));
adrcompilation^.channeldescription = decode (xml.getValue (adrchannel, "description"))};
bundle { //set imagetitle, imageurl, imagelink
try { //the image is not required
local (adrimage = xml.getAddress (adrchannel, "image"));
adrcompilation^.imagetitle = decode (xml.getValue (adrimage, "title"));
adrcompilation^.imageurl = decode (xml.getValue (adrimage, "url"));
adrcompilation^.imagelink = decode (xml.getValue (adrimage, "link"))}};
bundle { //set textinput stuff
try { //textinput is not required
local (adrtextinput = xml.getAddress (adrrdf, "textinput"));
adrcompilation^.textInputTitle = decode (xml.getValue (adrtextinput, "title"));
adrcompilation^.textInputDescription = decode (xml.getValue (adrtextinput, "description"));
adrcompilation^.textInputName = decode (xml.getValue (adrtextinput, "name"));
adrcompilation^.textInputLink = decode (xml.getValue (adrtextinput, "link"))}};
local (itemlist = xml.getAddressList (adrrdf, "item"), item, ct = 1, i);
new (tabletype, @adrcompilation^.items);
local (docnsuri = xml.rss.data.nsRss1);
for i = sizeof (itemlist) downto 1 { //each item is the address of an item
item = itemlist [i];
<<bundle //JES 9/29/02: skip <item>s which are outside the RSS 1.0 namespace if declared
<<local (itemnsuri = xml.getNamespaceUriForElement (item, adrrdf, docnsuri))
<<if sizeOf (itemnsuri) > 0 //make sure it's in our namespace or skip it
<<if itemnsuri != xml.rss.data.nsRss1 //skip
<<continue
adritem = @adrcompilation^.items.[string.padwithzeros (ct++, 5)];
new (tabletype, adritem);
<<Original code
local (link = decode (xml.getValue (item, "link")));
local (title = decode (xml.getValue (item, "title")));
local (description = "");
try {description = decode (xml.getValue (item, "description"))};
<<bundle //new code that skips elements outside the RSS 1.0 namespace -- disabled for now
<<local (link = "", title = "", description = "")
<<getElementValueWithMatchingNamespace (item, adrrdf, "link", @link, docnsuri)
<<getElementValueWithMatchingNamespace (item, adrrdf, "title", @title, docnsuri)
<<getElementValueWithMatchingNamespace (item, adrrdf, "description", @description, docnsuri)
if sizeOf (link) > 0 and sizeOf (title) > 0 {
adritem^.title = "<a href=\"" + link + "\">" + title + "</a>"};
if sizeof (description) > 0 {
adritem^.title = adritem^.title + ". " + description};
if flSaveData {
local (adrdata = @adritem^.data);
new (tabletype, adrdata);
adrdata^.link = link;
adrdata^.title = title;
adrdata^.description = description};
addToHistory (adritem)}};
"RSS" { //version 0.91, 0.92 or 2.0
local (adrRss = xml.getAddress (adrxstruct, "rss"));
local (adrchannel = xml.getAddress (adrRss, "channel"));
local (version = xml.getAttribute (adrRss, "version")^);
local (flVersion91 = date.versionlessthan (version, "0.92d1"));
local (flVersion20 = not date.versionlessthan (version, "2.0"));
if flVersion20 { //run all the moduleDrivers' init scripts
initModulesForService (adrservice)};
adrcompilation^.channeltitle = decode (xml.getValue (adrchannel, "title"));
adrcompilation^.channellink = decode (xml.getValue (adrchannel, "link"));
adrcompilation^.channeldescription = decode (xml.getValue (adrchannel, "description"));
bundle { //get language, it's optional in 0.92 and greater
if flVersion91 {
adrcompilation^.channellanguage = decode (xml.getValue (adrchannel, "language"))}
else {
try {
adrcompilation^.channellanguage = decode (xml.getValue (adrchannel, "language"))}}};
bundle { //get the cloud element, new in 0.92, optional
try {
local (cloudinfo);
new (tabletype, @cloudinfo);
local (adrcloud = xml.getaddress (adrchannel, "cloud"));
cloudinfo.domain = decode (xml.getAttribute (adrcloud, "domain")^);
cloudinfo.path = decode (xml.getAttribute (adrcloud, "path")^);
cloudinfo.port = decode (xml.getAttribute (adrcloud, "port")^);
cloudinfo.protocol = decode (xml.getAttribute (adrcloud, "protocol")^);
cloudinfo.registerProcedure = decode (xml.getAttribute (adrcloud, "registerProcedure")^);
adrcompilation^.cloud = cloudinfo}};
bundle { //set imagetitle, imageurl, imagelink
try { //the image is not required
local (adrimage = xml.getAddress (adrchannel, "image"));
adrcompilation^.imagetitle = decode (xml.getValue (adrimage, "title"));
adrcompilation^.imageurl = decode (xml.getValue (adrimage, "url"));
adrcompilation^.imagelink = decode (xml.getValue (adrimage, "link"));
adrcompilation^.imagewidth = decode (xml.getValue (adrimage, "width"));
adrcompilation^.imageheight = decode (xml.getValue (adrimage, "height"));
adrcompilation^.imagedescription = decode (xml.getValue (adrimage, "description"))}};
bundle { //set textinput stuff
try { //textinput is not required
local (adrtextinput = xml.getAddress (adrRss, "textinput"));
adrcompilation^.textInputTitle = decode (xml.getValue (adrtextinput, "title"));
adrcompilation^.textInputDescription = decode (xml.getValue (adrtextinput, "description"));
adrcompilation^.textInputName = decode (xml.getValue (adrtextinput, "name"));
adrcompilation^.textInputLink = decode (xml.getValue (adrtextinput, "link"))}};
bundle { //get the items
local (itemlist = xml.getAddressList (adrchannel, "item"), item, ct = 1, i);
local (docnsuri = ""); //default
new (tabletype, @adrcompilation^.items);
for i = sizeof (itemlist) downto 1 { //each item is the address of an item
item = itemlist [i];
bundle { //JES 9/29/02: skip <item>s which have namespaces
local (itemnsuri = xml.getNamespaceUriForElement (item, adrRss, docnsuri));
if sizeOf (itemnsuri) > 0 { //make sure it's in our namespace or skip it
if itemnsuri != docnsuri { //skip
continue}}};
adritem = @adrcompilation^.items.[string.padwithzeros (ct++, 5)];
new (tabletype, adritem);
local (link = "", title = "", description = "", s = "");
if flVersion20 { //namespace support
getElementValueWithMatchingNamespace (item, adrrss, "link", @link);
getElementValueWithMatchingNamespace (item, adrrss, "title", @title);
getElementValueWithMatchingNamespace (item, adrrss, "description", @description)}
else { //0.91, 0.92 -- ignore namespaces
try {link = decode (xml.getValue (item, "link"))};
try {title = decode (xml.getValue (item, "title"))};
try {description = decode (xml.getValue (item, "description"))}};
bundle { //handle title/link, 10/10/02 by DW
if sizeof (title) > 0 {
if sizeof (link) > 0 {
s = "<a href=\"" + link + "\">" + title + "</a>"}
else {
s = title}}};
if sizeof (description) > 0 {
if sizeof (s) > 0 {
s = s + ". " + description}
else {
s = description}};
bundle { //handle Media-RSS thumbnails, 11/30/08 by DW
try { //see if the item has a media:thumbnail element
local (adrthumbnail = xml.getaddress (item, "thumbnail"));
local (adratts = @adrthumbnail^.["/atts"]);
if adratts^.namespace == "media:" {
local (thumbtext = "<img src=\"" + decode (adratts^.url) + "\" width=\"" + adratts^.width + "\" height=\"" + adratts^.height + "\" border=\"0\" alt=\"Thumbnail version of the image\">");
bundle { //link it to enclosure, if possible
try {
local (adrenclosure = xml.getaddress (item, "enclosure"));
local (url = decode (xml.getattributevalue (adrenclosure, "url")));
thumbtext = "<a href=\"" + url + "\">" + thumbtext + "</a>"}};
s = s + "<p>" + thumbtext + "</p>"}}};
local (author="", comments=""); //2.0 elements -- need to save to add to data table if flSaveData is true
bundle { //if 2.0, look for author, if present, add it to s, 10/10/02 by DW
if flVersion20 {
try {
author = decode (xml.getValue (item, "author"));
s = s + " By " + author + "."}}};
bundle { //if 2.0, look for comments, if present add it to s, 10/17/02 by JES
if flVersion20 {
try {
comments = decode (xml.getValue (item, "comments"));
adritem^.comments = comments}}};
adritem^.title = s;
if flSaveData {
local (adrdata = @adritem^.data);
new (tabletype, adrdata);
adrdata^.link = link;
adrdata^.title = title;
adrdata^.description = description;
if flVersion20 {
if sizeOf (author) > 0 {
adrdata^.author = author};
if sizeOf (comments) > 0 {
adrdata^.comments = comments};
try {
local (pubdate = xml.getValue (item, "pubDate"));
adrdata^.pubDate = pubDate}}};
bundle { //get enclosure, 1/11/01 by DW
try {
local (adrxenclosure = xml.getaddress (item, "enclosure"));
local (adrenclosure = @adritem^.enclosure);
new (tabletype, adrenclosure);
adrenclosure^.url = decode (xml.getattribute (adrxenclosure, "url")^);
adrenclosure^.length = decode (xml.getattribute (adrxenclosure, "length")^);
adrenclosure^.type = decode (xml.getattribute (adrxenclosure, "type")^)}};
bundle { //get permalink or guid, if available, RSS 2.0 only, 9/16/02 by DW
if flVersion20 { //only in RSS 2.0
try { //it's optional
local (adrguid = xml.getaddress (item, "guid"));
local (flpermalink = true);
try {flpermalink = xml.getattribute (adrguid, "isPermaLink")^};
local (guidval = "");
bundle { //set guidval
if typeof (adrguid^) == tabletype {
guidval = string (adrguid^.["/pcdata"])}
else {
guidval = string (adrguid^)}};
if flpermalink {
adritem^.permalink = guidval}
else {
adritem^.guid = guidval}}}}; //new element, 11/16/02 by DW
local (catlist = {});
bundle { //9/30/99 by DW, get the categories, if there are any
local (xcatlist = xml.getAddressList (item, "category"), cat);
for cat in xcatlist {
if typeof (cat^) != tabletype { //1/16/03 by DW
catlist = catlist + {decode (cat^)}}};
if sizeof (catlist) > 0 { //9/5/03 by DW
adritem^.catlist = catlist}};
addToHistory (adritem, catlist);
bundle { //get source, it's optional in 0.92 and greater
if not flVersion91 {
try {
local (adrsource = xml.getAddress (item, "source"));
local (url = decode (xml.getAttribute (adrsource, "url")^));
adritem^.sourceChannelTitle = decode (adrsource^.["/pcdata"]);
adritem^.sourceChannelUrl = url}}};
runModules (item, adrRss, "", adritem)}}; //run modules for sub-elements of <item>
bundle { //JES 9/29/02: process extension elements
if flVersion20 {
runModules (adrchannel, adrRss, ""); //run modules for sub-elements of <channel>
runModules (adrRss, adrRss, "")}}; //run modules for sub-elements of <rss>
bundle { //DW 10/30/02: get the URL for this file's changes.xml, if it has one
local (changesurl = "");
local (catlist = xml.getAddressList (adrchannel, "category"), adrcat);
for adrcat in catlist {
try {
if string.lower (adrcat^.["/pcdata"]) == "rssupdates" {
changesurl = adrcat^.["/atts"].domain;
break}}};
if changesurl != "" {
adrcompilation^.changesUrl = changesurl}};
bundle { //DW 11/8/02 extract skipHours info, if available
try {
local (adrskiphours = xml.getAddress (adrchannel, "skipHours"));
local (hourslist = xml.getAddressList (adrskiphours, "hour"));
local (skiplist = {}, adrhour, h, ctz = date.getCurrentTimeZone ());
for adrhour in hourslist {
try {
h = number (adrhour^) + (ctz / 60 / 60);
if h < 0 {
h = 24 + h};
skiplist = skiplist + {h}}};
adrcompilation^.skipHoursList = skiplist}};
bundle { //if 2.0 set format to RSS2
if flVersion20 {
format = "RSS2"}}};
"scriptingNews" {
local (adrScriptingNews = xml.getAddress (adrxstruct, "scriptingNews"));
local (adrheader = xml.getAddress (adrScriptingNews, "header"));
local (adrversion = xml.getAddress (adrheader, "scriptingNewsVersion"));
if not date.versionLessThan (adrversion^, "2.0b1") {
adrcompilation^.channeltitle = decode (xml.getValue (adrheader, "channelTitle"));
adrcompilation^.channellink = decode (xml.getValue (adrheader, "channelLink"));
adrcompilation^.channeldescription = decode (xml.getValue (adrheader, "channelDescription"))}
else {
adrcompilation^.channeltitle = "";
adrcompilation^.channellink = "";
adrcompilation^.channeldescription = ""};
bundle { //set imagetitle, imageurl, imagelink
try { //the image is not required
adrcompilation^.imagetitle = decode (xml.getValue (adrheader, "imageTitle"));
adrcompilation^.imageurl = decode (xml.getValue (adrheader, "imageUrl"));
adrcompilation^.imagelink = decode (xml.getValue (adrheader, "imageLink"))}};
bundle { //get skipHours
try { //it's optional
local (adrskip = xml.getAddress (adrheader, "skipHours"));
local (skiplist = xml.getAddressList (adrskip, "hour"), item);
adrcompilation^.skipHours = {};
for item in skiplist { //each item is the address of an item
adrcompilation^.skipHours = adrcompilation^.skipHours + {decode (item^)}}}};
bundle { //get skipDays
try { //it's optional
local (adrskip = xml.getAddress (adrheader, "skipDays"));
local (skiplist = xml.getAddressList (adrskip, "day"), item);
adrcompilation^.skipDays = {};
for item in skiplist { //each item is the address of an item
adrcompilation^.skipDays = adrcompilation^.skipDays + {decode (item^)}}}};
bundle { //get various optional things from the header
try {adrcompilation^.managingEditor = decode (xml.getValue (adrheader, "managingEditor"))};
try {adrcompilation^.webmaster = decode (xml.getValue (adrheader, "webmaster"))};
try {adrcompilation^.language = decode (xml.getValue (adrheader, "language"))};
try {adrcompilation^.imageHeight = decode (xml.getValue (adrheader, "imageHeight"))};
try {adrcompilation^.imageWidth = decode (xml.getValue (adrheader, "imageWidth"))};
try {adrcompilation^.imageCaption = decode (xml.getValue (adrheader, "imageCaption"))}};
bundle { //load the items
local (itemlist = xml.getAddressList (adrScriptingNews, "item"));
local (item, ct = 1, adritem, itemtext, i);
new (tabletype, @adrcompilation^.items);
for i = sizeof (itemlist) downto 1 { //each item is the address of an item
item = itemlist [i];
adritem = @adrcompilation^.items.[string.padwithzeros (ct++, 5)];
new (tabletype, adritem);
itemtext = decode (xml.getValue (item, "text"));
<<itemtext = string.replaceall (itemtext, "<", "<")
<<itemtext = string.replaceall (itemtext, ">", ">")
itemtext = decode (itemtext);
local (linklist = xml.getAddressList (item, "link"), link);
for link in linklist {
local (url = decode (xml.getValue (link, "url")));
local (linetext = decode (xml.getValue (link, "linetext")));
local (s = "<a href=\"" + url + "\">" + linetext + "</a>");
itemtext = string.replace (itemtext, linetext, s)};
adritem^.title = itemtext;
local (catlist = {});
bundle { //9/30/99; 8:48:02 PM by DW, get the categories, if there are any
local (xcatlist = xml.getAddressList (item, "category"), cat);
for cat in xcatlist {
catlist = catlist + {decode (cat^)}}};
addToHistory (adritem, catlist)}}};
"unknown" { //see if a driver can handle this format
<<How it works -- 3/15/02; 2:35:19 PM by DW
<<Drivers are located either in user.xml.rss.formatDrivers or xml.rss.formatDrivers; the former for user-supplied drivers, the latter for system drivers. All new formats supported by UserLand will come in the form of drivers.
<<First we locate the driver, starting in the user table, then looking in the system table. If we don't locate one, do nothing (as before). If we do, we call the driver script, passing it enough parameters so it can do exactly what one of the internal format handlers does.
local (adr, drivername = "", name);
for adr in adrxstruct {
name = xml.convertToDisplayName (nameof (adr^));
if (not (name beginswith "?")) and (not (name beginswith "/")) {
drivername = name;
break}};
if drivername != "" {
local (adrdriver = @user.xml.rss.formatDrivers.[drivername]);
if not defined (adrdriver^) {
adrdriver = @xml.rss.formatDrivers.[drivername]};
if defined (adrdriver^) {
adrdriver^.compile (adrservice, flSaveData, adrStoryArrivedCallback);
format = drivername}}}};
adrcompilation^.format = format;
bundle { //clean up itemHistory table
local (i);
for i = sizeof (adrhistory^) downto 1 {
if not adrhistory^ [i] {
delete (@adrhistory^ [i])}}}}
<<bundle //test item-level categories
<<compileService (@aggregatorData.services.["http://radio.weblogs.com/0001015/rss.xml"], true, @xml.aggregator.storyArrivedCallback)
<<bundle //test for Jon Udell's experiment
<<compileService (@aggregatorData.services.["http://weblog.infoworld.com/udell/rss.xml"], true, @xml.aggregator.storyArrivedCallback)
<<bundle //test guid with isPermalink attribute present
<<compileService (@aggregatorData.services.["http://www.raibledesigns.com/rss/rd"], true, @xml.aggregator.storyArrivedCallback)
<<bundle //test category support at top level
<<compileService (@aggregatorData.services.["http://www.scripting.com/rss.xml"], true, @xml.aggregator.storyArrivedCallback)
<<bundle //test namespace support in RSS 2.0
<<compileService (@aggregatorData.services.["http://www.scripting.com/rss.xml"], true, @xml.aggregator.storyArrivedCallback)
<<compileService (@aggregatorData.services.["http://diveintomark.org/xml/rss2.xml"], true, @xml.aggregator.storyArrivedCallback)
<<bundle //test namespace support in RSS 1.0
<<compileService (@aggregatorData.services.["http://www.infoworld.com/rss/news.rdf"], true, @xml.aggregator.storyArrivedCallback)
<<compileService (@aggregatorData.services.["http://www.pocketsoap.com/rssTests/rss1.0withModulesNoDefNSLocalNameClash.xml"], true, @xml.aggregator.storyArrivedCallback)
<<bundle //test code
<<compileService (@scratchpad.["http://www.teleread.org/blog/rss.xml"], true, @xml.aggregator.storyArrivedCallback)
<<bundle //test code, testing support for <link> redirection
<<local (url = "http://w3future.com/weblog/")
<<local (adrservice = xml.rss.initService (url, @aggregatorData.services))
<<new (tabletype, @adrservice^.compilation)
<<new (tabletype, @adrservice^.compilation.itemHistory)
<<adrservice^.xmltext = tcp.httpReadUrl (url)
<<compileService (adrservice, true, @xml.aggregator.storyArrivedCallback)
<<bundle //test code, testing formatDrivers
<<local (url = "http://rcm.amazon.com/e/cm?t=naviseek&l=st1&search=programming&mode=books&p=102&o=1&f=xml")
<<local (adrservice = xml.rss.initService (url, @aggregatorData.services))
<<new (tabletype, @adrservice^.compilation)
<<new (tabletype, @adrservice^.compilation.itemHistory)
<<adrservice^.xmltext = tcp.httpReadUrl (url)
<<xml.compile (adrservice^.xmltext, @adrservice^.xmlstruct)
<<compileService (adrservice, true, @xml.aggregator.storyArrivedCallback)
<<bundle //more test code, profiling
<<new (tableType, @temp.profileData)
<<script.startProfile (true)
<<compileService (@aggregatorData.services.["http://scriptingnews.userland.com/xml/scriptingNews2.xml"], true, @xml.aggregator.storyArrivedCallback)
<<script.stopProfile (@temp.profileData)
<<edit (@temp.profileData)
This listing is for code that runs in the OPML Editor environment. I created these listings because I wanted the search engines to index it, so that when I want to look up something in my codebase I don't have to use the much slower search functionality in my object database. Dave Winer.