[Home]WikiPatches/XmlRss

UseModWiki | WikiPatches | RecentChanges | Preferences

Yet another patch to produce RSS output. This one -- unlike WikiPatches/ModWiki -- uses the XML::RSS module. It grew out of the WikiPatches/ModWiki patch, however. It only adds more variables and uses a different GetRcRss? subroutine. This is why some of the variables have the same name as in the WikiPatches/ModWiki patch.

Add the new variables to the Configuration/constant variables section:

use vars qw(@RcDays @HtmlPairs @HtmlSingle
  $TempDir ...
  ...
  $InterWikiMoniker $SiteDescription
  $RssImageUrl $RssPublisher $RssContributor $RssRights);

In the Configuration section, set the variables:

$InterWikiMoniker = "EmacsWiki"; # InterWiki prefix for this wiki. (for RSS)
$SiteDescription  = "This is the Emacs and XEmacs know-how repository.";  # Description of this wiki. (for RSS)
$RssImageUrl       = "http://www.emacswiki.org/emacs_rss.png"; # URL to image to associate with your RSS feed
$RssPublisher     = "Alex Schroeder";
$RssContributor   = "The EmacsWiki community";
$RssRights        = "Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation.";

As in the WikiPatches/ModWiki patch, DoRc now accepts a reference to a subroutine.

In BrowsePage, add the new parameter to the call to DoRc:

  if (($id eq $RCName) || (T($RCName) eq $id) || (T($id) eq $RCName)) {
    print $fullHtml;
    &DoRc(\&GetRcHtml);
    print "<hr>\n"  if (!&GetParam('embed', $EmbedWiki));
    print &GetFooterText($id, $goodRevision);
    return;
  }

The changes to DoRc and GetRcHtml in the old script are pretty extensive and confusing so here is just a replacement chunk. Replace the old DoRc and GetRcHtml with the following code. Personally I find the code really hairy, but that is how it was done in the WikiPatches/ModWiki patch, and I did not feel like reinventing yet another wheel.

Here is how to read the patch:

  1. Add a line to set $GetRC according to @_ (the new parameter)
  2. Add a line to set $showHTML if $GetRC is the HTML function
  3. Enclose all HTML emitting code with if( $showHTML )
  4. Split some of the code into its own subrouting, GetRc. This takes three parameters: A block to execute for every day, a block to execute for every line, and the lines themselves.
  5. Change GetRcHtml such that it now calls GetRc with the necessary code passed in the two blocks.
  6. Add a new GetRcRss subroutine that also uses GetRc.
  7. Add a tiny DoRss subroutine.

Note that in order to use PRE tags to escape this source code, I had to change some existing </pre> tags to </pre" . "> -- you can undo this, of course.

By default, the RSS returned is limited to first 15 items. This limit is recommended in the standard. If you want more items, pass the rsslimit parameter along with the upper limit you want, or use `all' to indicate that you want the maximum. Here is a reminder of the more usefull options you can use: `days=12' is the number of days, `from' is the start time, `showedits=1' includes minor edits, `showedits=2' shows only minor edits, `all=1' shows all changes, not only the latest change for every page, and `newtop=0' shows the latest changes at the end of the list. Watch out: If you use `newtop=0', you should probably use `rsslimit=all', too.

Validation:

sub DoRc {
  my ($GetRC) = @_;
  my ($fileData, $rcline, $i, $daysago, $lastTs, $ts, $idOnly);
  my (@fullrc, $status, $oldFileData, $firstTs, $errorText);
  my $starttime = 0;
  my $showbar = 0;
  my $showHTML = $GetRC eq \&GetRcHtml; # Special (normative) case

  if (&GetParam("from", 0)) {
    $starttime = &GetParam("from", 0);
    if( $showHTML ) {
      print "<h2>" . Ts('Updates since %s', &TimeToText($starttime))
            . "</h2>\n";
    }
  } else {
    $daysago = &GetParam("days", 0);
    $daysago = &GetParam("rcdays", 0)  if ($daysago == 0);
    if ($daysago) {
      $starttime = $Now - ((24*60*60)*$daysago);
      if( $showHTML ) {
        print "<h2>" . Ts('Updates in the last %s day'
                          . (($daysago != 1)?"s":""), $daysago) . "</h2>\n";
      }
      # Note: must have two translations (for "day" and "days")
      # Following comment line is for translation helper script
      # Ts('Updates in the last %s days', '');
    }
  }
  if ($starttime == 0) {
    $starttime = $Now - ((24*60*60)*$RcDefault);
    if( $showHTML ) {
      print "<h2>" . Ts('Updates in the last %s day'
                        . (($RcDefault != 1)?"s":""), $RcDefault) . "</h2>\n";
    }
    # Translation of above line is identical to previous version
  }

  # Read rclog data (and oldrclog data if needed)
  ($status, $fileData) = &ReadFile($RcFile);
  $errorText = "";
  if (!$status) {
    # Save error text if needed.
    $errorText = '<p><strong>' . Ts('Could not open %s log file', $RCName)
                 . ":</strong> $RcFile<p>"
                 . T('Error was') . ":\n<pre>$!</pre" . ">\n" . '<p>'
    . T('Note: This error is normal if no changes have been made.') . "\n";
  }
  @fullrc = split(/\n/, $fileData);
  $firstTs = 0;
  if (@fullrc > 0) {  # Only false if no lines in file
    ($firstTs) = split(/$FS3/, $fullrc[0]);
  }
  if (($firstTs == 0) || ($starttime <= $firstTs)) {
    ($status, $oldFileData) = &ReadFile($RcOldFile);
    if ($status) {
      @fullrc = split(/\n/, $oldFileData . $fileData);
    } else {
      if ($errorText ne "") {  # could not open either rclog file
        print $errorText;
        print "<p><strong>"
              . Ts('Could not open old %s log file', $RCName)
              . ":</strong> $RcOldFile<p>"
              . T('Error was') . ":\n<pre>$!</pre" . ">\n";
        return;
      }
    }
  }
  $lastTs = 0;
  if (@fullrc > 0) {  # Only false if no lines in file
    ($lastTs) = split(/$FS3/, $fullrc[$#fullrc]);
  }
  $lastTs++  if (($Now - $lastTs) > 5);  # Skip last unless very recent

  $idOnly = &GetParam("rcidonly", "");
  if ($idOnly && $showHTML) {
    print '<b>(' . Ts('for %s only', &ScriptLink($idOnly, $idOnly))
          . ')</b><br>';
  }

  if( $showHTML ) {
    foreach $i (@RcDays) {
      print " | "  if $showbar;
      $showbar = 1;
      print &ScriptLink("action=rc&days=$i",
                        Ts('%s day' . (($i != 1)?'s':''), $i));
        # Note: must have two translations (for "day" and "days")
        # Following comment line is for translation helper script
        # Ts('%s days', '');
    }
    print "<br>" . &ScriptLink("action=rc&from=$lastTs",
                               T('List new changes starting from'));
    print " " . &TimeToText($lastTs) . "<br>\n";
  }

  # Later consider a binary search?
  $i = 0;
  while ($i < @fullrc) {  # Optimization: skip old entries quickly
    ($ts) = split(/$FS3/, $fullrc[$i]);
    if ($ts >= $starttime) {
      $i -= 1000  if ($i > 0);
      last;
    }
    $i += 1000;
  }
  $i -= 1000  if (($i > 0) && ($i >= @fullrc));
  for (; $i < @fullrc ; $i++) {
    ($ts) = split(/$FS3/, $fullrc[$i]);
    last if ($ts >= $starttime);
  }
  if ($i == @fullrc && $showHTML) {
    print '<br><strong>' . Ts('No updates since %s',
                              &TimeToText($starttime)) . "</strong><br>\n";
  } else {
    splice(@fullrc, 0, $i);  # Remove items before index $i
    # Later consider an end-time limit (items older than X)
    print &$GetRC(@fullrc);
  }
  print '<p>' . Ts('Page generated %s', &TimeToText($Now)), "<br>\n" if $showHTML;
}

sub GetRc {
  my $printDailyTear = shift;
  my $printRCLine = shift;
  my @outrc = @_;
  my ($rcline, $date, $newtop, $author);
  my ($showedit, $link, $all, $idOnly);
  my ($ts, $pagename, $summary, $isEdit, $host, $kind, $extraTemp);
  my %extra = ();
  my %changetime = ();
  my %pagecount = ();

  # Slice minor edits
  $showedit = &GetParam("rcshowedit", $ShowEdits);
  $showedit = &GetParam("showedit", $showedit);
  if ($showedit != 1) {
    my @temprc = ();
    foreach $rcline (@outrc) {
      ($ts, $pagename, $summary, $isEdit, $host) = split(/$FS3/, $rcline);
      if ($showedit == 0) {  # 0 = No edits
        push(@temprc, $rcline)  if (!$isEdit);
      } else {               # 2 = Only edits
        push(@temprc, $rcline)  if ($isEdit);
      }
    }
    @outrc = @temprc;
  }

  # Later consider folding into loop above?
  # Later add lines to assoc. pagename array (for new RC display)
  foreach $rcline (@outrc) {
    ($ts, $pagename) = split(/$FS3/, $rcline);
    $pagecount{$pagename}++;
    $changetime{$pagename} = $ts;
  }
  $date = "";
  $all = &GetParam("rcall", 0);
  $all = &GetParam("all", $all);
  $newtop = &GetParam("rcnewtop", $RecentTop);
  $newtop = &GetParam("newtop", $newtop);
  $idOnly = &GetParam("rcidonly", "");

  @outrc = reverse @outrc if ($newtop);
  foreach $rcline (@outrc) {
    ($ts, $pagename, $summary, $isEdit, $host, $kind, $extraTemp)
      = split(/$FS3/, $rcline);
    # Later: need to change $all for new-RC?
    next  if ((!$all) && ($ts < $changetime{$pagename}));
    next  if (($idOnly ne "") && ($idOnly ne $pagename));
    %extra = split(/$FS2/, $extraTemp, -1);
    if ($date ne &CalcDay($ts)) {
      $date = &CalcDay($ts);
      &$printDailyTear($date);
    }
    &$printRCLine( $pagename, $ts, $host, $extra{'name'}, $extra{'id'}, 
                   $summary, $isEdit, $pagecount{$pagename},
                   $extra{'revision'} );
  }
}

sub GetRcHtml {
  my ($html, $inlist, $all, $rcchangehist);
  my ($tEdit, $tChanges, $tDiff);

  # Optimize param fetches out of main loop
  $all = &GetParam("rcall", 0);
  $all = &GetParam("all", $all);
  $rcchangehist = &GetParam("rcchangehist", 1);

  # Optimize translations out of main loop
  $tEdit    = T('(edit)');
  $tDiff    = T('(diff)');
  $tChanges = T('changes');

  GetRc

    # printDailyTear
    sub {
      my $date = shift;
      if ($inlist) {
        $html .= "</UL>\n";
        $inlist = 0;
      }

      $html .= "<p><strong>" . $date . "</strong><p>\n";

      if (!$inlist) {
        $html .= "<UL>\n";
        $inlist = 1;
      }
    },

    # printRCLine
    sub {
    my( $pagename, $timestamp, $host, $userName, $userID, $summary, $isEdit, $pagecount, $revision ) = @_;
      my( $author, $sum, $edit, $count, $link );
      $host = &QuoteHtml($host);
      if (defined($userName) && defined($userID)) {
        $author = &GetAuthorLink($host, $userName, $userID);
      } else {
        $author = &GetAuthorLink($host, "", 0);
      }
      $sum = "";
      if (($summary ne "") && ($summary ne "*")) {
        $summary = &QuoteHtml($summary);
        $sum = "<strong>[$summary]</strong> ";
      }
      $edit = "";
      $edit = "<em>$tEdit</em> "  if ($isEdit);
      $count = "";
      if ((!$all) && ($pagecount > 1)) {
        $count = "($pagecount ";
        if ($rcchangehist) {
          $count .= &GetHistoryLink($pagename, $tChanges);
        } else {
          $count .= $tChanges;
        }
        $count .= ") ";
      }
      $link = "";
      if ($UseDiff && &GetParam("diffrclink", 1)) {
        $link .= &ScriptLinkDiff(4, $pagename, $tDiff, "") . "  ";
      }
      $link .= &GetPageLink($pagename);
      $html .= "<li>$link ";

      # Later do new-RC looping here.
      $html .=  &CalcTime($timestamp) . " $count$edit" . " $sum";
      $html .= ". . . . . $author\n";  # Make dots optional?
    },

    @_;

  $html .= "</UL>\n" if ($inlist);
  return $html;
}

sub GetRcRss {
  my ($QuotedFullUrl, $ChannelAbout, $diffPrefix, $historyPrefix);

  # Normally get URL from script, but allow override.
  $FullUrl = $q->url(-full=>1)  if ($FullUrl eq "");
  $QuotedFullUrl = &QuoteHtml($FullUrl);
  $diffPrefix = $QuotedFullUrl . &QuoteHtml("?action=browse\&diff=4\&id=");
  $historyPrefix = $QuotedFullUrl . &QuoteHtml("?action=history\&id=");

  $SiteDescription = &QuoteHtml($SiteDescription);

  my $timestamp = time + $TimeZoneOffset;
  my ($sec, $min, $hour, $mday, $mon, $year) = localtime($timestamp);
  $year += 1900;
  my $date = sprintf( "%4d-%02d-%02dT%02d:%02d:%02d+%02d:00",
                   $year, $mon+1, $mday, $hour, $min, $sec,
                   $TimeZoneOffset/(60*60) );

  require XML::RSS;
  my $rss = new XML::RSS (version => '1.0', encoding => $HttpCharset);

  $rss->add_module(
    prefix => 'wiki',
    uri    => 'http://purl.org/rss/1.0/modules/wiki/'
  );

  $rss->channel(
    title         => &QuoteHtml($SiteName),
    link          => $QuotedFullUrl . &QuoteHtml("?$RCName"),
    description   => $SiteDescription,
    dc => {
      publisher   => $RssPublisher,
      contributor => $RssContributor,
      date        => $date,
      rights      => $RssRights,
    },
    wiki => {
      interwiki   => $InterWikiMoniker,
    },
  );

  $rss->image(
    title  => &QuoteHtml($SiteName),
    url    => $RssImageUrl,
    link   => $QuotedFullUrl,
  );

  # Now call GetRc with some blocks of code as parameters:

  GetRc

    # printDailyTear
    sub {},

    # printRCLine
    sub {
      my( $pagename, $timestamp, $host, $userName, $userID, $summary,
          $isEdit, $pagecount, $revision ) = @_;
      my( $description, $author, $status, $importance, $date );

      $timestamp += $TimeZoneOffset;
      my ($sec, $min, $hour, $mday, $mon, $year) = localtime($timestamp);
      $year += 1900;
      $date = sprintf( "%4d-%02d-%02dT%02d:%02d:%02d+%02d:00",
        $year, $mon+1, $mday, $hour, $min, $sec, $TimeZoneOffset/(60*60) );

      if (($summary ne "") && ($summary ne "*")) {
        $description = &QuoteHtml($summary);
      }

      if( $userName ) {
        $author = &QuoteHtml($userName);
      } else {
        $author = $host;
      }

      $status = (1 == $revision) ? 'new' : 'updated';
      $importance = $isEdit ? 'minor' : 'major';

      $rss->add_item(
        title         => &QuoteHtml($pagename),
        link          => $QuotedFullUrl . '?action=browse'
                                        . '&id=' . $pagename
                                        . '&revision=' . $revision,
        description   => $description,
        dc => {
          date        => $date,
          contributor => $author,
        },
        wiki => {
          status      => $status,
          importance  => $importance,
          diff        => $diffPrefix . $pagename,
          version     => $revision,
          history     => $historyPrefix . $pagename,
        },
      );
    },

    # RC Lines
    @_;

  # Only take the first 15 entries
  my $limit = &GetParam("rsslimit", 14);
  if ($limit != 'all') {
    @{$rss->{'items'}} = @{$rss->{'items'}}[0..$limit];
  }

  return $rss->as_string;
}

sub DoRss {
  print "Content-type: text/plain\n\n";
  &DoRc(\&GetRcRss);
}

This last subroutine, DoRss is what we need to call when we want the RSS output. In DoOtherRequest, therefore, we add the following fragment, anywhere amongst the various actions:

    } elsif ($action eq "rss") {
      &DoRss();


According to http://feeds.archive.org/validator/docs/warning/UnexpectedContentType.html the media type of a RSS feed shouldn't be 'text/plain', but something like 'application/xml'. So if we change the first line in DoRss() to
 print "Content-type: application/xml\n\n";
everything should be fine.
--gerhard


"[This document] summarizes the best current practice for using various Internet media types for serving various XHTML Family documents. In summary, 'application/xhtml+xml' SHOULD be used for XHTML Family documents, and the use of 'text/html' SHOULD be limited to HTML-compatible XHTML 1.0 documents. 'application/xml' and 'text/xml' MAY also be used, but whenever appropriate, 'application/xhtml+xml' SHOULD be used rather than those generic XML media types."


WikiPatches

UseModWiki | WikiPatches | RecentChanges | Preferences
Edit text of this page | View other revisions | Search MetaWiki
Last edited July 7, 2005 1:50 am by 200.210.178.143 (diff)
Search: