Monday, November 08, 2010 at 12:05 AM.
system.verbs.builtins.radio.weblog.render
on render (adrblog, dayTemplate, itemTemplate, catname=nil, flSameMachine=false, flIEditLinks=false, maxDays=nil, maxDate=nil, minDate=nil, adratts=nil, flStaticRendering=false, archiveFileExtension="html") {
<<Changes
<<8/30/03; 12:50:24 PM by JES
<<Add TrackBack auto-discovery RDF bits inside an HTML comment. Fixes HTML validation errors.
<<7/20/03; 6:14:16 PM by JES
<<Added trackback support.
<<11/11/02; 5:33:38 PM by JES
<<Use & instead of & for the path-argument delimiter for the comment-counter JavaScript source url.
<<10/28/02; 7:36:24 PM by JES
<<Removed an extra cr/lf pair between the permalink anchor tag and the weblog item, which was causing extra paragraph tags.
<<9/3/02; 6:53:29 PM by LL
<<Set defaults for maxdays for weekly and monthly archive pages by checking the pageTable.
<<8/28/02; 6:09:17 PM by LL
<<Call bottleneck scripts, radio.weblog.getUrlForDay and radio.weblog.getUrlForPost, to get the URLs for day and item permalinks.
<<3/31/02; 10:45:22 PM by JES
<<Add the anchor tag for the permalinks before the entire item, instead of before the itemtext.
<<3/13/02; 1:13:58 AM by JES
<<Only insert the comment counter javascript if the preference is enabled *and* the item template contains a <%commentCount%> macro. Prevents JavaScript when the <%commentCount%> macro is present but the comments feature is disabled.
<<3/12/02; 5:00:00 PM by JES
<<If the auto-generated title links feature is enabled, and no link is specified for a post, use the auto-generated link.
<<3/11/02; 3:37:57 PM by DW
<<Instead of tacking the title/link combo at the beginning of itemText, there's now a new element of an item template called itemLink.
<<3/11/02; 11:47:52 AM by DW
<<Use the title and link info, if it's available.
<<2/23/02; 1:15:20 AM by JES
<<If the commentLinkText pref contains a <%commentCount%> macro, add the code to the page that loads the external JavaScript that renders the comment count.
<<2/16/02; 6:27:43 PM by JES
<<Added <%commentLink%> pseudo-macro. Call radio.html.commentLink to get the HTML for the link.
<<2/13/02; 1:42:38 PM by JES
<<Get permalink, source and enclosure image prefs using calls to radio.weblog.getThemePref.
<<2/10/02; 11:20:22 PM by JES
<<Make sure that adrsource^.compilation.channeltitle and adrsource^.compilation.channellink both exist, before attempting to generate the source-link. Prevents macro errors on pages which link to items from a source which doesn't have a complete compilation.
<<1/25/02; 11:19:25 AM by JES
<<Don't include posts which have an flNotOnHomePage == true, in the post table.
<<1/24/02; 9:43:44 PM by JES
<<New optional parameter, archiveFileExtension, specifies the extension to use for permalinks. Defaults to "html", and replaces the hard-coded extensions within the script. If pta^.renderedFileExtension is defined, use its value instead of the value of the archiveFileExtension parameter.
<<1/24/02; 11:35:59 AM by JES
<<Get the img tags for the item-level permalink, source and enclosure icons from weblogData.prefs, instead of from radio.data.strings.
<<1/14/02; 7:13:15 PM by DW
<<Add callbacks support in user.radio.callbacks.publishItem. Each callback gets the address of a post which is being published.
<<http://radio.userland.com/stories/storyReader$7740.
<<12/12/01; 12:57:41 PM by JES
<<If the channel corresponding to the sourceUrl is no longer defined in aggregatorData, don't cause an error when expanding the <%source%> macro in the itemTemplate.
<<12/10/01; 10:32:58 PM by JES
<<Added support for the <%itemNum%> and <%paddedItemNum%> macros: <%itemNum%> is expanded to the non-padded number of the item being rendered; <%paddedItemNum%> is expanded to the zero-padded number of the item being rendered.
<<12/7/01; 3:58:51 PM by JES
<<Trim whitespace in the templates, to prevent extra <p> tags in the rendered HTML.
<<12/5/01; 9:34:13 PM by JES
<<Prepend an 'a' character onto the name for the <a name> tags (permalinks).
<<11/30/01; 12:33:06 PM by JES
<<Convert line-endings in bodytext to \r, before returning.
<<11/16/01; 7:20:35 PM by JES
<<Fixed the anchor tags that the permalinks point to.
<<11/15/01; 3:44:24 PM by JES
<<Make the item-level and day-level permanent links work for category index and archive pages. Make the item-level permalinks point into the archive page.
<<11/12/01; 1:47:55 PM by DW
<<Add support for the item-level permalink.
<<11/12/01; 1:24:08 AM by JES
<<Ignore adrblog^.prefs.maxOutputItemsPerChannel, which is for rss files, and not for HTML renderings. Instead, always render the number of days specified by the maxDays parameter. This makes category index pages work just like the weblog home page.
<<10/30/01; 9:13:12 PM by PBS
<<Use the new string.multipleReplaceAll verb if available.
<<10/29/01; 10:49:55 PM by PBS
<<Fixed a bug -- introduced in the last bug fix -- where the current day would be repeated on the home page.
<<10/29/01; 4:27:47 PM by PBS
<<Don't process macros with html.processMacros -- use string.replaceAll. It's faster, and it partly fixes a bug where html.processMacros is called too often.
<<10/9/01; 1:15:12 PM by JES
<<Added support for category HTML rendering. The only change is to limit the number of items rendered to the number defined by adrblog^.prefs.maxOutputItemsPerChannel, rather than to limit by maxDays, when catname is non-nil.
<<9/3/01; 4:34:24 PM by JES
<<New optional parameter: flStaticRendering, defaults to false.
<<If true, then archive links will point at the static site, instead of the dynamic site.
<<9/2/01; 10:26:39 PM by JES
<<New optional parameter: adratts. If non-nil, then the table at adratts is copied into the radioResponder sub-table of the pageTable, before calling html.processMacros. This puts the atts in scope so that user macros can get at them.
<<8/15/01; 6:53:34 PM by JES
<<If the app version is less than 7.1b1, patch the templates to use the older { and } macro delimiters.
<<8/12/01; 2:28:40 AM by JES
<<Do replcaements using html.processMacros, instead of with string.replaceAll. This opens up user.html.macros, as well as glossary processing.
<<Changed the <%text%> macro in the itemTemplate to <%itemText%> since <%text%> was rendering as garbage.
<<6/25/01; 1:41:54 PM by DW
<<Changes. General purpose weblog renderer. We have no say in how the text flows. We just generate the HTML from the templates, and return it.
local (savedArgs = "", flArchivePage = false);
local (dateZero = date.set (1, 1, 1904, 0, 0, 0), dateInfinity = date.set (1, 1, 2040, 0, 0, 0));
bundle { //set defaults if not specified
if maxDays == nil {
<<if catname == nil //home or archive page
maxdays = adrblog^.prefs.ctDaysToDisplay};
<<else //category page -- the limit is a count of items, not based on date
<<maxdays = infinity
if minDate == nil {
minDate = dateZero};
if maxDate == nil {
maxDate = dateInfinity};
try { //get some info out of the pagetable if possible
local (pta = html.getPageTableAddress ());
if defined (pta^.flArchivePage) {
flArchivePage = pta^.flArchivePage};
if defined (pta^.flMonthlyArchive) {
maxDays = 31};
if defined (pta^.flWeeklyArchive) {
maxDays = 7};
if defined (pta^.renderedFileExtension) {
archiveFileExtension = pta^.renderedFileExtension};
bundle { //save getArgs -- so that the archive calendar on the weblog page will show the correct day, and the menu remembers its state
local (adrargs = @pta^.radioResponder.getArgs);
if defined (adrargs^) {
if defined (adrargs^.d) {
savedArgs = "&d=" + adrargs^.d};
if defined (adrargs^.menu) {
if sizeOf (args^.menu) > 0 {
savedArgs = savedArgs + "&menu=" + adrargs^.menu}}}}}};
if archiveFileExtension beginsWith "." { //it's expected not to
archiveFileExtension = string.delete (archiveFileExtension, 1, 1)};
bundle { //trim whitespace on templates
dayTemplate = string.trimWhiteSpace (dayTemplate);
itemTemplate = string.trimWhiteSpace (itemTemplate)};
local (adrsite = @radio.data.website);
local (editbuttonimg = radio.images.systemImageRef ("icons/editbutton", flFileUrl:flSameMachine));
local (bodytext);
local (flUseMultipleReplaceAll = defined (string.multipleReplaceAll));
on doReplacements (adrText, adrTable) {
if flUseMultipleReplaceAll { //use string.multipleReplaceAll
adrText^ = string.multipleReplaceAll (adrText^, adrTable, false, "<%", "%>");
return (true)}
else { //old-fashioned way
local (adr);
for adr in adrTable {
adrText^ = string.replaceAll (adrText^, "<%" + nameOf (adr^) + "%>", adr^, false)};
return (true)}};
bundle { //if the app version is less than 7.1b1, patch the templates to use the old macro delimiters
if date.versionLessThan (Frontier.version (), "7.1b1") {
if defined (adrsite^.["#prefs"].macroStartCharacters) {
itemTemplate = string.replaceAll (itemTemplate, adrsite^.["#prefs"].macroStartCharacters, "{", false);
dayTemplate = string.replaceAll (dayTemplate, adrsite^.["#prefs"].macroStartCharacters, "{", false)};
if defined (adrsite^.["#prefs"].macroEndCharacters) {
itemTemplate = string.replaceAll (itemTemplate, adrsite^.["#prefs"].macroEndCharacters, "}", false);
dayTemplate = string.replaceAll (dayTemplate, adrsite^.["#prefs"].macroEndCharacters, "}", false)}}};
bundle { //set bodytext
local (ctdays = 0, lastday = -1, itemstext = "", lastitemdate);
on addDay () {
local (t);
new (tableType, @t); //PBS 10/30/01: table used for string replacements
t.longDate = date.longstring (lastitemdate);
t.shortDate = date.shortstring (lastitemdate);
t.dayOfWeek = date.dayOfWeekToString (date.dayOfWeek (lastitemdate));
t.items = itemstext; itemstext = "";
bundle { //build archiveLink
local (url = radio.weblog.getUrlForDay (lastitemdate, catname, adrblog) );
t.archiveLink = "<a href=\"" + url + "\">" + radio.weblog.getThemePref ("archiveLinkImgTag", adrblog) + "</a>"};
<<bundle //old code
<<if flStaticRendering //the archiveLink points at the static site
<<local (extension = "")
<<local (partialpath = adrblog^.prefs.homePageFilePath)
<<local (fname = string.nthField (partialpath, '/', string.countFields (partialpath, '/')))
<<if fname contains "."
<<extension = "." + string.nthField (fname, '.', string.countFields (fname, '.'))
<<t.archiveLink = "<a href=\"/" + yr + "/" + mo + "/" + dy + extension + "\">" + adrblog^.prefs.archiveLinkImgTag + "</a>"
<<else //dynamic link
<<t.archiveLink = "<a href=\"?d=" + yr + "/" + mo + "/" + dy + "\">" + adrblog^.prefs.archiveLinkImgTag + "</a>"
bundle { //process (pseudo) macros -- PBS 10/29/01
local (s = daytemplate); //PBS 10/29/01: make a copy of daytemplate, so day doesn't get repeated
doReplacements (@s, @t);
bodytext = bodytext + s;}};
bodytext = "";
local (i);
for i = sizeOf (adrblog^.posts) downto 1 {
local (adrpost);
adrpost = @adrblog^.posts [i];
bundle { //check min and max dates
if adrpost^.when < minDate {
break};
if adrpost^.when > maxDate {
continue}};
bundle { //only include if in category, if catname was specified
if catname != nil {
local (flskip = true);
if defined (adrpost^.categories) {
local (adrthiscat = @adrpost^.categories.[catname]);
if defined (adrthiscat^) {
if adrthiscat^ {
flskip = false}}};
if flskip {
continue}}};
bundle { //if rendering the home page, or a non-category archive page, skip posts with flNotOnHomePage == true
if catname == nil {
if defined (adrpost^.flNotOnHomePage) {
if adrpost^.flNotOnHomePage {
continue}}}};
bundle { //check date rollover
local (when = adrpost^.when, day, month, year, hour, minute, second);
date.get (when, @day, @month, @year, @hour, @minute, @second);
if lastday == -1 { //first time through loop
lastday = day}
else {
if day != lastday {
ctdays++;
if ctdays == maxdays {
break};
lastday = day;
addDay ()}}};
bundle { //call the callbacks -- 1/14/02; 7:15:52 PM by DW
try {
local (pta = html.getpagetableaddress ());
if pta^.radioresponder.flstaticrendering {
if not defined (user.radio.callbacks.publishItem) {
new (tabletype, @user.radio.callbacks.publishItem)};
local (adrcallback);
for adrcallback in @radio.weblog.builtinCallbacks.publishItem {
try {
adrcallback^ (adrpost)}};
for adrcallback in @user.radio.callbacks.publishItem {
try {
while typeOf (adrcallback^) == addressType {
adrcallback = adrcallback^};
adrcallback^ (adrpost)}}}}};
local (t);
new (tableType, @t);
bundle { //set macro strings
bundle { //set itemTitle
t.itemTitle = "";
if defined (adrpost^.title) {
if adrblog^.prefs.flTitleAndLinkOnPostForm {
if defined (adrpost^.link) {
t.itemTitle = "<a href=\"" + adrpost^.link + "\" class=\"weblogItemTitle\">" + adrpost^.title + "</a>"}
else {
local (url, flLink = false);
if adrblog^.prefs.flAutoGenerateLinks {
flLink = radio.weblog.getUrlForPost (adrpost, @url, catname, adrblog)};
if flLink {
t.itemTitle = "<a href=\"" + url + "\" class=\"weblogItemTitle\">" + adrpost^.title + "</a>"}
else {
t.itemTitle = adrpost^.title}}}}};
bundle { //set itemtext
<<t.itemtext = "<a name=\"a" + number (nameOf (adrpost^)) + "\"></a>" + string (adrpost^.text)
t.itemtext = string.trimWhiteSpace (string (adrpost^.text))};
bundle { //set itemNum
t.itemNum = number (nameOf (adrpost^))};
bundle { //set paddedItemNum
t.paddedItemNum = nameOf (adrpost^)};
bundle { //set permalink
local (url);
bundle { //build linkurl
radio.weblog.getUrlForPost (adrpost, @url, catname, adrblog)};
t.permalink = "<a href=\"" + url + "\">" + radio.weblog.getThemePref ("itemPermaLinkImgTag", adrblog) + "</a>"};
bundle { //set enclosure
if defined (adrpost^.enclosure) {
t.enclosure = "<a href=\"" + adrpost^.enclosure.url + "\">" + radio.weblog.getThemePref ("enclosureImgTag", adrblog) + "</a>"}
else {
t.enclosure = ""}};
bundle { //set editButton
if flIEditLinks {
local (url = radio.data.systemUrls.weblogEditor + "?itemtoedit=" + number (nameof (adrpost^)) + savedArgs);
t.editButton = "<a href=\"" + url + "\">" + editbuttonimg + "</a>"}
else {
t.editButton = ""}};
bundle { //set source
t.source = "";
if defined (adrpost^.sourceUrl) {
local (adrdata = xml.aggregator.init ());
local (adrsource = @adrdata^.services.[adrpost^.sourceUrl]);
if defined (adrsource^) {
if defined (adrsource^.compilation.channellink) and defined (adrsource^.compilation.channeltitle) {
t.source = "<a href=\"" + adrsource^.compilation.channellink + "\" title=\"Source: " + adrsource^.compilation.channeltitle + ".\">" + radio.weblog.getThemePref ("sourceImgTag", adrblog) + "</a>"}}}};
bundle { //set when
t.when = date.timestring (adrpost^.when)};
bundle { //set commentLink
t.commentLink = radio.html.commentLink (t.itemNum, adrblog)};
bundle { //set trackbackLink
t.trackbackLink = "";
if adrblog^.prefs.trackback.flEnabled {
if adrblog^.prefs.trackback.flAddTrackbackLinks {
try {
t.trackbackLink = radio.html.trackbackLink (t.itemNum, adrblog)}}}}};
local (s = itemtemplate);
doReplacements (@s, @t); //PBS 10/30/01: process (pseudo) macros
itemstext = itemstext + ("<a name=\"a" + number (nameOf (adrpost^)) + "\"></a>\r\n" + s);;
bundle { //trackback autodiscovery support
if adrblog^.prefs.trackback.flEnabled {
if string.lower (adrblog^.prefs.trackback.trackbackLinkText) contains "<%trackbackcount%>" {
local (pingurl = radio.weblog.getTrackbackPingUrl (adrpost, adrblog));
if sizeOf (pingurl) > 0 {
local (permalinkurl);
if radio.weblog.getUrlForPost (adrpost, @permalinkurl, catname, adrblog) {
local (tbtitle = "");
if defined (adrpost^.title) {
tbtitle = adrpost^.title};
if sizeOf (tbtitle) == 0 { //if the post doesn't have a title, use the blog's title
tbtitle = adrblog^.prefs.title};
local (excerpt);
bundle { //create exceprt from post text
excerpt = string.firstsentence (searchengine.stripmarkup (t.itemtext));
excerpt = string.replaceAll (excerpt, "\r\n", "\r");
excerpt = string.replaceAll (excerpt, "\r", " ")};
itemstext = "<!--\r" + trackback.getAutodiscoveryXml (permalinkurl, pingUrl, tbtitle, "", excerpt, adrblog^.prefs.authorName, adrpost^.when) + "-->" + itemstext}}}}};
lastitemdate = adrpost^.when};
<<ctitems++
<<if catname != nil
<<if ctitems >= adrblog^.prefs.maxOutputItemsPerChannel
<<if mindate > dateZero and maxDate < dateInfinity
<<break
if itemstext != "" { //there was some text left over when loop finished
addDay ()}};
bodytext = string.replaceAll (bodytext, "\r\n", "\r");
if adrblog^.prefs.flCommentLinksEnabled { //add JavaScript to support comment counters
if string.lower (adrblog^.prefs.commentLinkText) contains "<%commentcount%>" {
bodytext = "<script src=\"" + adrblog^.prefs.commentsPageUrl + "?u=" + user.radio.prefs.usernum + "&c=counts\" type=\"text/javascript\"></script>\r" + bodytext}};
if adrblog^.prefs.trackback.flEnabled { //add JavaScript to support TrackBack counters
try {
if string.lower (adrblog^.prefs.trackback.trackbackLinkText) contains "<%trackbackcount%>" {
bodytext = "<script src=\"" + adrblog^.prefs.trackback.trackbackPageUrl + "?u=" + user.radio.prefs.usernum + "&c=counts\" type=\"text/javascript\"></script>\r" + bodytext}}};
return (bodytext)}
<<bundle //test code
<<local (folder = user.radio.prefs.wwwfolder)
<<local (daytemplate = string (file.readwholefile (folder + "#dayTemplate.txt")))
<<local (itemtemplate = string (file.readwholefile (folder + "#itemTemplate.txt")))
<<local (catname = nil, flSameMachine = false)
<<local (htmltext = render (radio.weblog.init (), daytemplate, itemtemplate, catname, flSameMachine, flStaticRendering:true))
<<local (f = folder + "tmp.html")
<<file.writewholefile (f, htmltext)
<<webbrowser.openurl ("http://127.0.0.1:" + user.inetd.config.http.port + "/tmp.html")
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.