Monday, November 08, 2010 at 12:07 AM.
system.verbs.builtins.xml.rss.formatDrivers.feed.compile
on compile (adrservice, flSaveData, adrStoryArrivedCallback) { <<Changes <<8/13/04; 3:44:16 PM by JES <<Created. Format driver for atom 0.3 feeds. <<Based on original by DW, 7/11/03 <<7/11/03; 3:19:52 PM by DW <<Created. <<Comments here: <<http://scriptingnews.userland.com/2003/07/11 <<ATOM 0.3 specification: <<http://www.intertwingly.net/wiki/pie/FrontPage on addToHistory (adritem, categorylist={}) { bundle { //if the item has a permalink, use that if defined (adritem^.permalink) or defined (adritem^.guid) { local (guidval); if defined (adritem^.guid) { guidval = adritem^.guid} else { //use permalink for guidval guidval = adritem^.permalink}; 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 local (xstructcopy = adrservice^.xmlstruct, adrxstruct = @xstructcopy); local (adrcomp = @adrservice^.compilation); local (adrhistory = @adrcomp^.itemHistory); local (adrfeed = xml.getAddress (adrxstruct, "feed")); if not (adrfeed^.["/atts"].version == "0.3") { //error -- incompatible version scriptError ("Can't compile the feed because version " + adrfeed^.["/atts"].version + " is unknown. Version must be 0.3.")}; new (tabletype, @adrcomp^.items); on encodeHtmlCharacters (s) { //encode &, < and > for literal display in browser s = string.replaceAll (s, "&", "&"); s = string.replaceAll (s, "<", "<"); s = string.replaceAll (s, ">", ">"); return (s)}; on patchUrl (adrurl, baseurl) { //patch relative URL to absolute URL if sizeOf (baseurl) > 0 { if not (adrurl^ contains ":") { if adrurl^ beginsWith "/" { local (urlparts = string.urlSplit (baseurl)); baseurl = urlparts[1] + urlparts[2]} else { while adrurl^ beginswith "../" { local (urlparts = string.urlSplit (baseurl)); urlparts[3] = "/" + urlparts[3]; if urlparts[3] endswith "/" { urlparts[3] = string.popTrailing (urlparts[3], "/")}; baseurl = urlparts[1] + urlparts[2] + string.popSuffix (urlparts[3], "/") + "/"; adrurl^ = string.delete (adrurl^, 1, 3)}}; adrurl^ = string.popTrailing (baseurl, "/") + "/" + string.popLeading (adrurl^, "/")}}; return}; on getElementValue (adrelement) { //get the value of an atom:element, decoding as specified local (s = "", type = "text/plain", mode = "xml"); if typeOf (adrelement^) == tableType { //pcdata, cdata, or embedded xhtml bundle { //set type and mode, and get element content if defined (adrelement^.["/atts"].type) { type = adrelement^.["/atts"].type}; if defined (adrelement^.["/atts"].mode) { mode = adrelement^.["/atts"].mode}; if defined (adrelement^.["/pcdata"]) { s = adrelement^.["/pcdata"]}; if defined (adrelement^.["/cdata"]) { s = adrelement^.["/cdata"]}}; case mode { "xml" { local (xfrag = adrelement^); on visitproc (adr) { if nameOf (adr^) beginswith "/" { if nameOf (adr^) == "/pcdata" { adr^ = string.replace (adr^, "&", "&"); adr^ = string.replace (adr^, "<", "<"); adr^ = string.replace (adr^, ">", ">")}}; return (true)}; table.visit (@xfrag, @visitproc); local (adrsub); for adrsub in @xfrag { if not (nameOf (adrsub^) beginswith "/") { local (frag = xml.decompile (adrsub)); frag = string.delete (frag, 1, string.patternMatch (">", frag) ); if type == "application/xhtml+xml" { //decode &'s encoded by xml.decompile frag = string.replaceAll (frag, "&", "&")}; s = s + string.trimWhiteSpace (frag)}}}; "base64" { s = base64.decode (s)}; "escaped" { try { s = xml.entityDecode (s, false, true, true)} else { //old version of xml.entityDecode? s = string.replaceAll (s, "<", "<"); s = string.replaceAll (s, ">", ">"); s = string.replaceAll (s, """, "\""); s = xml.entityDecode (s, false, true); s = string.replaceAll (s, "&", "&")}}}} else { //plain-text string try { s = xml.entityDecode (adrelement^, false, true, true)} else { //old version of xml.entityDecode? s = xml.entityDecode (adrelement^, false, true)}}; case type { "text/plain" { //encode HTML with entities, for correct display in browser s = string.replaceAll (s, "&", "&"); s = string.replaceAll (s, "<", "<"); s = string.replaceAll (s, ">", ">"); s = string.replaceAll (s, "\"", """)}; "text/html" { }};//do nothing -- this is what we need out the other end anyway return (s)}; on getSubElementValue (adr, name) { local (adrsub = xml.getAddress (adr, name)); return (getElementValue (adrsub))}; on getAuthorValue (adr) { local (name="", url="", email=""); try {name = getSubElementValue (adr, "name")}; try {email = getSubElementValue (adr, "email")}; try {url = getSubElementValue (adr, "url")}; local (baseUrl = base); try {baseUrl = adr^.["/atts"].["xml:base"]}; if sizeOf (url) > 0 { patchUrl (@url, baseUrl)}; if name == "" { if email == "" { if url == "" { return ("")} else { return (url)}} else { if url == "" { return (email)} else { return (url + " (" + email + ")")}}} else { if url == "" { if email == "" { return (name)} else { return (name + " (" + email + ")")}} else { if email == "" { return (html.getLink (name, url))} else { return (html.getLink (name, url) + " (" + email + ")")}}}}; local (months); bundle { //set months -- used for shortcut to converting UTC time via date.netStandardString months = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}}; local (encoding = "", base = "", author = "", pubdate = clock.now ()); bundle { //set feed globals for character-encoding and base url local (adrxml = xml.getAddress (adrxstruct, "?xml")); if defined (adrxml^.encoding) { encoding = string.lower (adrxml^.encoding); case encoding { //recompile XML with correct character encoding "utf-8" { xml.compile (string.utf8ToAnsi (adrservice^.xmltext), @xstructcopy); adrxml = xml.getAddress (adrxstruct, "?xml"); adrfeed = xml.getAddress (adrxstruct, "feed")}; "utf-16" { scriptError ("Can't read this item because UTF-16 encoding is not supported.")}}}; if defined (adrfeed^.["/atts"].["xml:base"]) { base = adrfeed^.["/atts"].["xml:base"]}; try { //get feed-level author local (adrauthor = xml.getAddress (adrfeed, "author")); author = getAuthorValue (adrauthor)}}; bundle { //set feed-level items adrcomp^.format = "atom 0.3"; adrcomp^.channeltitle = getSubElementValue (adrfeed, "title"); bundle { //set adrcomp^.channellink adrcomp^.channellink = ""; //it's possible that there's only one <link rel...> which we don't understand <<Loop over <link rel="..." ...> tags, and use the first rel="alternate" whose type="text/html" or "application/xhtml+xml" local (adrlinks = xml.getAddressList (adrfeed, "link") ); local (adr); for adr in adrlinks { if adr^.["/atts"].rel == "alternate" { case string.lower (adr^.["/atts"].type) { "text/html"; "application/xhtml+xml" { adrcomp^.channellink = adr^.["/atts"].href; patchUrl (@adrcomp^.channellink, base); break}}}}}; adrcomp^.channeldescription = ""; try {adrcomp^.channeldescription = getSubElementValue (adrfeed, "tagline")}}; //tagline is optional bundle { //loop over entries local (adr, ct = 1); local (sfeed = sizeOf (adrfeed^), i); for i = sfeed downto 1 { adr = @adrfeed^[i]; if nameof (adr^) endswith "\tentry" { local (adritem = @adrcomp^.items.[string.padwithzeros (ct++, 5)]); new (tabletype, adritem); local (entryAuthor = "", entryTitle = "", content = ""); bundle { //author try { //item-level author local (adrauthor = xml.getAddress (adr, "author")); entryAuthor = getAuthorValue (adrauthor)} else { //feed-level author if author != "" { entryAuthor = author}}}; bundle { //title try { entryTitle = getSubElementValue (adr, "title")}}; bundle { //guid try { adritem^.guid = getSubElementValue (adr, "id")}}; bundle { //pubdate local (d = getSubElementValue (adr, "issued")); local (y = string.mid (d, 1, 4)); local (m = string.mid (d, 6, 2)); local (dy = string.mid (d, 9, 2)); local (hr = string.mid (d, 12, 2)); local (min = string.mid (d, 15, 2)); local (sec = string.mid (d, 18, 2)); local (zone = string.mid (d, 20, infinity)); local (zonemin = 0); if string.lower (zone) == "z" { //UTC pubdate = date (dy + " " + months[number(m)] + " " + y + " " + hr + ":" + min + ":" + sec + " GMT")} else { //have to convert time y = number (y); m = number (m); dy = number (dy); hr = number (hr); min = number (min); sec = number (sec); zonemin = ( (60*number(string.mid(zone,2,2))) + number(string.mid(zone,5,2))); if zone[1] == "-" { zonemin = 0-zonemin}; pubdate = date.set (dy, m, y, hr, min, sec) + (60*zonemin)}}; bundle { //permalink, link, sourceChannelTitle, sourceChannelUrl local (adrlink, adrlinks = xml.getAddressList (adr, "link") ); for adrlink in adrlinks { if defined (adrlink^.["/atts"].rel) { local (baseUrl = base); if defined (adrlink^.["/atts"].["xml:base"]) { baseUrl = adrlink^.["/atts"].["xml:base"]}; case string.lower (adrlink^.["/atts"].rel) { "alternate" { //permalink if defined (adrlink^.["/atts"].type) { case string.lower (adrlink^.["/atts"].type) { "text/plain"; "text/html"; "application/xhtml+xml" { adritem^.permalink = adrlink^.["/atts"].href; patchUrl (@adritem^.permalink, baseUrl)}}}}; "related" { //external link if defined (adrlink^.["/atts"].type) { case string.lower (adrlink^.["/atts"].type) { "text/plain"; "text/html"; "application/xhtml+xml" { adritem^.link = adrlink^.["/atts"].href; patchUrl (@adritem^.link, baseUrl)}}}}; "source" { adritem^.sourceChannelTitle = adrlink^.["/atts"].title; adritem^.sourceChannelUrl = adrlink^.["/atts"].href; patchUrl (@adritem^.sourceChannelUrl, baseUrl)}}}}; if not defined (adritem^.guid) { //use permalink for guid, for item history adritem^.guid = adritem^.permalink}}; <<bundle //comments <<//Not supported as of the 0.3 version of the spec. As best I can tell, all you get is a link to a feed for comments, or a set of links to atom entries for individual comments, and not a link to the HTML page that displays comments for this entry. bundle { //content (a.k.a. description), and top-level title string (mashed data) try { //prefer full content content = getSubElementValue (adr, "content")} else { //use summary instead try { //summary is optional content = getSubElementValue (adr, "summary")}}; local (linkUrl = "", linkHtml = ""); if defined (adritem^.permalink) { //prefer permalink over link linkUrl = adritem^.permalink} else { //no permalink -- use link if present if defined (adritem^.link) { linkUrl = adritem^.link}}; if entryTitle == "" { //empty title -- use the # mark for the link text entryTitle = "#"}; if sizeOf (linkUrl) > 0 { //we have a link -- generate the HTML for display linkHtml = html.getLink (entryTitle, linkUrl)} else { //no link -- do we have a title? if entryTitle == "#" { //no link or title linkHtml = ""} else { //no link -- just use the title linkHtml = entryTitle}}; if sizeOf (entryAuthor) > 0 { adritem^.title = linkHtml + " " + content + " - " + entryAuthor} else { adritem^.title = linkHtml + " " + content}}; bundle { //data sub-table of item if flSaveData { local (adrdata = @adritem^.data); new (tabletype, adrdata); adrdata^.author = entryAuthor; adrdata^.description = content; adrdata^.link = adritem^.permalink; adrdata^.pubdate = date.netStandardString (pubdate); adrdata^.title = entryTitle}}; addToHistory (adritem)}}}; return (true)}
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.